Merge m-c to b2ginbound a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 01 May 2015 17:24:12 -0700
changeset 273421 647d32b7a5a6d3c9bd3b30d84549917de96ce157
parent 273420 f391b582103ec24cccddb951e6eacc56cdbfa1e9 (current diff)
parent 273409 767d72d50db81f48963cce3fb34ac544fb1a12df (diff)
child 273422 c301b147dbd13b147e95c7b3959231c0bf25ed79
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to b2ginbound a=merge
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -95,17 +95,17 @@ nsContextMenu.prototype = {
 
     if (this.isTextSelected && !this.onLink &&
         this.selectionInfo && this.selectionInfo.linkURL) {
       this.linkURL = this.selectionInfo.linkURL;
       try {
         this.linkURI = makeURI(this.linkURL);
       } catch (ex) {}
 
-      this.linkText = this.selectionInfo.linkText;
+      this.linkTextStr = this.selectionInfo.linkText;
       this.onPlainTextLink = true;
     }
 
     var shouldShow = this.onSaveableLink || isMailtoInternal || this.onPlainTextLink;
     var isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
     this.showItem("context-openlink", shouldShow && !isWindowPrivate);
     this.showItem("context-openlinkprivate", shouldShow);
     this.showItem("context-openlinkintab", shouldShow);
@@ -519,17 +519,17 @@ nsContextMenu.prototype = {
     this.onKeywordField    = false;
     this.mediaURL          = "";
     this.onLink            = false;
     this.onMailtoLink      = false;
     this.onSaveableLink    = false;
     this.link              = null;
     this.linkURL           = "";
     this.linkURI           = null;
-    this.linkText          = "";
+    this.linkTextStr       = "";
     this.linkProtocol      = "";
     this.linkDownload      = "";
     this.linkHasNoReferrer = false;
     this.onMathML          = false;
     this.inFrame           = false;
     this.inSrcdocFrame     = false;
     this.inSyntheticDoc    = false;
     this.hasBGImage        = false;
@@ -695,17 +695,17 @@ nsContextMenu.prototype = {
 
           // Target is a link or a descendant of a link.
           this.onLink = true;
 
           // Remember corresponding element.
           this.link = elem;
           this.linkURL = this.getLinkURL();
           this.linkURI = this.getLinkURI();
-          this.linkText = this.getLinkText();
+          this.linkTextStr = this.getLinkText();
           this.linkProtocol = this.getLinkProtocol();
           this.onMailtoLink = (this.linkProtocol == "mailto");
           this.onSaveableLink = this.isLinkSaveable( this.link );
           this.linkHasNoReferrer = BrowserUtils.linkHasNoReferrer(elem);
           try {
             if (elem.download) {
               // Ignore download attribute on cross-origin links
               this.principal.checkMayLoad(this.linkURI, false, true);
@@ -1276,17 +1276,17 @@ nsContextMenu.prototype = {
 
     // kick off the channel with our proxy object as the listener
     channel.asyncOpen(new saveAsListener(), null);
   },
 
   // Save URL of clicked-on link.
   saveLink: function() {
     urlSecurityCheck(this.linkURL, this.principal);
-    this.saveHelper(this.linkURL, this.linkText, null, true, this.ownerDoc,
+    this.saveHelper(this.linkURL, this.linkTextStr, null, true, this.ownerDoc,
                     gContextMenuContentData.documentURIObject,
                     gContextMenuContentData.frameOuterWindowID,
                     this.linkDownload);
   },
 
   // Backwards-compatibility wrapper
   saveImage : function() {
     if (this.onCanvas || this.onImage)
@@ -1491,16 +1491,21 @@ nsContextMenu.prototype = {
         if (!text || !text.match(/\S/))
           text = this.linkURL;
       }
     }
 
     return text;
   },
 
+  // Kept for addon compat
+  linkText: function() {
+    return this.linkTextStr;
+  },
+
   isMediaURLReusable: function(aURL) {
     return !/^(?:blob|mediasource):/.test(aURL);
   },
 
   toString: function () {
     return "contextMenu.target     = " + this.target + "\n" +
            "contextMenu.onImage    = " + this.onImage + "\n" +
            "contextMenu.onLink     = " + this.onLink + "\n" +
@@ -1558,17 +1563,17 @@ nsContextMenu.prototype = {
   },
 
   bookmarkThisPage: function CM_bookmarkThisPage() {
     window.top.PlacesCommandHook.bookmarkPage(this.browser, PlacesUtils.bookmarksMenuFolderId, true);
   },
 
   bookmarkLink: function CM_bookmarkLink() {
     window.top.PlacesCommandHook.bookmarkLink(PlacesUtils.bookmarksMenuFolderId,
-                                              this.linkURL, this.linkText);
+                                              this.linkURL, this.linkTextStr);
   },
 
   addBookmarkForFrame: function CM_addBookmarkForFrame() {
     var doc = this.target.ownerDocument;
     var uri = doc.documentURIObject;
 
     var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
     if (itemId == -1) {
@@ -1652,17 +1657,17 @@ nsContextMenu.prototype = {
     if (this.onImage)
       return this.mediaURL;
     return "";
   },
 
   // Formats the 'Search <engine> for "<selection or link text>"' context menu.
   formatSearchContextItem: function() {
     var menuItem = document.getElementById("context-searchselect");
-    let selectedText = this.isTextSelected ? this.textSelected : this.linkText;
+    let selectedText = this.isTextSelected ? this.textSelected : this.linkTextStr;
 
     // Store searchTerms in context menu item so we know what to search onclick
     menuItem.searchTerms = selectedText;
 
     // If the JS character after our truncation point is a trail surrogate,
     // include it in the truncated string to avoid splitting a surrogate pair.
     if (selectedText.length > 15) {
       let truncLength = 15;
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3342,49 +3342,60 @@
               } else if (event.type == "TabRemotenessChange") {
                 this.onRemotenessChange(event.target);
               }
 
               this.postActions();
             },
 
             /*
-             * Telemetry related helpers for recording tab switch timing.
+             * Telemetry and Profiler related helpers for recording tab switch
+             * timing.
              */
 
             startTabSwitch: function () {
               TelemetryStopwatch.start("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
+              this.addMarker("AsyncTabSwitch:Start");
             },
 
             finishTabSwitch: function () {
               if (this.requestedTab && this.getTabState(this.requestedTab) == this.STATE_LOADED) {
                 let time = TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
                 if (time != -1) {
                   TelemetryStopwatch.finish("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
                   this.log("DEBUG: tab switch time = " + time);
+                  this.addMarker("AsyncTabSwitch:Finish");
                 }
               }
             },
 
             spinnerDisplayed: function () {
               if (this.spinnerTab) {
                 TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
+                this.addMarker("AsyncTabSwitch:SpinnerShown");
               }
             },
 
             spinnerHidden: function () {
               if (this.spinnerTab) {
                 this.log("DEBUG: spinner time = " +
                          TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window));
                 TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
+                this.addMarker("AsyncTabSwitch:SpinnerHidden");
                 // we do not get a onPaint after displaying the spinner
                 this.finishTabSwitch();
               }
             },
 
+            addMarker: function(marker) {
+              if (Services.profiler) {
+                Services.profiler.AddMarker(marker);
+              }
+            },
+
             /*
              * Debug related logging for switcher.
              */
 
             _useDumpForLogging: false,
             _logInit: false,
 
             logging: function () {
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/plugins/blocklist_proxy.js
@@ -0,0 +1,78 @@
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cm = Components.manager;
+
+const kBlocklistServiceUUID = "{66354bc9-7ed1-4692-ae1d-8da97d6b205e}";
+const kBlocklistServiceContractID = "@mozilla.org/extensions/blocklist;1";
+const kBlocklistServiceFactory = Cm.getClassObject(Cc[kBlocklistServiceContractID], Ci.nsIFactory);
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+/*
+ * A lightweight blocklist proxy for the testing purposes.
+ */
+let BlocklistProxy = {
+  _uuid: null,
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsIBlocklistService,
+                                         Ci.nsITimerCallback]),
+
+  init: function() {
+    if (!this._uuid) {
+      this._uuid =
+        Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator)
+                                           .generateUUID();
+      Cm.nsIComponentRegistrar.registerFactory(this._uuid, "",
+                                               "@mozilla.org/extensions/blocklist;1",
+                                               this);
+    }
+  },
+
+  uninit: function() {
+    if (this._uuid) {
+      Cm.nsIComponentRegistrar.unregisterFactory(this._uuid, this);
+      Cm.nsIComponentRegistrar.registerFactory(Components.ID(kBlocklistServiceUUID),
+                                               "Blocklist Service",
+                                               "@mozilla.org/extensions/blocklist;1",
+                                               kBlocklistServiceFactory);
+      this._uuid = null;
+    }
+  },
+
+  notify: function (aTimer) {
+  },
+
+  observe: function (aSubject, aTopic, aData) {
+  },
+
+  isAddonBlocklisted: function (aAddon, aAppVersion, aToolkitVersion) {
+    return false;
+  },
+
+  getAddonBlocklistState: function (aAddon, aAppVersion, aToolkitVersion) {
+    return 0; // STATE_NOT_BLOCKED
+  },
+
+  getPluginBlocklistState: function (aPluginTag, aAppVersion, aToolkitVersion) {
+    return 0; // STATE_NOT_BLOCKED
+  },
+
+  getAddonBlocklistURL: function (aAddon, aAppVersion, aToolkitVersion) {
+    return "";
+  },
+
+  getPluginBlocklistURL: function (aPluginTag) {
+    return "";
+  },
+
+  getPluginInfoURL: function (aPluginTag) {
+    return "";
+  },
+}
+
+BlocklistProxy.init();
+addEventListener("unload", () => {
+  BlocklistProxy.uninit();
+});
--- a/browser/base/content/test/plugins/browser.ini
+++ b/browser/base/content/test/plugins/browser.ini
@@ -1,17 +1,12 @@
 [DEFAULT]
-# These tests all fail with e10s enabled.
-# * Bug 899347 - no e10s click-to-play support
-# * Bug 921916 - no plugin events
-# * Bug XXXXX - no plugins in content processes ("Error: You cannot use the AddonManager in child processes!")
-# * Bug 866413 - PageInfo doesn't work in e10s [browser_pageInfo_plugins.js]
-# * Bug 921957 - remote webprogress doesn't supply originalURI attribute on the request object [browser_clearplugindata.js]
-skip-if = buildapp == "mulet" || e10s
+skip-if = buildapp == "mulet"
 support-files =
+  blocklist_proxy.js
   blockNoPlugins.xml
   blockPluginHard.xml
   blockPluginInfoURL.xml
   blockPluginVulnerableNoUpdate.xml
   blockPluginVulnerableUpdatable.xml
   browser_clearplugindata.html
   browser_clearplugindata_noage.html
   head.js
@@ -48,35 +43,49 @@ support-files =
 [browser_bug743421.js]
 [browser_bug744745.js]
 [browser_bug787619.js]
 [browser_bug797677.js]
 [browser_bug812562.js]
 [browser_bug818118.js]
 [browser_bug820497.js]
 [browser_clearplugindata.js]
+skip-if = e10s # bug 1149253
 [browser_CTP_context_menu.js]
-skip-if = toolkit == "gtk2" || toolkit == "gtk3"   # browser_CTP_context_menu.js fails intermittently on Linux (bug 909342)
+skip-if = toolkit == "gtk2" || toolkit == "gtk3"   # fails intermittently on Linux (bug 909342)
 [browser_CTP_crashreporting.js]
 skip-if = !crashreporter
 [browser_CTP_data_urls.js]
 [browser_CTP_drag_drop.js]
+skip-if = e10s # misc. issues, bug 1156871
 [browser_CTP_hide_overlay.js]
 [browser_CTP_iframe.js]
 skip-if = os == 'linux' || os == 'mac' # Bug 984821
 [browser_CTP_multi_allow.js]
 [browser_CTP_nonplugins.js]
 [browser_CTP_notificationBar.js]
 [browser_CTP_outsideScrollArea.js]
 [browser_CTP_remove_navigate.js]
 [browser_CTP_resize.js]
 [browser_CTP_zoom.js]
-[browser_globalplugin_crashinfobar.js]
-[browser_pageInfo_plugins.js]
+[browser_blocking.js]
+[browser_plugins_added_dynamically.js]
 [browser_pluginnotification.js]
-[browser_pluginplaypreview.js]
-[browser_pluginplaypreview2.js]
-[browser_pluginplaypreview3.js]
+[browser_plugin_infolink.js]
+skip-if = e10s # bug 1160166
+[browser_plugin_reloading.js]
+[browser_blocklist_content.js]
+skip-if = !e10s
+[browser_globalplugin_crashinfobar.js]
+skip-if = !crashreporter
 [browser_pluginCrashCommentAndURL.js]
 skip-if = !crashreporter
-[browser_plugins_added_dynamically.js]
+[browser_pageInfo_plugins.js]
+skip-if = e10s # Bug 866413
+[browser_pluginplaypreview.js]
+skip-if = e10s # bug 1148827
+[browser_pluginplaypreview2.js]
+skip-if = e10s # bug 1148827
+[browser_pluginplaypreview3.js]
+skip-if = e10s # bug 1148827
 [browser_pluginCrashReportNonDeterminism.js]
 skip-if = !crashreporter || os == 'linux' # Bug 1152811
+
--- a/browser/base/content/test/plugins/browser_CTP_context_menu.js
+++ b/browser/base/content/test/plugins/browser_CTP_context_menu.js
@@ -1,116 +1,69 @@
-var rootDir = getRootDirectory(gTestPath);
-const gTestRoot = rootDir;
-const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-
-var gTestBrowser = null;
-var gNextTest = null;
-var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+let rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    gBrowser.removeCurrentTab();
+    window.focus();
+  });
+});
 
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
-    clearAllPluginPermissions();
-    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
-  });
+// Test that the activate action in content menus for CTP plugins works
+add_task(function* () {
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
-  let newTab = gBrowser.addTab();
-  gBrowser.selectedTab = newTab;
-  gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
+  gBrowser.selectedTab = gBrowser.addTab();
 
   Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
-
-  prepareTest(runAfterPluginBindingAttached(test1), gHttpTestRoot + "plugin_test.html");
-}
-
-function finishTest() {
-  clearAllPluginPermissions();
-  gTestBrowser.removeEventListener("load", pageLoad, true);
-  gBrowser.removeCurrentTab();
-  window.focus();
-  finish();
-}
-
-function pageLoad() {
-  // The plugin events are async dispatched and can come after the load event
-  // This just allows the events to fire before we then go on to test the states
-  executeSoon(gNextTest);
-}
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+  let bindingPromise = waitForEvent(gBrowser.selectedBrowser, "PluginBindingAttached", null, true, true);
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+  yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+  yield bindingPromise;
 
-function prepareTest(nextTest, url) {
-  gNextTest = nextTest;
-  gTestBrowser.contentWindow.location = url;
-}
-
-// Due to layout being async, "PluginBindAttached" may trigger later.
-// This wraps a function to force a layout flush, thus triggering it,
-// and schedules the function execution so they're definitely executed
-// afterwards.
-function runAfterPluginBindingAttached(func) {
-  return function() {
-    let doc = gTestBrowser.contentDocument;
-    let elems = doc.getElementsByTagName('embed');
-    if (elems.length < 1) {
-      elems = doc.getElementsByTagName('object');
-    }
-    elems[0].clientTop;
-    executeSoon(func);
-  };
-}
-
-// Test that the activate action in content menus for CTP plugins works
-function test1() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
   ok(popupNotification, "Test 1, Should have a click-to-play notification");
 
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 1, Plugin should not be activated");
+  // check plugin state
+  let pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser);
+  ok(!pluginInfo.activated, "plugin should not be activated");
+   
+  // Display a context menu on the test plugin so we can test
+  // activation menu options.
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    let bounds = plugin.getBoundingClientRect();
+    let left = (bounds.left + bounds.right) / 2;
+    let top = (bounds.top + bounds.bottom) / 2;
+    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("contextmenu", left, top, 2, 1, 0);
+  });
 
-  // When the popupshown DOM event is fired, the actual showing of the popup
-  // may still be pending. Clear the event loop before continuing so that
-  // subsequently-opened popups aren't cancelled by accident.
-  let goToNext = function(aEvent) {
-    window.document.removeEventListener("popupshown", goToNext, false);
-    executeSoon(function() {
-      test2();
-      aEvent.target.hidePopup();
-    });
-  };
-  window.document.addEventListener("popupshown", goToNext, false);
-  EventUtils.synthesizeMouseAtCenter(plugin,
-                                     { type: "contextmenu", button: 2 },
-                                     gTestBrowser.contentWindow);
-}
+  popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+  ok(popupNotification, "Should have a click-to-play notification");
+  ok(popupNotification.dismissed, "notification should be dismissed");
+
+  // fixes a occasional test timeout on win7 opt
+  yield promiseForCondition(() => document.getElementById("context-ctp-play"));
 
-function test2() {
-  let activate = window.document.getElementById("context-ctp-play");
-  ok(activate, "Test 2, Should have a context menu entry for activating the plugin");
-
-  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(notification, "Test 2, Should have a click-to-play notification");
-  ok(notification.dismissed, "Test 2, notification should be dismissed");
+  let actMenuItem = document.getElementById("context-ctp-play");
+  ok(actMenuItem, "Should have a context menu entry for activating the plugin");
 
-  // Trigger the click-to-play popup
-  activate.doCommand();
+  // Activate the plugin via the context menu
+  EventUtils.synthesizeMouseAtCenter(actMenuItem, {});
 
-  waitForCondition(() => !notification.dismissed,
-		   test3, "Test 2, waited too long for context activation");
-}
+  yield promiseForCondition(() => !PopupNotifications.panel.dismissed && PopupNotifications.panel.firstChild);
 
-function test3() {
   // Activate the plugin
   PopupNotifications.panel.firstChild._primaryButton.click();
 
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  waitForCondition(() => objLoadingContent.activated, test4, "Waited too long for plugin to activate");
-}
-
-function test4() {
-  finishTest();
-}
+  // check plugin state
+  pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser);
+  ok(pluginInfo.activated, "plugin should not be activated");
+});
--- a/browser/base/content/test/plugins/browser_CTP_crashreporting.js
+++ b/browser/base/content/test/plugins/browser_CTP_crashreporting.js
@@ -1,15 +1,13 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
+let rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
 const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
-const PLUGIN_PAGE = getRootDirectory(gTestPath) + "plugin_big.html";
-const PLUGIN_SMALL_PAGE = getRootDirectory(gTestPath) + "plugin_small.html";
+const PLUGIN_PAGE = gTestRoot + "plugin_big.html";
+const PLUGIN_SMALL_PAGE = gTestRoot + "plugin_small.html";
 
 /**
  * Takes an nsIPropertyBag and converts it into a JavaScript Object. It
  * will also convert any nsIPropertyBag's within the nsIPropertyBag
  * recursively.
  *
  * @param aBag
  *        The nsIPropertyBag to convert.
@@ -25,66 +23,63 @@ function convertPropertyBag(aBag) {
     if (value instanceof Ci.nsIPropertyBag) {
       value = convertPropertyBag(value);
     }
     result[name] = value;
   }
   return result;
 }
 
-function promisePopupNotificationShown(notificationID) {
-  return new Promise((resolve) => {
-    waitForNotificationShown(notificationID, resolve);
-  });
-}
-
 add_task(function* setup() {
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
 
   // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables plugin
   // crash reports.  This test needs them enabled.  The test also needs a mock
   // report server, and fortunately one is already set up by toolkit/
   // crashreporter/test/Makefile.in.  Assign its URL to MOZ_CRASHREPORTER_URL,
   // which CrashSubmit.jsm uses as a server override.
   let env = Cc["@mozilla.org/process/environment;1"].
             getService(Components.interfaces.nsIEnvironment);
   let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
   let serverURL = env.get("MOZ_CRASHREPORTER_URL");
   env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
   env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
 
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
   registerCleanupFunction(function cleanUp() {
+    clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
     env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport);
     env.set("MOZ_CRASHREPORTER_URL", serverURL);
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    window.focus();
   });
 });
 
 /**
  * Test that plugin crash submissions still work properly after
  * click-to-play activation.
  */
 add_task(function*() {
   yield BrowserTestUtils.withNewTab({
     gBrowser,
     url: PLUGIN_PAGE,
   }, function* (browser) {
-    let activated = yield ContentTask.spawn(browser, null, function*() {
-      let plugin = content.document.getElementById("test");
-      return plugin.QueryInterface(Ci.nsIObjectLoadingContent).activated;
-    });
-    ok(!activated, "Plugin should not be activated");
+    // Work around for delayed PluginBindingAttached
+    yield promiseUpdatePluginBindings(browser);
 
-    // Open up the click-to-play notification popup...
-    let popupNotification = PopupNotifications.getNotification("click-to-play-plugins",
-                                                               browser);
-    ok(popupNotification, "Should have a click-to-play notification");
+    let pluginInfo = yield promiseForPluginInfo("test", browser);
+    ok(!pluginInfo.activated, "Plugin should not be activated");
 
-    yield promisePopupNotificationShown(popupNotification);
-
-    // The primary button in the popup should activate the plugin.
+    // Simulate clicking the "Allow Always" button.
+    let notification = PopupNotifications.getNotification("click-to-play-plugins", browser);
+    yield promiseForNotificationShown(notification, browser);
     PopupNotifications.panel.firstChild._primaryButton.click();
 
     // Prepare a crash report topic observer that only returns when
     // the crash report has been successfully sent.
     let crashReportChecker = (subject, data) => {
       return (data == "success");
     };
     let crashReportPromise = TestUtils.topicObserved("crash-report-status",
@@ -94,18 +89,19 @@ add_task(function*() {
       let plugin = content.document.getElementById("test");
       plugin.QueryInterface(Ci.nsIObjectLoadingContent);
 
       yield ContentTaskUtils.waitForCondition(() => {
         return plugin.activated;
       }, "Waited too long for plugin to activate.");
 
       try {
-        plugin.crash();
-      } catch(e) {}
+        Components.utils.waiveXrays(plugin).crash();
+      } catch(e) {
+      }
 
       let doc = plugin.ownerDocument;
 
       let getUI = (anonid) => {
         return doc.getAnonymousElementByAttribute(plugin, "anonid", anonid);
       };
 
       // Now wait until the plugin crash report UI shows itself, which is
@@ -172,31 +168,21 @@ add_task(function*() {
  * Test that plugin crash submissions still work properly after
  * click-to-play with the notification bar.
  */
 add_task(function*() {
   yield BrowserTestUtils.withNewTab({
     gBrowser,
     url: PLUGIN_SMALL_PAGE,
   }, function* (browser) {
-    let activated = yield ContentTask.spawn(browser, null, function*() {
-      let plugin = content.document.getElementById("test");
-      return plugin.QueryInterface(Ci.nsIObjectLoadingContent).activated;
-    });
-    ok(!activated, "Plugin should not be activated");
+    // Work around for delayed PluginBindingAttached
+    yield promiseUpdatePluginBindings(browser);
 
-    // Open up the click-to-play notification popup...
-    let popupNotification = PopupNotifications.getNotification("click-to-play-plugins",
-                                                               browser);
-    ok(popupNotification, "Should have a click-to-play notification");
-
-    yield promisePopupNotificationShown(popupNotification);
-
-    // The primary button in the popup should activate the plugin.
-    PopupNotifications.panel.firstChild._primaryButton.click();
+    let pluginInfo = yield promiseForPluginInfo("test", browser);
+    ok(pluginInfo.activated, "Plugin should be activated from previous test");
 
     // Prepare a crash report topic observer that only returns when
     // the crash report has been successfully sent.
     let crashReportChecker = (subject, data) => {
       return (data == "success");
     };
     let crashReportPromise = TestUtils.topicObserved("crash-report-status",
                                                      crashReportChecker);
@@ -205,17 +191,17 @@ add_task(function*() {
       let plugin = content.document.getElementById("test");
       plugin.QueryInterface(Ci.nsIObjectLoadingContent);
 
       yield ContentTaskUtils.waitForCondition(() => {
         return plugin.activated;
       }, "Waited too long for plugin to activate.");
 
       try {
-        plugin.crash();
+        Components.utils.waiveXrays(plugin).crash();
       } catch(e) {}
     });
 
     // Wait for the notification bar to be displayed.
     let notification = yield waitForNotificationBar("plugin-crashed", browser);
 
     // Then click the button to submit the crash report.
     let buttons = notification.querySelectorAll(".notification-button");
@@ -239,9 +225,9 @@ add_task(function*() {
     file.initWithPath(Services.crashmanager._submittedDumpsDir);
     file.append(crashData.serverCrashID + ".txt");
     ok(file.exists(), "Submitted report file should exist");
     file.remove(false);
 
     is(crashData.extra.PluginContentURL, undefined,
        "URL should be absent from extra data when opt-in not checked");
   });
-});
\ No newline at end of file
+});
--- a/browser/base/content/test/plugins/browser_CTP_data_urls.js
+++ b/browser/base/content/test/plugins/browser_CTP_data_urls.js
@@ -1,212 +1,255 @@
-var rootDir = getRootDirectory(gTestPath);
-const gTestRoot = rootDir;
-const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-
-var gTestBrowser = null;
-var gNextTest = null;
-var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
-
-Components.utils.import("resource://gre/modules/Services.jsm");
+let rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+let gTestBrowser = null;
 
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
+add_task(function* () {
+  registerCleanupFunction(function () {
     clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    Services.prefs.clearUserPref("plugins.click_to_play");
     Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    gBrowser.removeCurrentTab();
+    window.focus();
+    gTestBrowser = null;
   });
-  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
-  var newTab = gBrowser.addTab();
-  gBrowser.selectedTab = newTab;
+  gBrowser.selectedTab =  gBrowser.addTab();
   gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
 
   Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
-
-  prepareTest(test1a, gHttpTestRoot + "plugin_data_url.html");
-}
-
-function finishTest() {
-  clearAllPluginPermissions();
-  gTestBrowser.removeEventListener("load", pageLoad, true);
-  gBrowser.removeCurrentTab();
-  window.focus();
-  finish();
-}
-
-function pageLoad() {
-  // The plugin events are async dispatched and can come after the load event
-  // This just allows the events to fire before we then go on to test the states
-  executeSoon(gNextTest);
-}
+  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
-function prepareTest(nextTest, url) {
-  gNextTest = nextTest;
-  gTestBrowser.contentWindow.location = url;
-}
-
-// Due to layout being async, "PluginBindAttached" may trigger later.
-// This wraps a function to force a layout flush, thus triggering it,
-// and schedules the function execution so they're definitely executed
-// afterwards.
-function runAfterPluginBindingAttached(func) {
-  return function() {
-    let doc = gTestBrowser.contentDocument;
-    let elems = doc.getElementsByTagName('embed');
-    if (elems.length < 1) {
-      elems = doc.getElementsByTagName('object');
-    }
-    elems[0].clientTop;
-    executeSoon(func);
-  };
-}
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+});
 
 // Test that the click-to-play doorhanger still works when navigating to data URLs
-function test1a() {
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_data_url.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
   let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(popupNotification, "Test 1a, Should have a click-to-play notification");
 
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 1a, Plugin should not be activated");
+  let pluginInfo = yield promiseForPluginInfo("test");
+  ok(!pluginInfo.activated, "Test 1a, plugin should not be activated");
 
-  gNextTest = runAfterPluginBindingAttached(test1b);
-  gTestBrowser.contentDocument.getElementById("data-link-1").click();
-}
+  let loadPromise = promiseTabLoadEvent(gBrowser.selectedTab);
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    // navigate forward to a page with 'test' in it
+    content.document.getElementById("data-link-1").click();
+  });
+  yield loadPromise;
 
-function test1b() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(popupNotification, "Test 1b, Should have a click-to-play notification");
 
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 1b, Plugin should not be activated");
+  pluginInfo = yield promiseForPluginInfo("test");
+  ok(!pluginInfo.activated, "Test 1b, plugin should not be activated");
+
+  let promise = promisePopupNotification("click-to-play-plugins");
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    let bounds = plugin.getBoundingClientRect();
+    let left = (bounds.left + bounds.right) / 2;
+    let top = (bounds.top + bounds.bottom) / 2;
+    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+  });
+  yield promise;
 
   // Simulate clicking the "Allow Always" button.
-  waitForNotificationShown(popupNotification, function() {
-    PopupNotifications.panel.firstChild._primaryButton.click();
+  let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
+    PopupNotifications.panel.firstChild;
+  yield promiseForCondition(condition);
+  PopupNotifications.panel.firstChild._primaryButton.click();
+
+  // check plugin state
+  pluginInfo = yield promiseForPluginInfo("test");
+  ok(pluginInfo.activated, "Test 1b, plugin should be activated");
+});
 
-    let condition = function() objLoadingContent.activated;
-    waitForCondition(condition, test1c, "Test 1b, Waited too long for plugin to activate");
-  });
-}
+// Test that the click-to-play notification doesn't break when navigating
+// to data URLs with multiple plugins.
+add_task(function* () {
+  // We click activated above
+  clearAllPluginPermissions();
 
-function test1c() {
-  clearAllPluginPermissions();
-  prepareTest(runAfterPluginBindingAttached(test2a), gHttpTestRoot + "plugin_data_url.html");
-}
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_data_url.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 2a, Should have a click-to-play notification");
 
-// Test that the click-to-play notification doesn't break when navigating to data URLs with multiple plugins
-function test2a() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "Test 2a, Should have a click-to-play notification");
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 2a, Plugin should not be activated");
+  let pluginInfo = yield promiseForPluginInfo("test");
+  ok(!pluginInfo.activated, "Test 2a, plugin should not be activated");
+
+  let loadPromise = promiseTabLoadEvent(gBrowser.selectedTab);
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    // navigate forward to a page with 'test1' & 'test2' in it
+    content.document.getElementById("data-link-2").click();
+  });
+  yield loadPromise;
 
-  gNextTest = runAfterPluginBindingAttached(test2b);
-  gTestBrowser.contentDocument.getElementById("data-link-2").click();
-}
+  // Work around for delayed PluginBindingAttached
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    content.document.getElementById("test1").clientTop;
+    content.document.getElementById("test2").clientTop;
+  });
 
-function test2b() {
-  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  pluginInfo = yield promiseForPluginInfo("test1");
+  ok(!pluginInfo.activated, "Test 2a, test1 should not be activated");
+  pluginInfo = yield promiseForPluginInfo("test2");
+  ok(!pluginInfo.activated, "Test 2a, test2 should not be activated");
+
+  notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(notification, "Test 2b, Should have a click-to-play notification");
 
+  yield promiseForNotificationShown(notification);
+
   // Simulate choosing "Allow now" for the test plugin
-  waitForNotificationShown(notification, function() {
-    is(notification.options.pluginData.size, 2, "Test 2b, Should have two types of plugin in the notification");
+  is(notification.options.pluginData.size, 2, "Test 2b, Should have two types of plugin in the notification");
 
-    var centerAction = null;
-    for (var action of notification.options.pluginData.values()) {
-      if (action.pluginName == "Test") {
-        centerAction = action;
-        break;
-      }
+  let centerAction = null;
+  for (let action of notification.options.pluginData.values()) {
+    if (action.pluginName == "Test") {
+      centerAction = action;
+      break;
     }
-    ok(centerAction, "Test 2b, found center action for the Test plugin");
+  }
+  ok(centerAction, "Test 2b, found center action for the Test plugin");
 
-    var centerItem = null;
-    for (var item of PopupNotifications.panel.firstChild.childNodes) {
-      is(item.value, "block", "Test 2b, all plugins should start out blocked");
-      if (item.action == centerAction) {
-        centerItem = item;
-        break;
-      }
+  let centerItem = null;
+  for (let item of PopupNotifications.panel.firstChild.childNodes) {
+    is(item.value, "block", "Test 2b, all plugins should start out blocked");
+    if (item.action == centerAction) {
+      centerItem = item;
+      break;
     }
-    ok(centerItem, "Test 2b, found center item for the Test plugin");
+  }
+  ok(centerItem, "Test 2b, found center item for the Test plugin");
 
-    // "click" the button to activate the Test plugin
-    centerItem.value = "allownow";
-    PopupNotifications.panel.firstChild._primaryButton.click();
+  // "click" the button to activate the Test plugin
+  centerItem.value = "allownow";
+  PopupNotifications.panel.firstChild._primaryButton.click();
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-    let plugin = gTestBrowser.contentDocument.getElementById("test1");
-    let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-    let condition = function() objLoadingContent.activated;
-    waitForCondition(condition, test2c, "Test 2b, Waited too long for plugin to activate");
-  });
-}
+  // check plugin state
+  pluginInfo = yield promiseForPluginInfo("test1");
+  ok(pluginInfo.activated, "Test 2b, plugin should be activated");
+});
 
-function test2c() {
-  let plugin = gTestBrowser.contentDocument.getElementById("test1");
-  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(objLoadingContent.activated, "Test 2c, Plugin should be activated");
+add_task(function* () {
+  // We click activated above
+  clearAllPluginPermissions();
 
-  clearAllPluginPermissions();
-  prepareTest(runAfterPluginBindingAttached(test3a), gHttpTestRoot + "plugin_data_url.html");
-}
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_data_url.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+});
 
 // Test that when navigating to a data url, the plugin permission is inherited
-function test3a() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "Test 3a, Should have a click-to-play notification");
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 3a, Plugin should not be activated");
+add_task(function* () {
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 3a, Should have a click-to-play notification");
+
+  // check plugin state
+  let pluginInfo = yield promiseForPluginInfo("test");
+  ok(!pluginInfo.activated, "Test 3a, plugin should not be activated");
+
+  let promise = promisePopupNotification("click-to-play-plugins");
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    let bounds = plugin.getBoundingClientRect();
+    let left = (bounds.left + bounds.right) / 2;
+    let top = (bounds.top + bounds.bottom) / 2;
+    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+  });
+  yield promise;
 
   // Simulate clicking the "Allow Always" button.
-  waitForNotificationShown(popupNotification, function() {
-    PopupNotifications.panel.firstChild._primaryButton.click();
+  let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
+    PopupNotifications.panel.firstChild;
+  yield promiseForCondition(condition);
+  PopupNotifications.panel.firstChild._primaryButton.click();
 
-    let condition = function() objLoadingContent.activated;
-    waitForCondition(condition, test3b, "Test 3a, Waited too long for plugin to activate");
-  });
-}
+  // check plugin state
+  pluginInfo = yield promiseForPluginInfo("test");
+  ok(pluginInfo.activated, "Test 3a, plugin should be activated");
 
-function test3b() {
-  gNextTest = test3c;
-  gTestBrowser.contentDocument.getElementById("data-link-1").click();
-}
+  let loadPromise = promiseTabLoadEvent(gBrowser.selectedTab);
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    // navigate forward to a page with 'test' in it
+    content.document.getElementById("data-link-1").click();
+  });
+  yield loadPromise;
 
-function test3c() {
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(objLoadingContent.activated, "Test 3c, Plugin should be activated");
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  // check plugin state
+  pluginInfo = yield promiseForPluginInfo("test");
+  ok(pluginInfo.activated, "Test 3b, plugin should be activated");
 
   clearAllPluginPermissions();
-  prepareTest(runAfterPluginBindingAttached(test4b),
-              'data:text/html,<embed id="test" style="width: 200px; height: 200px" type="application/x-test"/>');
-}
+});
+
+// Test that the click-to-play doorhanger still works
+// when directly navigating to data URLs.
+// Fails, bug XXX. Plugins plus a data url don't fire a load event.
+/*
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab,
+   "data:text/html,Hi!<embed id='test' style='width:200px; height:200px' type='application/x-test'/>");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 4a, Should have a click-to-play notification");
 
-// Test that the click-to-play doorhanger still works when directly navigating to data URLs
-function test4a() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "Test 4a, Should have a click-to-play notification");
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 4a, Plugin should not be activated");
+  // check plugin state
+  let pluginInfo = yield promiseForPluginInfo("test");
+  ok(!pluginInfo.activated, "Test 4a, plugin should not be activated");
+
+  let promise = promisePopupNotification("click-to-play-plugins");
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    let bounds = plugin.getBoundingClientRect();
+    let left = (bounds.left + bounds.right) / 2;
+    let top = (bounds.top + bounds.bottom) / 2;
+    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+  });
+  yield promise;
 
   // Simulate clicking the "Allow Always" button.
-  waitForNotificationShown(popupNotification, function() {
-    PopupNotifications.panel.firstChild._primaryButton.click();
+  let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
+    PopupNotifications.panel.firstChild;
+  yield promiseForCondition(condition);
+  PopupNotifications.panel.firstChild._primaryButton.click();
 
-    let condition = function() objLoadingContent.activated;
-    waitForCondition(condition, test4b, "Test 4a, Waited too long for plugin to activate");
-  });
-}
-
-function test4b() {
-  clearAllPluginPermissions();
-  finishTest();
-}
+  // check plugin state
+  pluginInfo = yield promiseForPluginInfo("test");
+  ok(pluginInfo.activated, "Test 4a, plugin should be activated");
+});
+*/
\ No newline at end of file
--- a/browser/base/content/test/plugins/browser_CTP_drag_drop.js
+++ b/browser/base/content/test/plugins/browser_CTP_drag_drop.js
@@ -1,102 +1,96 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-let gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-
-let gNextTest = null;
+let gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
 let gNewWindow = null;
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    gNewWindow.close();
+    gNewWindow = null;
+    window.focus();
+  });
+});
 
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
-    clearAllPluginPermissions();
-    Services.prefs.clearUserPref("plugins.click_to_play");
-  });
+add_task(function* () {
   Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
   gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("PluginBindingAttached", handleEvent, true, true);
-  gNextTest = part1;
-  gBrowser.selectedBrowser.contentDocument.location = gHttpTestRoot + "plugin_test.html";
-}
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+  yield promisePopupNotification("click-to-play-plugins");
+});
 
-function handleEvent() {
-  gNextTest();
-}
+add_task(function* () {
+  gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+
+  // XXX technically can't load fire before we get this call???
+  yield waitForEvent(gNewWindow, "load", null, true);
+
+  yield promisePopupNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser);
+
+  ok(PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser), "Should have a click-to-play notification in the tab in the new window");
+  ok(!PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should not have a click-to-play notification in the old window now");
+});
 
-function part1() {
-  gBrowser.selectedBrowser.removeEventListener("PluginBindingAttached", handleEvent);
-  waitForNotificationPopup("click-to-play-plugins", gBrowser.selectedBrowser, () => {
-    gNextTest = part2;
-    gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
-    gNewWindow.addEventListener("load", handleEvent, true);
-  });
-}
+add_task(function* () {
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, gNewWindow.gBrowser.selectedTab);
+
+  yield promisePopupNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+
+  ok(PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should have a click-to-play notification in the initial tab again");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+});
 
-function part2() {
-  gNewWindow.removeEventListener("load", handleEvent);
-  let condition = function() PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser);
-  waitForCondition(condition, part3, "Waited too long for click-to-play notification");
-}
+add_task(function* () {
+  yield promisePopupNotification("click-to-play-plugins");
+
+  gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
 
-function part3() {
+  yield promiseWaitForFocus(gNewWindow);
+
+  yield promisePopupNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser);
+});
+
+add_task(function* () {
   ok(PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser), "Should have a click-to-play notification in the tab in the new window");
   ok(!PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should not have a click-to-play notification in the old window now");
 
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, gNewWindow.gBrowser.selectedTab);
-  let condition = function() PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
-  waitForCondition(condition, part4, "Waited too long for click-to-play notification");
-}
-
-function part4() {
-  ok(PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should have a click-to-play notification in the initial tab again");
+  let pluginInfo = yield promiseForPluginInfo("test", gNewWindow.gBrowser.selectedBrowser);
+  ok(!pluginInfo.activated, "plugin should not be activated");
 
-  gBrowser.selectedBrowser.addEventListener("PluginBindingAttached", handleEvent, true, true);
-  gNextTest = part5;
-  gBrowser.selectedBrowser.contentDocument.location = gHttpTestRoot + "plugin_test.html";
-}
-
-function part5() {
-  gBrowser.selectedBrowser.removeEventListener("PluginBindingAttached", handleEvent);
-  waitForNotificationPopup("click-to-play-plugins", gBrowser.selectedBrowser, () => {
-    gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
-    waitForFocus(part6, gNewWindow);
+  yield ContentTask.spawn(gNewWindow.gBrowser.selectedBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let bounds = plugin.getBoundingClientRect();
+    let left = (bounds.left + bounds.right) / 2;
+    let top = (bounds.top + bounds.bottom) / 2;
+    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
   });
-}
-
-function part6() {
-  let condition = function() PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser);
-  waitForCondition(condition, part7, "Waited too long for click-to-play notification");
-}
-
-function part7() {
-  ok(PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser), "Should have a click-to-play notification in the tab in the new window");
-  ok(!PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should not have a click-to-play notification in the old window now");
 
-  let plugin = gNewWindow.gBrowser.selectedBrowser.contentDocument.getElementById("test");
-  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "plugin should not be activated");
+  let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser).dismissed && gNewWindow.PopupNotifications.panel.firstChild;
+  yield promiseForCondition(condition);
+});
 
-  EventUtils.synthesizeMouseAtCenter(plugin, {}, gNewWindow.gBrowser.selectedBrowser.contentWindow);
-  let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser).dismissed && gNewWindow.PopupNotifications.panel.firstChild;
-  waitForCondition(condition, part8, "waited too long for plugin to activate");
-}
-
-function part8() {
+add_task(function* () {
   // Click the activate button on doorhanger to make sure it works
   gNewWindow.PopupNotifications.panel.firstChild._primaryButton.click();
 
-  let plugin = gNewWindow.gBrowser.selectedBrowser.contentDocument.getElementById("test");
-  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  waitForCondition(() => objLoadingContent.activated, shutdown, "plugin should be activated now");
-}
-
-function shutdown() {
-  gNewWindow.close();
-  gNewWindow = null;
-  finish();
-}
+  let pluginInfo = yield promiseForPluginInfo("test", gNewWindow.gBrowser.selectedBrowser);
+  ok(pluginInfo.activated, "plugin should be activated");
+});
--- a/browser/base/content/test/plugins/browser_CTP_hide_overlay.js
+++ b/browser/base/content/test/plugins/browser_CTP_hide_overlay.js
@@ -1,76 +1,54 @@
-var rootDir = getRootDirectory(gTestPath);
-const gTestRoot = rootDir;
-const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-
-var gTestBrowser = null;
-var gNextTest = null;
-var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+let rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    gBrowser.removeCurrentTab();
+    window.focus();
+  });
+});
 
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
-    clearAllPluginPermissions();
-    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
-  });
+add_task(function* () {
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
-  let newTab = gBrowser.addTab();
-  gBrowser.selectedTab = newTab;
-  gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
+  gBrowser.selectedTab = gBrowser.addTab();
 
   Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_DISABLED);
 
-  prepareTest(runAfterPluginBindingAttached(test1), gHttpTestRoot + "plugin_test.html");
-}
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
 
-function finishTest() {
-  clearAllPluginPermissions();
-  gTestBrowser.removeEventListener("load", pageLoad, true);
-  gBrowser.removeCurrentTab();
-  window.focus();
-  finish();
-}
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
 
-function pageLoad() {
-  // The plugin events are async dispatched and can come after the load event
-  // This just allows the events to fire before we then go on to test the states
-  executeSoon(gNextTest);
-}
-
-function prepareTest(nextTest, url) {
-  gNextTest = nextTest;
-  gTestBrowser.contentWindow.location = url;
-}
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
 
-// Due to layout being async, "PluginBindAttached" may trigger later.
-// This wraps a function to force a layout flush, thus triggering it,
-// and schedules the function execution so they're definitely executed
-// afterwards.
-function runAfterPluginBindingAttached(func) {
-  return function() {
-    let doc = gTestBrowser.contentDocument;
-    let elems = doc.getElementsByTagName('embed');
-    if (elems.length < 1) {
-      elems = doc.getElementsByTagName('object');
-    }
-    elems[0].clientTop;
-    executeSoon(func);
-  };
-}
+  // Tests that the overlay can be hidded for disabled plugins using the close icon.
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    let closeIcon = doc.getAnonymousElementByAttribute(plugin, "anonid", "closeIcon")
+    let bounds = closeIcon.getBoundingClientRect();
+    let left = (bounds.left + bounds.right) / 2;
+    let top = (bounds.top + bounds.bottom) / 2;
+    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+  });
 
-// Tests that the overlay can be hidded for disabled plugins using the close icon.
-function test1() {
-  var doc = gTestBrowser.contentDocument;
-  var plugin = doc.getElementById("test");
-  ok(plugin, "Test 1, Found plugin in page");
-  var overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(overlay.classList.contains("visible"), "Test 1, Plugin overlay should exist, not be hidden");
-  var closeIcon = doc.getAnonymousElementByAttribute(plugin, "anonid", "closeIcon")
-  EventUtils.synthesizeMouseAtCenter(closeIcon, {}, gTestBrowser.contentWindow);
-  var condition = function() !overlay.classList.contains("visible");
-  waitForCondition(condition, finishTest, "Test 1, Waited too long for the overlay to become invisible.");
-}
+  let overlayIsVisible = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return plugin && overlay.classList.contains("visible");
+  });
+  ok(!overlayIsVisible, "overlay should be hidden.");
+});
--- a/browser/base/content/test/plugins/browser_CTP_iframe.js
+++ b/browser/base/content/test/plugins/browser_CTP_iframe.js
@@ -1,104 +1,54 @@
 let rootDir = getRootDirectory(gTestPath);
-const gTestRoot = rootDir;
-const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-
-let gTestBrowser = null;
-let gNextTest = null;
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    gBrowser.removeCurrentTab();
+    window.focus();
+  });
+});
 
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
-    clearAllPluginPermissions();
-    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
-  });
+add_task(function* () {
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
-  let newTab = gBrowser.addTab();
-  gBrowser.selectedTab = newTab;
-  gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
+  gBrowser.selectedTab = gBrowser.addTab();
 
   Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
-
-  prepareTest(delayTest(runAfterPluginBindingAttached(test1)), gHttpTestRoot + "plugin_iframe.html");
-}
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
 
-function finishTest() {
-  clearAllPluginPermissions();
-  gTestBrowser.removeEventListener("load", pageLoad, true);
-  gBrowser.removeCurrentTab();
-  window.focus();
-  finish();
-}
-
-function pageLoad() {
-  gNextTest();
-}
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_iframe.html");
 
-function prepareTest(nextTest, url) {
-  gNextTest = nextTest;
-  gTestBrowser.contentWindow.location = url;
-}
+  // Tests that the overlays are visible and actionable if the plugin is in an iframe.
 
-// Delay executing a test for one load event to wait for frame loads.
-function delayTest(nextTest) {
-  return () => {
-    gNextTest = nextTest;
-  }
-}
-
-// Due to layout being async, "PluginBindAttached" may trigger later.
-// This wraps a function to force a layout flush, thus triggering it,
-// and schedules the function execution so they're definitely executed
-// afterwards.
-function runAfterPluginBindingAttached(func) {
-  return () => {
-    let frame = gTestBrowser.contentDocument.getElementById("frame");
+  let result = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    let frame = content.document.getElementById("frame");
     let doc = frame.contentDocument;
-    let elems = doc.getElementsByTagName('embed');
-    if (elems.length < 1) {
-      elems = doc.getElementsByTagName('object');
-    }
-    elems[0].clientTop;
-    executeSoon(func);
-  };
-}
-
-// Tests that the overlays are visible and actionable if the plugin is in an iframe.
-function test1() {
-  let frame = gTestBrowser.contentDocument.getElementById("frame");
-  let doc = frame.contentDocument;
-  let plugin = doc.getElementById("test");
-  ok(plugin, "Test 1, Found plugin in page");
-
-  waitForNotificationPopup("click-to-play-plugins", gTestBrowser, () => {
+    let plugin = doc.getElementById("test");
     let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-    ok(overlay.classList.contains("visible"), "Test 1, Plugin overlay should exist, not be hidden");
-    let closeIcon = doc.getAnonymousElementByAttribute(plugin, "anonid", "closeIcon");
+    return plugin && overlay.classList.contains("visible");
+  });
+  ok(result, "Test 1, Plugin overlay should exist, not be hidden");
 
-    EventUtils.synthesizeMouseAtCenter(closeIcon, {}, frame.contentWindow);
-    let condition = () => !overlay.classList.contains("visible");
-    waitForCondition(condition, test2, "Test 1, Waited too long for the overlay to become invisible.");
+  result = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    let frame = content.document.getElementById("frame");
+    let doc = frame.contentDocument;
+    let plugin = doc.getElementById("test");
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    let closeIcon = doc.getAnonymousElementByAttribute(plugin, "anonid", "closeIcon");
+    let bounds = closeIcon.getBoundingClientRect();
+    let left = (bounds.left + bounds.right) / 2;
+    let top = (bounds.top + bounds.bottom) / 2;
+    let utils = doc.defaultView.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+    return overlay.classList.contains("visible");
   });
-}
-
-function test2() {
-  prepareTest(delayTest(runAfterPluginBindingAttached(test3)), gHttpTestRoot + "plugin_iframe.html");
-}
+  ok(!result, "Test 1, Plugin overlay should exist, be hidden");
+});
 
-function test3() {
-  let frame = gTestBrowser.contentDocument.getElementById("frame");
-  let doc = frame.contentDocument;
-  let plugin = doc.getElementById("test");
-  ok(plugin, "Test 3, Found plugin in page");
-
-  let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(overlay.classList.contains("visible"), "Test 3, Plugin overlay should exist, not be hidden");
-
-  EventUtils.synthesizeMouseAtCenter(plugin, {}, frame.contentWindow);
-  let condition = () => PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  waitForCondition(condition, finishTest, "Test 3, Waited too long for the doorhanger to pop up.");
-}
--- a/browser/base/content/test/plugins/browser_CTP_multi_allow.js
+++ b/browser/base/content/test/plugins/browser_CTP_multi_allow.js
@@ -1,138 +1,99 @@
-var rootDir = getRootDirectory(gTestPath);
-const gTestRoot = rootDir;
-const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-
-var gTestBrowser = null;
-var gNextTest = null;
-var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+let rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    gBrowser.removeCurrentTab();
+    window.focus();
+  });
+});
 
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
-    clearAllPluginPermissions();
-    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
-  });
+add_task(function* () {
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
-  var newTab = gBrowser.addTab();
-  gBrowser.selectedTab = newTab;
-  gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
+  gBrowser.selectedTab = gBrowser.addTab();
 
   Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
   setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
 
-  prepareTest(test1a, gHttpTestRoot + "plugin_two_types.html");
-}
-
-function finishTest() {
-  clearAllPluginPermissions();
-  gTestBrowser.removeEventListener("load", pageLoad, true);
-  gBrowser.removeCurrentTab();
-  window.focus();
-  finish();
-}
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html");
 
-function pageLoad() {
-  // The plugin events are async dispatched and can come after the load event
-  // This just allows the events to fire before we then go on to test the states
-  executeSoon(gNextTest);
-}
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
 
-function prepareTest(nextTest, url) {
-  gNextTest = nextTest;
-  gTestBrowser.contentWindow.location = url;
-}
+  // Test that the click-to-play doorhanger for multiple plugins shows the correct
+  // state when re-opening without reloads or navigation.
 
-// Due to layout being async, "PluginBindAttached" may trigger later.
-// This wraps a function to force a layout flush, thus triggering it,
-// and schedules the function execution so they're definitely executed
-// afterwards.
-function runAfterPluginBindingAttached(func) {
-  return function() {
-    let doc = gTestBrowser.contentDocument;
-    let elems = doc.getElementsByTagName('embed');
-    if (elems.length < 1) {
-      elems = doc.getElementsByTagName('object');
-    }
-    elems[0].clientTop;
-    executeSoon(func);
-  };
-}
+  let pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser);
+  ok(!pluginInfo.activated, "plugin should be activated");
 
-// Test that the click-to-play doorhanger for multiple plugins shows the correct
-// state when re-opening without reloads or navigation.
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+  ok(notification, "Test 1a, Should have a click-to-play notification");
 
-function test1a() {
-  let doc = gTestBrowser.contentDocument;
-  let plugin = doc.getElementById("test");
-  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test1a, Plugin should not be activated");
-
-  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(notification, "Test 1a, Should have a click-to-play notification");
-  notification.reshow();
+  yield promiseForNotificationShown(notification);
 
   is(notification.options.pluginData.size, 2,
-     "Test 1a, Should have two types of plugin in the notification");
+      "Test 1a, Should have two types of plugin in the notification");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+  is(PopupNotifications.panel.firstChild.childNodes.length, 2, "have child nodes");
 
   let pluginItem = null;
   for (let item of PopupNotifications.panel.firstChild.childNodes) {
     is(item.value, "block", "Test 1a, all plugins should start out blocked");
     if (item.action.pluginName == "Test") {
       pluginItem = item;
     }
   }
-
+  
   // Choose "Allow now" for the test plugin
   pluginItem.value = "allownow";
   PopupNotifications.panel.firstChild._primaryButton.click();
 
-  waitForCondition(() => objLoadingContent.activated, test1b,
-                   "Test 1a, Waited too long for plugin to activate");
-}
+  pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser);
+  ok(pluginInfo.activated, "plugin should be activated");
 
-function test1b() {
-  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  notification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
   ok(notification, "Test 1b, Should have a click-to-play notification");
-  notification.reshow();
 
-  let pluginItem = null;
+  yield promiseForNotificationShown(notification);
+
+  pluginItem = null;
   for (let item of PopupNotifications.panel.firstChild.childNodes) {
     if (item.action.pluginName == "Test") {
       is(item.value, "allownow", "Test 1b, Test plugin should now be set to 'Allow now'");
     } else {
       is(item.value, "block", "Test 1b, Second Test plugin should still be blocked");
       pluginItem = item;
     }
   }
 
   // Choose "Allow and remember" for the Second Test plugin
   pluginItem.value = "allowalways";
   PopupNotifications.panel.firstChild._primaryButton.click();
 
-  let doc = gTestBrowser.contentDocument;
-  let plugin = doc.getElementById("secondtestA");
-  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  waitForCondition(() => objLoadingContent.activated, test1c,
-                   "Test 1b, Waited too long for plugin to activate");
-}
+  pluginInfo = yield promiseForPluginInfo("secondtestA", gBrowser.selectedBrowser);
+  ok(pluginInfo.activated, "plugin should be activated");
 
-function test1c() {
-  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  notification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
   ok(notification, "Test 1c, Should have a click-to-play notification");
-  notification.reshow();
+
+  yield promiseForNotificationShown(notification);
 
   for (let item of PopupNotifications.panel.firstChild.childNodes) {
     if (item.action.pluginName == "Test") {
       is(item.value, "allownow", "Test 1c, Test plugin should be set to 'Allow now'");
     } else {
       is(item.value, "allowalways", "Test 1c, Second Test plugin should be set to 'Allow always'");
     }
   }
-
-  finishTest();
-}
+});
--- a/browser/base/content/test/plugins/browser_CTP_nonplugins.js
+++ b/browser/base/content/test/plugins/browser_CTP_nonplugins.js
@@ -1,114 +1,58 @@
-var rootDir = getRootDirectory(gTestPath);
-const gTestRoot = rootDir;
-const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-
-var gTestBrowser = null;
-var gNextTest = null;
-var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
-var gRunNextTestAfterPluginRemoved = false;
+let rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    gBrowser.removeCurrentTab();
+    window.focus();
+  });
+});
 
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
-    clearAllPluginPermissions();
-    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
-    gTestBrowser.removeEventListener("PluginRemoved", handlePluginRemoved, true, true);
-  });
+add_task(function* () {
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
-  var newTab = gBrowser.addTab();
-  gBrowser.selectedTab = newTab;
-  gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
-  gTestBrowser.addEventListener("PluginRemoved", handlePluginRemoved, true, true);
-
-  Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
-
-  prepareTest(runAfterPluginBindingAttached(test1), gHttpTestRoot + "plugin_two_types.html");
-}
+  gBrowser.selectedTab = gBrowser.addTab();
 
-function finishTest() {
-  clearAllPluginPermissions();
-  gTestBrowser.removeEventListener("load", pageLoad, true);
-  gBrowser.removeCurrentTab();
-  window.focus();
-  finish();
-}
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_DISABLED, "Test Plug-in");
 
-function pageLoad() {
-  // The plugin events are async dispatched and can come after the load event
-  // This just allows the events to fire before we then go on to test the states
-  executeSoon(gNextTest);
-}
-
-function prepareTest(nextTest, url) {
-  gNextTest = nextTest;
-  gTestBrowser.contentWindow.location = url;
-}
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html");
 
-// Due to layout being async, "PluginBindAttached" may trigger later.
-// This wraps a function to force a layout flush, thus triggering it,
-// and schedules the function execution so they're definitely executed
-// afterwards.
-function runAfterPluginBindingAttached(func) {
-  return function() {
-    let doc = gTestBrowser.contentDocument;
-    let elems = doc.getElementsByTagName('embed');
-    if (elems.length < 1) {
-      elems = doc.getElementsByTagName('object');
-    }
-    elems[0].clientTop;
-    executeSoon(func);
-  };
-}
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
 
-function handlePluginRemoved() {
-  if (gRunNextTestAfterPluginRemoved) {
-    executeSoon(gNextTest);
-    gRunNextTestAfterPluginRemoved = false;
-  }
-}
-
-function runAfterPluginRemoved(func) {
-  gNextTest = func;
-  gRunNextTestAfterPluginRemoved = true;
-}
-
-// Test that the click-to-play notification is not shown for non-plugin object elements
-
-function test1() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  // Test that the click-to-play notification is not shown for non-plugin object elements
+  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
   ok(popupNotification, "Test 1, Should have a click-to-play notification");
 
-  let plugin = gTestBrowser.contentDocument.getElementById("secondtestA");
-  plugin.parentNode.removeChild(plugin);
-  plugin = gTestBrowser.contentDocument.getElementById("secondtestB");
-  plugin.parentNode.removeChild(plugin);
+  let pluginRemovedPromise = waitForEvent(gBrowser.selectedBrowser, "PluginRemoved", null, true, true);
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    let plugin = content.document.getElementById("secondtestA");
+    plugin.parentNode.removeChild(plugin);
+    plugin = content.document.getElementById("secondtestB");
+    plugin.parentNode.removeChild(plugin);
 
-  let image = gTestBrowser.contentDocument.createElement("object");
-  image.type = "image/png";
-  image.data = "moz.png";
-  gTestBrowser.contentDocument.body.appendChild(image);
+    let image = content.document.createElement("object");
+    image.type = "image/png";
+    image.data = "moz.png";
+    content.document.body.appendChild(image);
+  });
+  yield pluginRemovedPromise;
 
-  runAfterPluginRemoved(test2);
-}
-
-function test2() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
   ok(popupNotification, "Test 2, Should have a click-to-play notification");
 
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  plugin.parentNode.removeChild(plugin);
-
-  executeSoon(test3);
-}
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    plugin.parentNode.removeChild(plugin);
+  });
 
-function test3() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
   ok(popupNotification, "Test 3, Should still have a click-to-play notification");
-
-  finishTest();
-}
+});
--- a/browser/base/content/test/plugins/browser_CTP_notificationBar.js
+++ b/browser/base/content/test/plugins/browser_CTP_notificationBar.js
@@ -1,171 +1,154 @@
-var rootDir = getRootDirectory(gTestPath);
-const gTestRoot = rootDir;
-const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-
-var gTestBrowser = null;
-var gNextTest = null;
+let rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gTestBrowser = null;
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    gBrowser.removeCurrentTab();
+    window.focus();
+    gTestBrowser = null;
+  });
 
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
-    clearAllPluginPermissions();
-    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
-  });
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
-  var newTab = gBrowser.addTab();
+  let newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
   gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
+});
 
+add_task(function* () {
   Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
 
-  prepareTest(runAfterPluginBindingAttached(test1), gHttpTestRoot + "plugin_small.html");
-}
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_small.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-function finishTest() {
-  gTestBrowser.removeEventListener("load", pageLoad, true);
-  gBrowser.removeCurrentTab();
-  window.focus();
-  finish();
-}
+  yield promisePopupNotification("click-to-play-plugins");
 
-function pageLoad() {
-  // The plugin events are async dispatched and can come after the load event
-  // This just allows the events to fire before we then go on to test the states
-  executeSoon(gNextTest);
-}
+  // Expecting a notification bar for hidden plugins
+  yield promiseForNotificationBar("plugin-hidden", gTestBrowser);
+});
 
-function prepareTest(nextTest, url) {
-  gNextTest = nextTest;
-  gTestBrowser.contentWindow.location = url;
-}
+add_task(function* () {
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_small.html");
 
-// Due to layout being async, "PluginBindAttached" may trigger later.
-// This wraps a function to force a layout flush, thus triggering it,
-// and schedules the function execution so they're definitely executed
-// afterwards.
-function runAfterPluginBindingAttached(func) {
-  return function() {
-    let doc = gTestBrowser.contentDocument;
-    let elems = doc.getElementsByTagName('embed');
-    if (elems.length < 1) {
-      elems = doc.getElementsByTagName('object');
-    }
-    elems[0].clientTop;
-    executeSoon(func);
-  };
-}
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
+  yield promiseForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null);
+});
+
+add_task(function* () {
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_overlayed.html");
 
-// Tests for the notification bar for hidden plugins.
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-function test1() {
-  info("Test 1 - expecting a notification bar for hidden plugins.");
-  waitForNotificationPopup("click-to-play-plugins", gTestBrowser, () => {
-    waitForNotificationBar("plugin-hidden", gTestBrowser, () => {
-      // Don't use setTestPluginEnabledState here because we already saved the
-      // prior value
-      getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
-      prepareTest(test2, gTestRoot + "plugin_small.html");
-    });
-  });
-}
+  // Expecting a plugin notification bar when plugins are overlaid.
+  yield promiseForNotificationBar("plugin-hidden", gTestBrowser);
+});
+
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_overlayed.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-function test2() {
-  info("Test 2 - expecting no plugin notification bar on visible plugins.");
-  waitForNotificationPopup("click-to-play-plugins", gTestBrowser, () => {
-    let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
-
-    waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null,
-      () => {
-        getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
-        prepareTest(test3, gTestRoot + "plugin_overlayed.html");
-      },
-      "expected to not have a plugin notification bar"
-    );
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    return plugin.pluginFallbackType;
   });
-}
-
-function test3() {
-  info("Test 3 - expecting a plugin notification bar when plugins are overlaid");
-  waitForNotificationPopup("click-to-play-plugins", gTestBrowser, () => {
-    waitForNotificationBar("plugin-hidden", gTestBrowser, test3b);
-  });
-}
-
-function test3b()
-{
-  let doc = gTestBrowser.contentDocument;
-  let plugin = doc.getElementById("test");
-  ok(plugin, "Test 3b, Found plugin in page");
-  plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+  is(result, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
      "Test 3b, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
-  ok(!plugin.activated, "Test 3b, Plugin should not be activated");
-  let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(!overlay.classList.contains("visible"), "Test 3b, Plugin overlay should be hidden");
 
-  prepareTest(test4, gTestRoot + "plugin_positioned.html");
-}
+  let pluginInfo = yield promiseForPluginInfo("test");
+  ok(!pluginInfo.activated, "Test 1a, plugin should not be activated");
 
-function test4() {
-  info("Test 4 - expecting a plugin notification bar when plugins are overlaid offscreen")
-  waitForNotificationPopup("click-to-play-plugins", gTestBrowser, () => {
-    waitForNotificationBar("plugin-hidden", gTestBrowser, test4b);
+  result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return overlay && overlay.classList.contains("visible");
   });
-}
+  ok(!result, "Test 3b, overlay should be hidden.");
+});
+
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_positioned.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-function test4b() {
-  let doc = gTestBrowser.contentDocument;
-  let plugin = doc.getElementById("test");
-  ok(plugin, "Test 4b, Found plugin in page");
-  plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+  // Expecting a plugin notification bar when plugins are overlaid offscreen.
+  yield promisePopupNotification("click-to-play-plugins");
+  yield promiseForNotificationBar("plugin-hidden", gTestBrowser);
+
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    return plugin.pluginFallbackType;
+  });
+  is(result, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
      "Test 4b, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
-  ok(!plugin.activated, "Test 4b, Plugin should not be activated");
-  let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(!overlay.classList.contains("visible"), "Test 4b, Plugin overlay should be hidden");
 
-  prepareTest(runAfterPluginBindingAttached(test5), gHttpTestRoot + "plugin_small.html");
-}
-
-function test5() {
-  let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
-  waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") !== null,
-    test6,
-    "Test 5, expected a notification bar for hidden plugins");
-}
+  result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return overlay && overlay.classList.contains("visible");
+  });
+  ok(!result, "Test 4b, overlay should be hidden.");
+});
 
 // Test that the notification bar is getting dismissed when directly activating plugins
 // via the doorhanger.
 
-function test6() {
-  info("Test 6 - expecting the doorhanger to be dismissed when directly activating plugins.");
-  waitForNotificationPopup("click-to-play-plugins", gTestBrowser, (notification) => {
-    let plugin = gTestBrowser.contentDocument.getElementById("test");
-    ok(plugin, "Test 6, Found plugin in page");
-    let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-    is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
-       "Test 6, Plugin should be click-to-play");
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_small.html");
 
-    // simulate "always allow"
-    notification.reshow();
-    PopupNotifications.panel.firstChild._primaryButton.click();
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  // Expecting a plugin notification bar when plugins are overlaid offscreen.
+  yield promisePopupNotification("click-to-play-plugins");
 
-    let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
-    waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null,
-      test7,
-      "Test 6, expected the notification bar for hidden plugins to get dismissed");
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    return plugin.pluginFallbackType;
   });
-}
+  is(result, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+     "Test 6, Plugin should be click-to-play");
+
+  yield promisePopupNotification("click-to-play-plugins");
 
-function test7() {
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  ok(plugin, "Test 7, Found plugin in page");
-  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  waitForCondition(() => objLoadingContent.activated, finishTest,
-    "Test 7, Waited too long for plugin to activate");
-}
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 6, Should have a click-to-play notification");
+
+  // simulate "always allow"
+  yield promiseForNotificationShown(notification);
+
+  PopupNotifications.panel.firstChild._primaryButton.click();
+
+  let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
+  yield promiseForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null);
+
+  let pluginInfo = yield promiseForPluginInfo("test");
+  ok(pluginInfo.activated, "Test 7, plugin should be activated");
+});
--- a/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js
+++ b/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js
@@ -1,125 +1,120 @@
-var rootDir = getRootDirectory(gTestPath);
-const gTestRoot = rootDir;
-const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-
-var gTestBrowser = null;
-var gNextTest = null;
-var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
-var gRunNextTestAfterPluginRemoved = false;
+let rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gTestBrowser = null;
+let gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    gBrowser.removeCurrentTab();
+    window.focus();
+    gTestBrowser = null;
+  });
+});
 
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
-    clearAllPluginPermissions();
-    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
-  });
+add_task(function* () {
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
-  var newTab = gBrowser.addTab();
+  let newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
   gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
 
-  Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
-
-  prepareTest(test1, gHttpTestRoot + "plugin_outsideScrollArea.html");
-}
-
-function finishTest() {
-  clearAllPluginPermissions();
-  gTestBrowser.removeEventListener("load", pageLoad, true);
-  gBrowser.removeCurrentTab();
-  window.focus();
-  finish();
-}
-
-function pageLoad() {
-  // The plugin events are async dispatched and can come after the load event
-  // This just allows the events to fire before we then go on to test the states
-  executeSoon(gNextTest);
-}
-
-function prepareTest(nextTest, url) {
-  gNextTest = nextTest;
-  gTestBrowser.contentWindow.location = url;
-}
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
 
-// Due to layout being async, "PluginBindAttached" may trigger later.
-// This wraps a function to force a layout flush, thus triggering it,
-// and schedules the function execution so they're definitely executed
-// afterwards.
-function runAfterPluginBindingAttached(func) {
-  return function() {
-    let doc = gTestBrowser.contentDocument;
-    let elems = doc.getElementsByTagName('embed');
-    if (elems.length < 1) {
-      elems = doc.getElementsByTagName('object');
-    }
-    elems[0].clientTop;
-    executeSoon(func);
-  };
-}
-
-// Add plugin relative to bottom-left corner of #container.
-function addPlugin(x, y) {
-  let doc = gTestBrowser.contentDocument;
-  let p = doc.createElement('embed');
-
-  p.setAttribute('id', 'test');
-  p.setAttribute('type', 'application/x-test');
-  p.style.left = x.toString() + 'px';
-  p.style.bottom = y.toString() + 'px';
-
-  doc.getElementById('container').appendChild(p);
-}
+  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(!popupNotification, "Test 1, Should not have a click-to-play notification");
+});
 
 // Test that the click-to-play overlay is not hidden for elements
 // partially or fully outside the viewport.
 
-function test1() {
-  addPlugin(0, -200);
-  executeSoon(runAfterPluginBindingAttached(test2));
-}
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_outsideScrollArea.html");
+
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let p = doc.createElement('embed');
+
+    p.setAttribute('id', 'test');
+    p.setAttribute('type', 'application/x-test');
+    p.style.left = "0";
+    p.style.bottom = "200px";
+
+    doc.getElementById('container').appendChild(p);
+  });
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  yield promisePopupNotification("click-to-play-plugins");
 
-function test2() {
-  let doc = gTestBrowser.contentDocument;
-  let plugin = doc.getElementById("test");
-  let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(overlay, "Test 2, Should have an overlay.");
-  ok(overlay.classList.contains("visible"), "Test 2, Overlay should be visible");
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    let doc = content.document;
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return overlay && overlay.classList.contains("visible");
+  });
+  ok(result, "Test 2, overlay should be visible.");
+});
 
-  prepareTest(test3, gHttpTestRoot + "plugin_outsideScrollArea.html");
-}
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_outsideScrollArea.html");
 
-function test3() {
-  addPlugin(0, -410);
-  executeSoon(runAfterPluginBindingAttached(test4));
-}
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let p = doc.createElement('embed');
+
+    p.setAttribute('id', 'test');
+    p.setAttribute('type', 'application/x-test');
+    p.style.left = "0";
+    p.style.bottom = "-410px";
 
-function test4() {
-  let doc = gTestBrowser.contentDocument;
-  let plugin = doc.getElementById("test");
-  let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(overlay, "Test 4, Should have an overlay.");
-  ok(overlay.classList.contains("visible"), "Test 4, Overlay should be visible");
+    doc.getElementById('container').appendChild(p);
+  });
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  yield promisePopupNotification("click-to-play-plugins");
 
-  prepareTest(test5, gHttpTestRoot + "plugin_outsideScrollArea.html");
-}
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    let doc = content.document;
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return overlay && overlay.classList.contains("visible");
+  });
+  ok(result, "Test 3, overlay should be visible.");
+});
+
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_outsideScrollArea.html");
 
-function test5() {
-  addPlugin(-600, 0);
-  executeSoon(runAfterPluginBindingAttached(test6));
-}
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let p = doc.createElement('embed');
+
+    p.setAttribute('id', 'test');
+    p.setAttribute('type', 'application/x-test');
+    p.style.left = "-600px";
+    p.style.bottom = "0";
+
+    doc.getElementById('container').appendChild(p);
+  });
 
-function test6() {
-  let doc = gTestBrowser.contentDocument;
-  let plugin = doc.getElementById("test");
-  let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(overlay, "Test 6, Should have an overlay.");
-  ok(!overlay.classList.contains("visible"), "Test 6, Overlay should be hidden");
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-  finishTest();
-}
+  yield promisePopupNotification("click-to-play-plugins");
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    let doc = content.document;
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return overlay && overlay.classList.contains("visible");
+  });
+  ok(!result, "Test 4, overlay should be hidden.");
+});
--- a/browser/base/content/test/plugins/browser_CTP_remove_navigate.js
+++ b/browser/base/content/test/plugins/browser_CTP_remove_navigate.js
@@ -1,82 +1,79 @@
-/* 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 gTestRoot = getRootDirectory(gTestPath);
 const gHttpTestRoot = gTestRoot.replace("chrome://mochitests/content/",
                                         "http://127.0.0.1:8888/");
 
 add_task(function* () {
-  Services.prefs.setBoolPref("plugins.click_to_play", true);
-  registerCleanupFunction(() => {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
     Services.prefs.clearUserPref("plugins.click_to_play");
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    gBrowser.removeCurrentTab();
+    window.focus();
   });
-})
+
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+});
 
 /**
  * Tests that if a plugin is removed just as we transition to
  * a different page, that we don't show the hidden plugin
  * notification bar on the new page.
  */
 add_task(function* () {
-  let newTab = gBrowser.addTab();
-  gBrowser.selectedTab = newTab;
-  let browser = gBrowser.selectedBrowser;
-
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+  gBrowser.selectedTab = gBrowser.addTab();
 
   // Load up a page with a plugin...
-  let notificationPromise =
-    waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser);
-  yield loadPage(browser, gHttpTestRoot + "plugin_small.html")
-  yield forcePluginBindingAttached(browser);
+  let notificationPromise = waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser);
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gHttpTestRoot + "plugin_small.html");
+  yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
   yield notificationPromise;
 
   // Trigger the PluginRemoved event to be fired, and then immediately
   // browse to a new page.
-  let plugin = browser.contentDocument.getElementById("test");
-  plugin.remove();
-  yield loadPage(browser, "about:mozilla");
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    plugin.remove();
+  });
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, "about:mozilla");
 
   // There should be no hidden plugin notification bar at about:mozilla.
-  let notificationBox = gBrowser.getNotificationBox(browser);
+  let notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
   is(notificationBox.getNotificationWithValue("plugin-hidden"), null,
      "Expected no notification box");
-  gBrowser.removeTab(newTab);
 });
 
 /**
  * Tests that if a plugin is removed just as we transition to
  * a different page with a plugin, that we show the right notification
  * for the new page.
  */
 add_task(function* () {
-  let newTab = gBrowser.addTab();
-  gBrowser.selectedTab = newTab;
-  let browser = gBrowser.selectedBrowser;
-
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY,
-                            "Second Test Plug-in");
-
   // Load up a page with a plugin...
-  let notificationPromise =
-    waitForNotificationBar("plugin-hidden", browser);
-  yield loadPage(browser, gHttpTestRoot + "plugin_small.html")
-  yield forcePluginBindingAttached(browser);
+  let notificationPromise = waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser);
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gHttpTestRoot + "plugin_small.html");
+  yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
   yield notificationPromise;
 
   // Trigger the PluginRemoved event to be fired, and then immediately
   // browse to a new page.
-  let plugin = browser.contentDocument.getElementById("test");
-  plugin.remove();
-  yield loadPage(browser, gTestRoot + "plugin_small_2.html");
-  let notification = yield waitForNotificationBar("plugin-hidden", browser);
+  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    plugin.remove();
+  });
+});
+
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gHttpTestRoot + "plugin_small_2.html");
+  let notification = yield waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser);
   ok(notification, "There should be a notification shown for the new page.");
-
   // Ensure that the notification is showing information about
   // the x-second-test plugin.
-  ok(notification.label.includes("Second Test"), "Should mention the second plugin");
-  ok(!notification.label.includes("127.0.0.1"), "Should not refer to old principal");
-  ok(notification.label.includes("null"), "Should refer to the new principal");
-  gBrowser.removeTab(newTab);
+  let label = notification.label;
+  ok(label.includes("Second Test"), "Should mention the second plugin");
 });
--- a/browser/base/content/test/plugins/browser_CTP_resize.js
+++ b/browser/base/content/test/plugins/browser_CTP_resize.js
@@ -1,116 +1,130 @@
-var rootDir = getRootDirectory(gTestPath);
-const gTestRoot = rootDir;
-const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-
-var gTestBrowser = null;
-var gNextTest = null;
-var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+let rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gTestBrowser = null;
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    gBrowser.removeCurrentTab();
+    window.focus();
+    gTestBrowser = null;
+  });
+});
 
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
-    clearAllPluginPermissions();
-    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
-  });
+add_task(function* () {
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
-  var newTab = gBrowser.addTab();
+  let newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
   gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
 
-  Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
-
-  prepareTest(runAfterPluginBindingAttached(test1), gHttpTestRoot + "plugin_small.html");
-}
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
 
-function finishTest() {
-  clearAllPluginPermissions();
-  gTestBrowser.removeEventListener("load", pageLoad, true);
-  gBrowser.removeCurrentTab();
-  window.focus();
-  finish();
-}
+  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(!popupNotification, "Test 1, Should not have a click-to-play notification");
 
-function pageLoad() {
-  // The plugin events are async dispatched and can come after the load event
-  // This just allows the events to fire before we then go on to test the states
-  executeSoon(gNextTest);
-}
-
-function prepareTest(nextTest, url) {
-  gNextTest = nextTest;
-  gTestBrowser.contentWindow.location = url;
-}
+  yield promiseTabLoadEvent(newTab, gTestRoot + "plugin_small.html"); // 10x10 plugin
 
-// Due to layout being async, "PluginBindAttached" may trigger later.
-// This wraps a function to force a layout flush, thus triggering it,
-// and schedules the function execution so they're definitely executed
-// afterwards.
-function runAfterPluginBindingAttached(func) {
-  return function() {
-    let doc = gTestBrowser.contentDocument;
-    let elems = doc.getElementsByTagName('embed');
-    if (elems.length < 1) {
-      elems = doc.getElementsByTagName('object');
-    }
-    elems[0].clientTop;
-    executeSoon(func);
-  };
-}
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  yield promisePopupNotification("click-to-play-plugins");
+});
 
 // Test that the overlay is hidden for "small" plugin elements and is shown
 // once they are resized to a size that can hold the overlay
-
-function test1() {
+add_task(function* () {
   let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "Test 1, Should have a click-to-play notification");
+  ok(popupNotification, "Test 2, Should have a click-to-play notification");
 
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  let doc = gTestBrowser.contentDocument;
-  let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(overlay, "Test 1, Should have an overlay.");
-  ok(!overlay.classList.contains("visible"), "Test 1, Overlay should be hidden");
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return overlay && overlay.classList.contains("visible");
+  });
+  ok(!result, "Test 2, overlay should be hidden.");
+});
+
+add_task(function* () {
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    plugin.style.width = "300px";
+  });
 
-  plugin.style.width = '300px';
-  executeSoon(test2);
-}
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-function test2() {
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  let doc = gTestBrowser.contentDocument;
-  let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(overlay, "Test 2, Should have an overlay.");
-  ok(!overlay.classList.contains("visible"), "Test 2, Overlay should be hidden");
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return overlay && overlay.classList.contains("visible");
+  });
+  ok(!result, "Test 3, overlay should be hidden.");
+});
+
+
+add_task(function* () {
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    plugin.style.height = "300px";
+  });
+
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    content.document.getElementById("test").clientTop;
+  });
 
-  plugin.style.height = '300px';
-  let condition = () => overlay.classList.contains("visible");
-  waitForCondition(condition, test3, "Test 2, Waited too long for overlay to become visible");
-}
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return overlay && overlay.classList.contains("visible");
+  });
+  ok(result, "Test 4, overlay should be visible.");
+});
 
-function test3() {
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  let doc = gTestBrowser.contentDocument;
-  let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(overlay, "Test 3, Should have an overlay.");
-  ok(overlay.classList.contains("visible"), "Test 3, Overlay should be visible");
+add_task(function* () {
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    plugin.style.width = "10px";
+    plugin.style.height = "10px";
+  });
+
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    content.document.getElementById("test").clientTop;
+  });
 
-  plugin.style.width = '10px';
-  plugin.style.height = '10px';
-  let condition = () => !overlay.classList.contains("visible");
-  waitForCondition(condition, test4, "Test 3, Waited too long for overlay to become hidden");
-}
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return overlay && overlay.classList.contains("visible");
+  });
+  ok(!result, "Test 5, overlay should be hidden.");
+});
 
-function test4() {
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  let doc = gTestBrowser.contentDocument;
-  let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(overlay, "Test 4, Should have an overlay.");
-  ok(!overlay.classList.contains("visible"), "Test 4, Overlay should be hidden");
+add_task(function* () {
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    plugin.style.height = "300px";
+    plugin.style.width = "300px";
+  });
 
-  clearAllPluginPermissions();
-  finishTest();
-}
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    content.document.getElementById("test").clientTop;
+  });
+
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return overlay && overlay.classList.contains("visible");
+  });
+  ok(result, "Test 6, overlay should be visible.");
+});
--- a/browser/base/content/test/plugins/browser_CTP_zoom.js
+++ b/browser/base/content/test/plugins/browser_CTP_zoom.js
@@ -1,77 +1,62 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
 "use strict";
 
 let rootDir = getRootDirectory(gTestPath);
-const gTestRoot = rootDir;
-const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/",
-                                      "http://127.0.0.1:8888/");
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
 
 let gTestBrowser = null;
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    FullZoom.reset(); // must be called before closing the tab we zoomed!
+    gBrowser.removeCurrentTab();
+    window.focus();
+    gTestBrowser = null;
+  });
+});
 
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(() => {
-    FullZoom.reset();
-    clearAllPluginPermissions();
-    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
-  });
+add_task(function* () {
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
-  Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
-
-  let newTab = gBrowser.addTab();
-  gBrowser.selectedTab = newTab;
+  gBrowser.selectedTab = gBrowser.addTab();
   gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
-  gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_zoom.html"
-}
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(!popupNotification, "Test 1, Should not have a click-to-play notification");
 
-function finishTest() {
-  clearAllPluginPermissions();
-  gTestBrowser.removeEventListener("load", pageLoad, true);
-  gBrowser.removeCurrentTab();
-  window.focus();
-  finish();
-}
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_zoom.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  yield promisePopupNotification("click-to-play-plugins");
+});
 
-function pageLoad() {
-  // Due to layout being async, "PluginBindAttached" may trigger later.
-  // This wraps a function to force a layout flush, thus triggering it,
-  // and schedules the function execution so they're definitely executed
-  // afterwards.
-  let doc = gTestBrowser.contentDocument;
-  let elems = doc.getElementsByTagName('embed');
-  if (elems.length < 1) {
-    elems = doc.getElementsByTagName('object');
-  }
-  elems[0].clientTop;
-  executeSoon(testOverlay);
-}
-
-let enlargeCount = 4;
-
-// Enlarges the zoom level |enlargeCount| times and tests that the overlay is
+// Enlarges the zoom level 4 times and tests that the overlay is
 // visible after each enlargement.
-function testOverlay() {
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  let doc = gTestBrowser.contentDocument;
-  let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(overlay, "Overlay should exist");
-  ok(overlay.classList.contains("visible"), "Overlay should be visible");
+add_task(function* () {
+  for (let count = 0; count < 4; count++) {
+
+    FullZoom.enlarge();
 
-  if (enlargeCount > 0) {
-    --enlargeCount;
-    FullZoom.enlarge();
-    gTestBrowser.contentWindow.location.reload();
-  } else {
-    FullZoom.reset();
-    clearAllPluginPermissions();
-    finishTest();
+    // Reload the page
+    yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_zoom.html");
+    yield promiseUpdatePluginBindings(gTestBrowser);
+    let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+      let doc = content.document;
+      let plugin = doc.getElementById("test");
+      let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+      return overlay && overlay.classList.contains("visible");
+    });
+    ok(result, "Overlay should be visible for zoom change count " + count);
   }
-}
+});
+
+
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_blocking.js
@@ -0,0 +1,360 @@
+let gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gTestBrowser = null;
+let gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+function updateAllTestPlugins(aState) {
+  setTestPluginEnabledState(aState, "Test Plug-in");
+  setTestPluginEnabledState(aState, "Second Test Plug-in");
+}
+
+add_task(function* () {
+  registerCleanupFunction(Task.async(function*() {
+    clearAllPluginPermissions();
+    updateAllTestPlugins(Ci.nsIPluginTag.STATE_ENABLED);
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+    resetBlocklist();
+    gBrowser.removeCurrentTab();
+    window.focus();
+    gTestBrowser = null;
+  }));
+});
+
+add_task(function* () {
+  gBrowser.selectedTab = gBrowser.addTab();
+  gTestBrowser = gBrowser.selectedBrowser;
+
+  updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+  // Prime the content process
+  yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>");
+
+  // Make sure the blocklist service(s) are running
+  Components.classes["@mozilla.org/extensions/blocklist;1"]
+            .getService(Components.interfaces.nsIBlocklistService);
+  let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+  ok(!exmsg, "exception: " + exmsg);
+});
+
+add_task(function* () {
+  // enable hard blocklisting for the next test
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginHard.xml", gTestBrowser);
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  yield promisePopupNotification("click-to-play-plugins");
+
+  let notification = PopupNotifications.getNotification("click-to-play-plugins");
+  ok(notification.dismissed, "Test 5: The plugin notification should be dismissed by default");
+
+  yield promiseForNotificationShown(notification);
+
+  let pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED, "Test 5, plugin fallback type should be PLUGIN_BLOCKLISTED");
+
+  is(notification.options.pluginData.size, 1, "Test 5: Only the blocked plugin should be present in the notification");
+  ok(PopupNotifications.panel.firstChild._buttonContainer.hidden, "Part 5: The blocked plugins notification should not have any buttons visible.");
+
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
+
+// Tests a vulnerable, updatable plugin
+
+add_task(function* () {
+  // enable hard blocklisting of test
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableUpdatable.xml", gTestBrowser);
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  yield promisePopupNotification("click-to-play-plugins");
+
+  let pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE,
+     "Test 18a, plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE");
+  ok(!pluginInfo.activated, "Test 18a, Plugin should not be activated");
+
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    let doc = content.document;
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return overlay && overlay.classList.contains("visible");
+  });
+  ok(result, "Test 18a, Plugin overlay should exist, not be hidden");
+
+  result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink");
+    return updateLink.style.visibility != "hidden";
+  });
+  ok(result, "Test 18a, Plugin should have an update link");
+
+  let promise = waitForEvent(gBrowser.tabContainer, "TabOpen", null, true);
+  let pluginUpdateURL = Services.urlFormatter.formatURLPref("plugins.update.url");
+  info(pluginUpdateURL);
+
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink");
+    let bounds = updateLink.getBoundingClientRect();
+    let left = (bounds.left + bounds.right) / 2;
+    let top = (bounds.top + bounds.bottom) / 2;
+    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+  });
+  yield promise;
+
+  promise = waitForEvent(gBrowser.tabContainer, "TabClose", null, true);
+  gBrowser.removeCurrentTab();
+  yield promise;
+});
+
+add_task(function* () {
+  // clicking the update link should not activate the plugin
+  let pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE,
+     "Test 18a, plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE");
+  ok(!pluginInfo.activated, "Test 18b, Plugin should not be activated");
+
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return overlay && overlay.classList.contains("visible");
+  });
+  ok(result, "Test 18b, Plugin overlay should exist, not be hidden");
+});
+
+// Tests a vulnerable plugin with no update
+add_task(function* () {
+  updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableNoUpdate.xml", gTestBrowser);
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 18c, Should have a click-to-play notification");
+
+  let pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE,
+     "Test 18c, plugin fallback type should be PLUGIN_VULNERABLE_NO_UPDATE");
+  ok(!pluginInfo.activated, "Test 18c, Plugin should not be activated");
+
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return overlay && overlay.classList.contains("visible");
+  });
+  ok(result, "Test 18c, Plugin overlay should exist, not be hidden");
+
+  result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink");
+    return updateLink && updateLink.style.display != "block";
+  });
+  ok(result, "Test 18c, Plugin should not have an update link");
+
+  // check that click "Always allow" works with blocked plugins
+  yield promiseForNotificationShown(notification);
+
+  PopupNotifications.panel.firstChild._primaryButton.click();
+
+  pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE,
+     "Test 18c, plugin fallback type should be PLUGIN_VULNERABLE_NO_UPDATE");
+  ok(pluginInfo.activated, "Test 18c, Plugin should be activated");
+  let enabledState = getTestPluginEnabledState();
+  ok(enabledState, "Test 18c, Plugin enabled state should be STATE_CLICKTOPLAY");
+});
+
+// continue testing "Always allow", make sure it sticks.
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let pluginInfo = yield promiseForPluginInfo("test");
+  ok(pluginInfo.activated, "Test 18d, Waited too long for plugin to activate");
+
+  clearAllPluginPermissions();
+});
+
+// clicking the in-content overlay of a vulnerable plugin should bring
+// up the notification and not directly activate the plugin
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 18f, Should have a click-to-play notification");
+  ok(notification.dismissed, "Test 18f, notification should start dismissed");
+
+  let pluginInfo = yield promiseForPluginInfo("test");
+  ok(!pluginInfo.activated, "Test 18f, Waited too long for plugin to activate");
+
+  var oldEventCallback = notification.options.eventCallback;
+  let promise = promiseForCondition(() => oldEventCallback == null);
+  notification.options.eventCallback = function() {
+    if (oldEventCallback) {
+      oldEventCallback();
+    }
+    oldEventCallback = null;
+  };
+
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let bounds = plugin.getBoundingClientRect();
+    let left = (bounds.left + bounds.right) / 2;
+    let top = (bounds.top + bounds.bottom) / 2;
+    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+  });
+  yield promise;
+
+  ok(notification, "Test 18g, Should have a click-to-play notification");
+  ok(!notification.dismissed, "Test 18g, notification should be open");
+
+  pluginInfo = yield promiseForPluginInfo("test");
+  ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated");
+});
+
+// Test that "always allow"-ing a plugin will not allow it when it becomes
+// blocklisted.
+add_task(function* () {
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+
+  updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 24a, Should have a click-to-play notification");
+
+  // Plugin should start as CTP
+  let pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+     "Test 24a, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+  ok(!pluginInfo.activated, "Test 24a, Plugin should not be active.");
+
+  // simulate "always allow"
+  yield promiseForNotificationShown(notification);
+
+  PopupNotifications.panel.firstChild._primaryButton.click();
+
+  pluginInfo = yield promiseForPluginInfo("test");
+  ok(pluginInfo.activated, "Test 24a, Plugin should be active.");
+
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableUpdatable.xml", gTestBrowser);
+});
+
+// the plugin is now blocklisted, so it should not automatically load
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 24b, Should have a click-to-play notification");
+
+  let pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE,
+     "Test 24b, plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE");
+  ok(!pluginInfo.activated, "Test 24b, Plugin should not be active.");
+
+  // simulate "always allow"
+  yield promiseForNotificationShown(notification);
+
+  PopupNotifications.panel.firstChild._primaryButton.click();
+
+  pluginInfo = yield promiseForPluginInfo("test");
+  ok(pluginInfo.activated, "Test 24b, Plugin should be active.");
+
+  clearAllPluginPermissions();
+
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
+
+// Plugin sync removal test. Note this test produces a notification drop down since
+// the plugin we add has zero dims.
+add_task(function* () {
+  updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_syncRemoved.html");
+
+  // Maybe there some better trick here, we need to wait for the page load, then
+  // wait for the js to execute in the page.
+  yield waitForMs(500);
+
+  let notification = PopupNotifications.getNotification("click-to-play-plugins");
+  ok(notification, "Test 25: There should be a plugin notification even if the plugin was immediately removed");
+  ok(notification.dismissed, "Test 25: The notification should be dismissed by default");
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>");
+});
+
+// Tests a page with a blocked plugin in it and make sure the infoURL property
+// the blocklist file gets used.
+add_task(function* () {
+  clearAllPluginPermissions();
+
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginInfoURL.xml", gTestBrowser);
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let notification = PopupNotifications.getNotification("click-to-play-plugins");
+
+  // Since the plugin notification is dismissed by default, reshow it.
+  yield promiseForNotificationShown(notification);
+
+  let pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED,
+     "Test 26, plugin fallback type should be PLUGIN_BLOCKLISTED");
+
+  let result = ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    return objLoadingContent.activated;
+  });
+  ok(result, "Plugin should be activated.");
+
+  const testUrl = "http://test.url.com/";
+
+  let firstPanelChild = PopupNotifications.panel.firstChild;
+  let infoLink = document.getAnonymousElementByAttribute(firstPanelChild, "anonid",
+    "click-to-play-plugins-notification-link");
+  is(infoLink.href, testUrl,
+    "Test 26, the notification URL needs to match the infoURL from the blocklist file.");
+});
+
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_blocklist_content.js
@@ -0,0 +1,108 @@
+let gTestBrowser = null;
+let gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gChromeRoot = getRootDirectory(gTestPath);
+
+add_task(function* () {
+  registerCleanupFunction(Task.async(function*() {
+    clearAllPluginPermissions();
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+    resetBlocklist();
+    gBrowser.removeCurrentTab();
+    window.focus();
+    gTestBrowser = null;
+  }));
+});
+
+add_task(function* () {
+  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gTestBrowser = gBrowser.selectedBrowser;
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+
+  // Prime the blocklist service, the remote service doesn't launch on startup.
+  yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+  let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+  ok(!exmsg, "exception: " + exmsg);
+});
+
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let test = content.document.getElementById("test");
+    return test.activated;
+  });
+  ok(result, "task 1a: test plugin should be activated!");
+});
+
+// Load a fresh page, load a new plugin blocklist, then load the same page again.
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>GO!</html>");
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginHard.xml", gTestBrowser);
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let test = content.document.getElementById("test");
+    return test.activated;
+  });
+  ok(!result, "task 2a: test plugin shouldn't activate!");
+});
+
+// Unload the block list and lets do this again, only this time lets
+// hack around in the content blocklist service maliciously.
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>GO!</html>");
+
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+
+  // Hack the planet! Load our blocklist shim, so we can mess with blocklist
+  // return results in the content process. Active until we close our tab.
+  let mm = gTestBrowser.messageManager;
+  info("test 3a: loading " + gChromeRoot + "blocklist_proxy.js" + "\n");
+  mm.loadFrameScript(gChromeRoot + "blocklist_proxy.js", true);
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let test = content.document.getElementById("test");
+    return test.activated;
+  });
+  ok(result, "task 3a: test plugin should be activated!");
+});
+
+// Load a fresh page, load a new plugin blocklist, then load the same page again.
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>GO!</html>");
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginHard.xml", gTestBrowser);
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let test = content.document.getElementById("test");
+    return test.activated;
+  });
+  ok(!result, "task 4a: test plugin shouldn't activate!");
+
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
--- a/browser/base/content/test/plugins/browser_bug743421.js
+++ b/browser/base/content/test/plugins/browser_bug743421.js
@@ -1,114 +1,127 @@
-var rootDir = getRootDirectory(gTestPath);
-const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://mochi.test:8888/");
-
-var gTestBrowser = null;
-var gNextTest = null;
+let gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gTestBrowser = null;
 
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
+add_task(function* () {
+  registerCleanupFunction(Task.async(function*() {
     clearAllPluginPermissions();
     Services.prefs.clearUserPref("plugins.click_to_play");
-  });
-  Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+    resetBlocklist();
+    gBrowser.removeCurrentTab();
+    window.focus();
+    gTestBrowser = null;
+  }));
+});
 
-  var newTab = gBrowser.addTab();
+add_task(function* () {
+  let newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
   gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
-  prepareTest(test1a, gTestRoot + "plugin_add_dynamically.html");
-}
 
-function finishTest() {
-  gTestBrowser.removeEventListener("load", pageLoad, true);
-  gBrowser.removeCurrentTab();
-  window.focus();
-  finish();
-}
+  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
 
-function pageLoad() {
-  // The plugin events are async dispatched and can come after the load event
-  // This just allows the events to fire before we then go on to test the states
-  executeSoon(gNextTest);
-}
+  // Prime the blocklist service, the remote service doesn't launch on startup.
+  yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
 
-function prepareTest(nextTest, url) {
-  gNextTest = nextTest;
-  gTestBrowser.contentWindow.location = url;
-}
+  let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+  ok(!exmsg, "exception: " + exmsg);
+
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
 
 // Tests that navigation within the page and the window.history API doesn't break click-to-play state.
-function test1a() {
-  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(!popupNotification, "Test 1a, Should not have a click-to-play notification");
-  var plugin = new XPCNativeWrapper(XPCNativeWrapper.unwrap(gTestBrowser.contentWindow).addPlugin());
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_add_dynamically.html");
+
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(!notification, "Test 1a, Should not have a click-to-play notification");
+
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin());
+  });
 
-  var condition = function() PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  waitForCondition(condition, test1b, "Test 1a, Waited too long for plugin notification");
-}
+  yield promisePopupNotification("click-to-play-plugins");
+});
 
-function test1b() {
-  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "Test 1b, Should have a click-to-play notification");
-  var plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[0];
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 1b, Plugin should not be activated");
+add_task(function* () {
+  let isActivated = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementsByTagName("embed")[0];
+    let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    return objLoadingContent.activated;
+  });
+  ok(!isActivated, "Test 1b, Plugin should not be activated");
 
   // Click the activate button on doorhanger to make sure it works
-  popupNotification.reshow();
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+
+  yield promiseForNotificationShown(notification);
+
   PopupNotifications.panel.firstChild._primaryButton.click();
-  var condition = function() objLoadingContent.activated;
-  waitForCondition(condition, test1c, "Test 1b, Waited too long for plugin activation");
-}
-
-function test1c() {
-  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "Test 1c, Should still have a click-to-play notification");
-  var plugin = new XPCNativeWrapper(XPCNativeWrapper.unwrap(gTestBrowser.contentWindow).addPlugin());
 
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  var condition = function() objLoadingContent.activated;
-  waitForCondition(condition, test1d, "Test 1c, Waited too long for plugin activation");
-}
+  isActivated = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementsByTagName("embed")[0];
+    let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    return objLoadingContent.activated;
+  });
+  ok(isActivated, "Test 1b, Plugin should be activated");
+});
 
-function test1d() {
-  var plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[1];
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(objLoadingContent.activated, "Test 1d, Plugin should be activated");
+add_task(function* () {
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 1c, Should still have a click-to-play notification");
 
-  gNextTest = test1e;
-  gTestBrowser.contentWindow.addEventListener("hashchange", test1e, false);
-  gTestBrowser.contentWindow.location += "#anchorNavigation";
-}
+  let isActivated = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin());
+    let plugin = content.document.getElementsByTagName("embed")[1];
+    let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    return objLoadingContent.activated;
+  });
+  ok(isActivated, "Test 1c, Newly inserted plugin in activated page should be activated");
+});
 
-function test1e() {
-  gTestBrowser.contentWindow.removeEventListener("hashchange", test1e, false);
-
-  var plugin = new XPCNativeWrapper(XPCNativeWrapper.unwrap(gTestBrowser.contentWindow).addPlugin());
+add_task(function* () {
+  let isActivated = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementsByTagName("embed")[1];
+    let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    return objLoadingContent.activated;
+  });
+  ok(isActivated, "Test 1d, Plugin should be activated");
 
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  var condition = function() objLoadingContent.activated;
-  waitForCondition(condition, test1f, "Test 1e, Waited too long for plugin activation");
-}
+  let promise = waitForEvent(gTestBrowser.contentWindow, "hashchange", null);
+  gTestBrowser.contentWindow.location += "#anchorNavigation";
+  yield promise;
+});
 
-function test1f() {
-  var plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[2];
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(objLoadingContent.activated, "Test 1f, Plugin should be activated");
+add_task(function* () {
+  let isActivated = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin());
+    let plugin = content.document.getElementsByTagName("embed")[2];
+    let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    return objLoadingContent.activated;
+  });
+  ok(isActivated, "Test 1e, Plugin should be activated");
+});
 
-  gTestBrowser.contentWindow.history.replaceState({}, "", "replacedState");
-  var plugin = new XPCNativeWrapper(XPCNativeWrapper.unwrap(gTestBrowser.contentWindow).addPlugin());
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  var condition = function() objLoadingContent.activated;
-  waitForCondition(condition, test1g, "Test 1f, Waited too long for plugin activation");
-}
+add_task(function* () {
+  let isActivated = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementsByTagName("embed")[2];
+    let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    return objLoadingContent.activated;
+  });
+  ok(isActivated, "Test 1f, Plugin should be activated");
 
-function test1g() {
-  var plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[3];
-  var objLoadingContent2 = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(objLoadingContent2.activated, "Test 1g, Plugin should be activated");
-  finishTest();
-}
+  isActivated = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    content.history.replaceState({}, "", "replacedState");
+    new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin());
+    let plugin = content.document.getElementsByTagName("embed")[3];
+    let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    return objLoadingContent.activated;
+  });
+  ok(isActivated, "Test 1f, Plugin should not be activated");
+});
--- a/browser/base/content/test/plugins/browser_bug744745.js
+++ b/browser/base/content/test/plugins/browser_bug744745.js
@@ -1,43 +1,51 @@
-/* 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/. */
-
-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");
-    gTestBrowser.removeEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
-    gBrowser.removeCurrentTab();
-    window.focus();
-  });
-
-  Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(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";
-}
+let gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gTestBrowser = null;
+let gNumPluginBindingsAttached = 0;
 
 function pluginBindingAttached() {
   gNumPluginBindingsAttached++;
-
-  if (gNumPluginBindingsAttached == 1) {
-    var doc = gTestBrowser.contentDocument;
-    var testplugin = doc.getElementById("test");
-    ok(testplugin, "should have test plugin");
-    var style = getComputedStyle(testplugin);
-    ok('opacity' in style, "style should have opacity set");
-    is(style.opacity, 1, "opacity should be 1");
-    finish();
-  } else {
+  if (gNumPluginBindingsAttached != 1) {
     ok(false, "if we've gotten here, something is quite wrong");
   }
 }
+
+add_task(function* () {
+  registerCleanupFunction(function () {
+    gTestBrowser.removeEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
+    clearAllPluginPermissions();
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    gBrowser.removeCurrentTab();
+    window.focus();
+    gTestBrowser = null;
+  });
+});
+
+add_task(function* () {
+  gBrowser.selectedTab = gBrowser.addTab();
+  gTestBrowser = gBrowser.selectedBrowser;
+
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+  gTestBrowser.addEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
+
+  let testRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+  yield promiseTabLoadEvent(gBrowser.selectedTab, testRoot + "plugin_bug744745.html");
+
+  yield promiseForCondition(function () { return gNumPluginBindingsAttached == 1; });
+
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    if (!plugin) {
+      return false;
+    }
+    // We can't use MochiKit's routine
+    let style = content.getComputedStyle(plugin);
+    return 'opacity' in style && style.opacity == 1;
+  });
+
+  ok(result, true, "plugin style properly configured.");
+});
--- a/browser/base/content/test/plugins/browser_bug787619.js
+++ b/browser/base/content/test/plugins/browser_bug787619.js
@@ -1,49 +1,65 @@
-const gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-
+let gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
 let gTestBrowser = null;
 let gWrapperClickCount = 0;
 
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
     Services.prefs.clearUserPref("plugins.click_to_play");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    gBrowser.removeCurrentTab();
+    window.focus();
+    gTestBrowser = null;
   });
+});
+
+add_task(function* () {
   Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
 
   gBrowser.selectedTab = gBrowser.addTab();
   gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
-  gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_bug787619.html";
-}
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
 
-function pageLoad() {
+  let testRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+  yield promiseTabLoadEvent(gBrowser.selectedTab, testRoot + "plugin_bug787619.html");
+
   // Due to layout being async, "PluginBindAttached" may trigger later.
   // This forces a layout flush, thus triggering it, and schedules the
   // test so it is definitely executed afterwards.
-  gTestBrowser.contentDocument.getElementById('plugin').clientTop;
-  executeSoon(part1);
-}
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-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');
-  ok(!plugin.activated, 'plugin should not be activated');
-  ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed, "Doorhanger should not be open");
+  // check plugin state
+  let pluginInfo = yield promiseForPluginInfo("plugin");
+  ok(!pluginInfo.activated, "1a plugin should not be activated");
 
-  EventUtils.synthesizeMouseAtCenter(plugin, {}, gTestBrowser.contentWindow);
-  let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed;
-  waitForCondition(condition, part2,
-                   'waited too long for plugin to activate');
-}
+  // click the overlay to prompt
+  let promise = promisePopupNotification("click-to-play-plugins");
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementById("plugin");
+    let bounds = plugin.getBoundingClientRect();
+    let left = (bounds.left + bounds.right) / 2;
+    let top = (bounds.top + bounds.bottom) / 2;
+    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+  });
+  yield promise;
 
-function part2() {
+  // check plugin state
+  pluginInfo = yield promiseForPluginInfo("plugin");
+  ok(!pluginInfo.activated, "1b plugin should not be activated");
+
+  let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
+    PopupNotifications.panel.firstChild;
+  yield promiseForCondition(condition);
+  PopupNotifications.panel.firstChild._primaryButton.click();
+
+  // check plugin state
+  pluginInfo = yield promiseForPluginInfo("plugin");
+  ok(pluginInfo.activated, "plugin should be activated");
+
   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/plugins/browser_bug797677.js
+++ b/browser/base/content/test/plugins/browser_bug797677.js
@@ -1,47 +1,45 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- *  License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+let gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gTestBrowser = null;
+let gConsoleErrors = 0;
 
-var rootDir = getRootDirectory(gTestPath);
-const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
 const Cc = Components.classes;
 const Ci = Components.interfaces;
-var gTestBrowser = null;
-var gConsoleErrors = 0;
 
-function test() {
-  waitForExplicitFinish();
-  var newTab = gBrowser.addTab();
-  gBrowser.selectedTab = newTab;
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    consoleService.unregisterListener(errorListener);
+    gBrowser.removeCurrentTab();
+    window.focus();
+    gTestBrowser = null;
+  });
+
+  gBrowser.selectedTab = gBrowser.addTab();
   gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
-  var consoleService = Cc["@mozilla.org/consoleservice;1"]
+
+  let bindingPromise = waitForEvent(gTestBrowser, "PluginBindingAttached", null, true, true);
+
+  let consoleService = Cc["@mozilla.org/consoleservice;1"]
                          .getService(Ci.nsIConsoleService);
-  var errorListener = {
+  let errorListener = {
     observe: function(aMessage) {
-      if (aMessage.message.includes("NS_ERROR"))
+      if (aMessage.message.includes("NS_ERROR_FAILURE"))
         gConsoleErrors++;
     }
   };
   consoleService.registerListener(errorListener);
-  registerCleanupFunction(function() {
-    gTestBrowser.removeEventListener("PluginBindingAttached", pluginBindingAttached, true);
-    consoleService.unregisterListener(errorListener);
-    gBrowser.removeCurrentTab();
-    window.focus();
-  });
-  gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_bug797677.html";
-}
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_bug797677.html");
+
+  yield bindingPromise;
 
-function pluginBindingAttached() {
-  // Let browser-plugins.js handle the PluginNotFound event, then run the test
-  executeSoon(runTest);
-}
+  let pluginInfo = yield promiseForPluginInfo("plugin");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED, "plugin should not have been found.");
 
-function runTest() {
-  var doc = gTestBrowser.contentDocument;
-  var plugin = doc.getElementById("plugin");
+  // simple cpows
+  let plugin = gTestBrowser.contentDocument.getElementById("plugin");
   ok(plugin, "plugin should be in the page");
   is(gConsoleErrors, 0, "should have no console errors");
-  finish();
-}
+});
--- a/browser/base/content/test/plugins/browser_bug812562.js
+++ b/browser/base/content/test/plugins/browser_bug812562.js
@@ -1,93 +1,80 @@
-var gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-var gTestBrowser = null;
-var gNextTest = null;
-
-Components.utils.import("resource://gre/modules/Services.jsm");
+let gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gTestBrowser = null;
 
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
+add_task(function* () {
+  registerCleanupFunction(Task.async(function*() {
+    clearAllPluginPermissions();
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+    resetBlocklist();
     Services.prefs.clearUserPref("plugins.click_to_play");
-  });
-  Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
-
-  var newTab = gBrowser.addTab();
-  gBrowser.selectedTab = newTab;
+    gBrowser.removeCurrentTab();
+    window.focus();
+    gTestBrowser = null;
+  }));
+  gBrowser.selectedTab = gBrowser.addTab();
   gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
-  setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableUpdatable.xml",
-  function() {
-    prepareTest(function() {
-        // Due to layout being async, "PluginBindAttached" may trigger later.
-        // This forces a layout flush, thus triggering it, and schedules the
-        // test so it is definitely executed afterwards.
-        gTestBrowser.contentDocument.getElementById('test').clientTop;
-        testPart1();
-      },
-      gHttpTestRoot + "plugin_test.html");
-  });
-}
+
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
 
-function finishTest() {
-  gTestBrowser.removeEventListener("load", pageLoad, true);
-  gBrowser.removeCurrentTab();
-  window.focus();
-  setAndUpdateBlocklist(gHttpTestRoot + "blockNoPlugins.xml",
-  function() {
-    resetBlocklist();
-    finish();
-  });
-}
-
-function pageLoad(aEvent) {
-  // The plugin events are async dispatched and can come after the load event
-  // This just allows the events to fire before we then go on to test the states
-  if (gNextTest != null)
-    executeSoon(gNextTest);
-}
-
-function prepareTest(nextTest, url) {
-  gNextTest = nextTest;
-  gTestBrowser.contentWindow.location = url;
-}
+  // Prime the blocklist service, the remote service doesn't launch on startup.
+  yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+  let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+  ok(!exmsg, "exception: " + exmsg);
+});
 
 // Tests that the going back will reshow the notification for click-to-play
-// blocklisted plugins (part 1/4)
-function testPart1() {
-  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "test part 1: Should have a click-to-play notification");
-  var plugin = gTestBrowser.contentDocument.getElementById("test");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "test part 1: plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE");
-  ok(!objLoadingContent.activated, "test part 1: plugin should not be activated");
+// blocklisted plugins
+add_task(function* () {
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableUpdatable.xml", gTestBrowser);
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-  prepareTest(testPart2, "about:blank");
-}
+  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(popupNotification, "test part 1: Should have a click-to-play notification");
+
+  let pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "plugin should be marked as VULNERABLE");
 
-function testPart2() {
-  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(!popupNotification, "test part 2: Should not have a click-to-play notification");
-  var plugin = gTestBrowser.contentDocument.getElementById("test");
-  ok(!plugin, "test part 2: Should not have a plugin in this page");
+  let found = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    return !!content.document.getElementById("test");
+  });
+  ok(found, "test part 1: plugin should not be activated");
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+});
 
-  Services.obs.addObserver(testPart3, "PopupNotifications-updateNotShowing", false);
-  gTestBrowser.contentWindow.history.back();
-}
+add_task(function* () {
+  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(!popupNotification, "test part 2: Should not have a click-to-play notification");
+  let found = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    return !!content.document.getElementById("test");
+  });
+  ok(!found, "test part 2: plugin should not be activated");
 
-function testPart3() {
-  Services.obs.removeObserver(testPart3, "PopupNotifications-updateNotShowing");
-  var condition = function() PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  waitForCondition(condition, testPart4, "test part 3: waited too long for click-to-play-plugin notification");
-}
+  let obsPromise = TestUtils.topicObserved("PopupNotifications-updateNotShowing");
+  let overlayPromise = promisePopupNotification("click-to-play-plugins");
+  gTestBrowser.contentWindow.history.back();
+  yield obsPromise;
+  yield overlayPromise;
+});
 
-function testPart4() {
-  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "test part 4: Should have a click-to-play notification");
-  var plugin = gTestBrowser.contentDocument.getElementById("test");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "test part 4: plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE");
-  ok(!objLoadingContent.activated, "test part 4: plugin should not be activated");
+add_task(function* () {
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(popupNotification, "test part 3: Should have a click-to-play notification");
 
-  finishTest();
-}
+  let pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "plugin should be marked as VULNERABLE");
+
+  let found = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    return !!content.document.getElementById("test");
+  });
+  ok(found, "test part 3: plugin should not be activated");
+});
--- a/browser/base/content/test/plugins/browser_bug818118.js
+++ b/browser/base/content/test/plugins/browser_bug818118.js
@@ -1,45 +1,38 @@
-var gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-var gTestBrowser = null;
-
-Components.utils.import("resource://gre/modules/Services.jsm");
+let gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gTestBrowser = null;
 
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
     Services.prefs.clearUserPref("plugins.click_to_play");
-    gTestBrowser.removeEventListener("load", pageLoad, true);
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    gBrowser.removeCurrentTab();
+    window.focus();
+    gTestBrowser = null;
   });
+});
 
+add_task(function* () {
   Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
 
   gBrowser.selectedTab = gBrowser.addTab();
   gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
-  gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_both.html";
-}
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
 
-function pageLoad(aEvent) {
-  // Due to layout being async, "PluginBindAttached" may trigger later.
-  // This forces a layout flush, thus triggering it, and schedules the
-  // test so it is definitely executed afterwards.
-  gTestBrowser.contentDocument.getElementById('test').clientTop;
-  executeSoon(actualTest);
-}
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_both.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-function actualTest() {
-  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(popupNotification, "should have a click-to-play notification");
-  var plugin = gTestBrowser.contentDocument.getElementById("test");
-  ok(plugin, "should have known plugin in page");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
-  ok(!objLoadingContent.activated, "plugin should not be activated");
 
-  var unknown = gTestBrowser.contentDocument.getElementById("unknown");
-  ok(unknown, "should have unknown plugin in page");
+  let pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "plugin should be click to play");
+  ok(!pluginInfo.activated, "plugin should not be activated");
 
-  gBrowser.removeCurrentTab();
-  window.focus();
-  finish();
-}
+  let unknown = gTestBrowser.contentDocument.getElementById("unknown");
+  ok(unknown, "should have unknown plugin in page");
+});
--- a/browser/base/content/test/plugins/browser_bug820497.js
+++ b/browser/base/content/test/plugins/browser_bug820497.js
@@ -1,61 +1,73 @@
-/* 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/. */
-
-var gTestBrowser = null;
-var gNumPluginBindingsAttached = 0;
+let gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gTestBrowser = null;
+let gNumPluginBindingsAttached = 0;
 
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
     Services.prefs.clearUserPref("plugins.click_to_play");
-    gTestBrowser.removeEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
     gBrowser.removeCurrentTab();
     window.focus();
+    gTestBrowser = null;
   });
+});
 
+add_task(function* () {
   Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
 
   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";
-}
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+
+  gTestBrowser.addEventListener("PluginBindingAttached", function () { gNumPluginBindingsAttached++ }, true, true);
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_bug820497.html");
 
-function pluginBindingAttached() {
-  gNumPluginBindingsAttached++;
+  yield promiseForCondition(function () { return gNumPluginBindingsAttached == 1; });  
 
-  if (gNumPluginBindingsAttached == 1) {
-    var doc = gTestBrowser.contentDocument;
-    var testplugin = doc.getElementById("test");
+  // cpows
+  {
+    // Note we add the second plugin in the code farther down, so there's
+    // no way we got here with anything but one plugin loaded.
+    let doc = gTestBrowser.contentDocument;
+    let testplugin = doc.getElementById("test");
     ok(testplugin, "should have test plugin");
-    var secondtestplugin = doc.getElementById("secondtest");
+    let secondtestplugin = doc.getElementById("secondtest");
     ok(!secondtestplugin, "should not yet have second test plugin");
-    var notification;
-    waitForNotificationPopup("click-to-play-plugins", gTestBrowser, (notification => {
-      ok(notification, "should have popup notification");
-      // We don't set up the action list until the notification is shown
-      notification.reshow();
-      is(notification.options.pluginData.size, 1, "should be 1 type of plugin in the popup notification");
-      XPCNativeWrapper.unwrap(gTestBrowser.contentWindow).addSecondPlugin();
-    }));
-  } else if (gNumPluginBindingsAttached == 2) {
-    var doc = gTestBrowser.contentDocument;
-    var testplugin = doc.getElementById("test");
+  }
+
+  yield promisePopupNotification("click-to-play-plugins");
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "should have a click-to-play notification");
+
+  yield promiseForNotificationShown(notification);
+
+  is(notification.options.pluginData.size, 1, "should be 1 type of plugin in the popup notification");
+
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    XPCNativeWrapper.unwrap(content).addSecondPlugin();
+  });
+
+  yield promiseForCondition(function () { return gNumPluginBindingsAttached == 2; });  
+
+  // cpows
+  {
+    let doc = gTestBrowser.contentDocument;
+    let testplugin = doc.getElementById("test");
     ok(testplugin, "should have test plugin");
-    var secondtestplugin = doc.getElementById("secondtest");
+    let secondtestplugin = doc.getElementById("secondtest");
     ok(secondtestplugin, "should have second test plugin");
-    var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-    ok(notification, "should have popup notification");
-    notification.reshow();
-    let condition = () => (notification.options.pluginData.size == 2);
-    waitForCondition(condition, finish, "Waited too long for 2 types of plugins in popup notification");
-  } else {
-    ok(false, "if we've gotten here, something is quite wrong");
   }
-}
+
+  notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+
+  ok(notification, "should have popup notification");
+
+  yield promiseForNotificationShown(notification);
+
+  is(notification.options.pluginData.size, 2, "aited too long for 2 types of plugins in popup notification");
+});
--- a/browser/base/content/test/plugins/browser_clearplugindata.html
+++ b/browser/base/content/test/plugins/browser_clearplugindata.html
@@ -16,17 +16,15 @@
         p.setSitesWithDataCapabilities(true);
 
         p.setSitesWithData(
           "foo.com:0:5," +
           "bar.com:0:100," +
           "baz.com:1:5," +
           "qux.com:1:100"
         );
-
-        setTimeout(testFinishedCallback, 0);
       }
     </script>
   </head>
 
   <body onload="testSteps();"></body>
 
 </html>
--- a/browser/base/content/test/plugins/browser_clearplugindata.js
+++ b/browser/base/content/test/plugins/browser_clearplugindata.js
@@ -1,133 +1,127 @@
-/**
- * Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-var rootDir = getRootDirectory(gTestPath);
-const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://mochi.test:8888/");
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+var gTestBrowser = null;
 
 // Test clearing plugin data using sanitize.js.
-const testURL1 = gHttpTestRoot + "browser_clearplugindata.html";
-const testURL2 = gHttpTestRoot + "browser_clearplugindata_noage.html";
+const testURL1 = gTestRoot + "browser_clearplugindata.html";
+const testURL2 = gTestRoot + "browser_clearplugindata_noage.html";
 
-let tempScope = {};
+var tempScope = {};
 Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
                                            .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
-let Sanitizer = tempScope.Sanitizer;
+var Sanitizer = tempScope.Sanitizer;
 
 const pluginHostIface = Ci.nsIPluginHost;
 var pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
 pluginHost.QueryInterface(pluginHostIface);
 
 var pluginTag = getTestPlugin();
-var s;
+var sanitizer = null;
 
 function stored(needles) {
-  var something = pluginHost.siteHasData(this.pluginTag, null);
+  let something = pluginHost.siteHasData(this.pluginTag, null);
   if (!needles)
     return something;
 
   if (!something)
     return false;
 
-  for (var i = 0; i < needles.length; ++i) {
+  for (let i = 0; i < needles.length; ++i) {
     if (!pluginHost.siteHasData(this.pluginTag, needles[i]))
       return false;
   }
   return true;
 }
 
-function test() {
-  waitForExplicitFinish();
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    if (gTestBrowser) {
+      gBrowser.removeCurrentTab();
+    }
+    window.focus();
+    gTestBrowser = null;
+  });
+});
 
-  s = new Sanitizer();
-  s.ignoreTimespan = false;
-  s.prefDomain = "privacy.cpd.";
-  var itemPrefs = gPrefService.getBranch(s.prefDomain);
+add_task(function* () {
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+
+  sanitizer = new Sanitizer();
+  sanitizer.ignoreTimespan = false;
+  sanitizer.prefDomain = "privacy.cpd.";
+  let itemPrefs = gPrefService.getBranch(sanitizer.prefDomain);
   itemPrefs.setBoolPref("history", false);
   itemPrefs.setBoolPref("downloads", false);
   itemPrefs.setBoolPref("cache", false);
   itemPrefs.setBoolPref("cookies", true); // plugin data
   itemPrefs.setBoolPref("formdata", false);
   itemPrefs.setBoolPref("offlineApps", false);
   itemPrefs.setBoolPref("passwords", false);
   itemPrefs.setBoolPref("sessions", false);
   itemPrefs.setBoolPref("siteSettings", false);
-
-  executeSoon(test_with_age);
-}
+});
 
-function setFinishedCallback(callback)
-{
-  let testPage = gBrowser.selectedBrowser.contentWindow.wrappedJSObject;
-  testPage.testFinishedCallback = function() {
-    setTimeout(function() {
-      info("got finished callback");
-      callback();
-    }, 0);
-  }
-}
-
-function test_with_age()
-{
+add_task(function* () {
   // Load page to set data for the plugin.
   gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function () {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+  gTestBrowser = gBrowser.selectedBrowser;
 
-    setFinishedCallback(function() {
-      ok(stored(["foo.com","bar.com","baz.com","qux.com"]),
-        "Data stored for sites");
+  yield promiseTabLoadEvent(gBrowser.selectedTab, testURL1);
+
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-      // Clear 20 seconds ago
-      var now_uSec = Date.now() * 1000;
-      s.range = [now_uSec - 20*1000000, now_uSec];
-      s.sanitize();
+  ok(stored(["foo.com","bar.com","baz.com","qux.com"]),
+    "Data stored for sites");
 
-      ok(stored(["bar.com","qux.com"]), "Data stored for sites");
-      ok(!stored(["foo.com"]), "Data cleared for foo.com");
-      ok(!stored(["baz.com"]), "Data cleared for baz.com");
+  // Clear 20 seconds ago
+  let now_uSec = Date.now() * 1000;
+  sanitizer.range = [now_uSec - 20*1000000, now_uSec];
+  sanitizer.sanitize();
 
-      // Clear everything
-      s.range = null;
-      s.sanitize();
+  ok(stored(["bar.com","qux.com"]), "Data stored for sites");
+  ok(!stored(["foo.com"]), "Data cleared for foo.com");
+  ok(!stored(["baz.com"]), "Data cleared for baz.com");
 
-      ok(!stored(null), "All data cleared");
-
-      gBrowser.removeCurrentTab();
+  // Clear everything
+  sanitizer.range = null;
+  sanitizer.sanitize();
 
-      executeSoon(test_without_age);
-    });
-  }, true);
-  content.location = testURL1;
-}
+  ok(!stored(null), "All data cleared");
 
-function test_without_age()
-{
+  gBrowser.removeCurrentTab();
+  gTestBrowser = null;
+});
+
+add_task(function* () {
   // Load page to set data for the plugin.
   gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function () {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+  gTestBrowser = gBrowser.selectedBrowser;
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, testURL2);
 
-    setFinishedCallback(function() {
-      ok(stored(["foo.com","bar.com","baz.com","qux.com"]),
-        "Data stored for sites");
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  ok(stored(["foo.com","bar.com","baz.com","qux.com"]),
+    "Data stored for sites");
 
-      // Attempt to clear 20 seconds ago. The plugin will throw
-      // NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED, which should result in us
-      // clearing all data regardless of age.
-      var now_uSec = Date.now() * 1000;
-      s.range = [now_uSec - 20*1000000, now_uSec];
-      s.sanitize();
-
-      ok(!stored(null), "All data cleared");
+  // Attempt to clear 20 seconds ago. The plugin will throw
+  // NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED, which should result in us
+  // clearing all data regardless of age.
+  let now_uSec = Date.now() * 1000;
+  sanitizer.range = [now_uSec - 20*1000000, now_uSec];
+  sanitizer.sanitize();
 
-      gBrowser.removeCurrentTab();
+  ok(!stored(null), "All data cleared");
 
-      executeSoon(finish);
-    });
-  }, true);
-  content.location = testURL2;
-}
+  gBrowser.removeCurrentTab();
+  gTestBrowser = null;
+});
 
--- a/browser/base/content/test/plugins/browser_clearplugindata_noage.html
+++ b/browser/base/content/test/plugins/browser_clearplugindata_noage.html
@@ -16,17 +16,15 @@
         p.setSitesWithDataCapabilities(false);
 
         p.setSitesWithData(
           "foo.com:0:5," +
           "bar.com:0:100," +
           "baz.com:1:5," +
           "qux.com:1:100"
         );
-
-        setTimeout(testFinishedCallback, 0);
       }
     </script>
   </head>
 
   <body onload="testSteps();"></body>
 
 </html>
--- a/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js
+++ b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js
@@ -1,12 +1,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/. */
-
 /**
  * Test that plugin crash submissions still work properly after
  * click-to-play activation.
  */
 add_task(function*() {
   yield BrowserTestUtils.withNewTab({
     gBrowser,
     url: "about:blank",
--- a/browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js
+++ b/browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js
@@ -1,14 +1,9 @@
-/* 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/. */
-
 Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
-Cu.import("resource://gre/modules/Services.jsm");
 
 const CRASH_URL = "http://example.com/browser/browser/base/content/test/plugins/plugin_crashCommentAndURL.html";
 const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
 
 /**
  * Frame script that will be injected into the test browser
  * to cause plugin crashes, and then manipulate the crashed plugin
  * UI. The specific actions and checks that occur in the frame
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_plugin_infolink.js
@@ -0,0 +1,77 @@
+let gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+let gTestBrowser = null;
+
+add_task(function* () {
+  registerCleanupFunction(function () {
+    clearAllPluginPermissions();
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    gTestBrowser = null;
+    gBrowser.removeCurrentTab();
+    window.focus();
+  });
+});
+
+add_task(function* () {
+  gBrowser.selectedTab = gBrowser.addTab();
+  gTestBrowser = gBrowser.selectedBrowser;
+
+  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_DISABLED, "Test Plug-in");
+
+  // Prime the blocklist service, the remote service doesn't launch on startup.
+  yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+  let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+  ok(!exmsg, "exception: " + exmsg);
+
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
+
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_DISABLED,
+     "Test 1a, plugin fallback type should be PLUGIN_DISABLED");
+
+  // This test opens a new tab to about:addons
+  let promise = waitForEvent(gBrowser.tabContainer, "TabOpen", null, true);
+  let success = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let pluginNode = content.document.getElementById("test");
+    let manageLink = content.document.getAnonymousElementByAttribute(pluginNode, "anonid", "managePluginsLink");
+    let bounds = manageLink.getBoundingClientRect();
+    let left = (bounds.left + bounds.right) / 2;
+    let top = (bounds.top + bounds.bottom) / 2;
+    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+    return true;
+  });
+  ok(success, "click on manage link");
+  yield promise;
+
+  promise = waitForEvent(gBrowser.tabContainer, "TabClose", null, true);
+
+  // in-process page, no cpows here
+  let condition = function() {
+    let win = gBrowser.selectedBrowser.contentWindow;
+    if (!!win && !!win.wrappedJSObject && !!win.wrappedJSObject.gViewController) {
+      return win.wrappedJSObject.gViewController.currentViewId == "addons://list/plugin";
+    }
+    return false;
+  }
+
+  yield promiseForCondition(condition, "Waited too long for about:addons to display.", 40, 500);
+
+  // remove the tab containing about:addons
+  gBrowser.removeCurrentTab();
+
+  yield promise;
+});
+
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_plugin_reloading.js
@@ -0,0 +1,85 @@
+let gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+let gTestBrowser = null;
+
+function updateAllTestPlugins(aState) {
+  setTestPluginEnabledState(aState, "Test Plug-in");
+  setTestPluginEnabledState(aState, "Second Test Plug-in");
+}
+
+add_task(function* () {
+  registerCleanupFunction(Task.async(function*() {
+    clearAllPluginPermissions();
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+    resetBlocklist();
+    gTestBrowser = null;
+    gBrowser.removeCurrentTab();
+    window.focus();
+  }));
+});
+
+add_task(function* () {
+  gBrowser.selectedTab = gBrowser.addTab();
+  gTestBrowser = gBrowser.selectedBrowser;
+
+  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+  updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+  // Prime the blocklist service, the remote service doesn't launch on startup.
+  yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+  let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+  ok(!exmsg, "exception: " + exmsg);
+
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
+
+// Tests that a click-to-play plugin retains its activated state upon reloading
+add_task(function* () {
+  clearAllPluginPermissions();
+
+  updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 1, Should have a click-to-play notification");
+
+  let pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+     "Test 2, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+
+  // run the plugin
+  yield promisePlayObject("test");
+
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_PLUGIN, "Test 3, plugin should have started");
+  ok(pluginInfo.activated, "Test 4, plugin node should not be activated");
+
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let plugin = content.document.getElementById("test");
+    let npobj1 = Components.utils.waiveXrays(plugin).getObjectValue();
+    plugin.src = plugin.src;
+    let pluginsDiffer = false;
+     try {
+       Components.utils.waiveXrays(plugin).checkObjectValue(npobj1);
+     } catch (e) {
+       pluginsDiffer = true;
+     }
+     return pluginsDiffer;
+  });
+  ok(result, "Test 5, plugins differ.");
+
+  pluginInfo = yield promiseForPluginInfo("test");
+  ok(pluginInfo.activated, "Test 6, Plugin should have retained activated state.");
+  is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_PLUGIN, "Test 7, plugin should have started");
+});
--- a/browser/base/content/test/plugins/browser_pluginnotification.js
+++ b/browser/base/content/test/plugins/browser_pluginnotification.js
@@ -1,869 +1,578 @@
-var rootDir = getRootDirectory(gTestPath);
-const gTestRoot = rootDir;
-const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-
-var gTestBrowser = null;
-var gNextTest = null;
-var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
-
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-// This listens for the next opened tab and checks it is of the right url.
-// opencallback is called when the new tab is fully loaded
-// closecallback is called when the tab is closed
-function TabOpenListener(url, opencallback, closecallback) {
-  this.url = url;
-  this.opencallback = opencallback;
-  this.closecallback = closecallback;
-
-  gBrowser.tabContainer.addEventListener("TabOpen", this, false);
-}
-
-TabOpenListener.prototype = {
-  url: null,
-  opencallback: null,
-  closecallback: null,
-  tab: null,
-  browser: null,
-
-  handleEvent: function(event) {
-    if (event.type == "TabOpen") {
-      gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
-      this.tab = event.originalTarget;
-      this.browser = this.tab.linkedBrowser;
-      gBrowser.addEventListener("pageshow", this, false);
-    } else if (event.type == "pageshow") {
-      if (event.target.location.href != this.url)
-        return;
-      gBrowser.removeEventListener("pageshow", this, false);
-      this.tab.addEventListener("TabClose", this, false);
-      var url = this.browser.contentDocument.location.href;
-      is(url, this.url, "Should have opened the correct tab");
-      this.opencallback(this.tab, this.browser.contentWindow);
-    } else if (event.type == "TabClose") {
-      if (event.originalTarget != this.tab)
-        return;
-      this.tab.removeEventListener("TabClose", this, false);
-      this.opencallback = null;
-      this.tab = null;
-      this.browser = null;
-      // Let the window close complete
-      executeSoon(this.closecallback);
-      this.closecallback = null;
-    }
-  }
-};
+let gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+let gTestBrowser = null;
 
-function test() {
-  waitForExplicitFinish();
-  SimpleTest.requestCompleteLog();
-  requestLongerTimeout(2);
-  registerCleanupFunction(function() {
-    clearAllPluginPermissions();
-    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
-    return new Promise(resolve => {
-      setAndUpdateBlocklist(gHttpTestRoot + "blockNoPlugins.xml", resolve);
-    });
-  });
-  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
-
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
-
-  var newTab = gBrowser.addTab();
-  gBrowser.selectedTab = newTab;
-  gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
-  prepareTest(runAfterPluginBindingAttached(test1a), gTestRoot + "plugin_unknown.html");
-}
-
-function finishTest() {
-  clearAllPluginPermissions();
-  gTestBrowser.removeEventListener("load", pageLoad, true);
-  gBrowser.removeCurrentTab();
-  window.focus();
-  finish();
-}
-
-function pageLoad() {
-  // The plugin events are async dispatched and can come after the load event
-  // This just allows the events to fire before we then go on to test the states
-  executeSoon(gNextTest);
-}
-
-function prepareTest(nextTest, url) {
-  gNextTest = nextTest;
-  gTestBrowser.contentWindow.location = url;
-}
-
-// Due to layout being async, "PluginBindAttached" may trigger later.
-// This wraps a function to force a layout flush, thus triggering it,
-// and schedules the function execution so they're definitely executed
-// afterwards.
-function runAfterPluginBindingAttached(func) {
-  return function() {
-    let doc = gTestBrowser.contentDocument;
-    let elems = doc.getElementsByTagName('embed');
-    if (elems.length < 1) {
-      elems = doc.getElementsByTagName('object');
-    }
-    elems[0].clientTop;
-    executeSoon(func);
-  };
-}
-
-// Tests a page with an unknown plugin in it.
-function test1a() {
-  var pluginNode = gTestBrowser.contentDocument.getElementById("unknown");
-  ok(pluginNode, "Test 1a, Found plugin in page");
-  var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED, "Test 1a, plugin fallback type should be PLUGIN_UNSUPPORTED");
-
-  prepareTest(runAfterPluginBindingAttached(test1b), gTestRoot + "plugin_unknown.html");
-}
-
-
-function test1b() {
-  var plugin = getTestPlugin();
-  ok(plugin, "Test 1b, Should have a test plugin");
-  plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
-  prepareTest(runAfterPluginBindingAttached(test2), gTestRoot + "plugin_test.html");
+function updateAllTestPlugins(aState) {
+  setTestPluginEnabledState(aState, "Test Plug-in");
+  setTestPluginEnabledState(aState, "Second Test Plug-in");
 }
 
-// Tests a page with a working plugin in it.
-function test2() {
-  var plugin = getTestPlugin();
-  ok(plugin, "Should have a test plugin");
-  plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
-  prepareTest(runAfterPluginBindingAttached(test3), gTestRoot + "plugin_test.html");
-}
-
-// Tests a page with a disabled plugin in it.
-function test3() {
-  new TabOpenListener("about:addons", test4, prepareTest5);
-
-  var pluginNode = gTestBrowser.contentDocument.getElementById("test");
-  ok(pluginNode, "Test 3, Found plugin in page");
-  var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_DISABLED, "Test 3, plugin fallback type should be PLUGIN_DISABLED");
-  var manageLink = gTestBrowser.contentDocument.getAnonymousElementByAttribute(pluginNode, "anonid", "managePluginsLink");
-  ok(manageLink, "Test 3, found 'manage' link in plugin-problem binding");
-
-  EventUtils.synthesizeMouseAtCenter(manageLink, {}, gTestBrowser.contentWindow);
-}
-
-function test4(tab, win) {
-  is(win.wrappedJSObject.gViewController.currentViewId, "addons://list/plugin", "Test 4, Should have displayed the plugins pane");
-  gBrowser.removeTab(tab);
-}
+add_task(function* () {
+  registerCleanupFunction(Task.async(function*() {
+    clearAllPluginPermissions();
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+    resetBlocklist();
+    gTestBrowser = null;
+    gBrowser.removeCurrentTab();
+    window.focus();
+  }));
+});
 
-function prepareTest5() {
-  info("prepareTest5");
-  var plugin = getTestPlugin();
-  plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
-  setAndUpdateBlocklist(gHttpTestRoot + "blockPluginHard.xml",
-    function() {
-      info("prepareTest5 callback");
-      prepareTest(runAfterPluginBindingAttached(test5), gTestRoot + "plugin_test.html");
-  });
-}
+add_task(function* () {
+  gBrowser.selectedTab = gBrowser.addTab();
+  gTestBrowser = gBrowser.selectedBrowser;
+
+  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
 
-// Tests a page with a blocked plugin in it.
-function test5() {
-  info("test5");
-  let notification = PopupNotifications.getNotification("click-to-play-plugins");
-  ok(notification, "Test 5: There should be a plugin notification for blocked plugins");
-  ok(notification.dismissed, "Test 5: The plugin notification should be dismissed by default");
+  updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
 
-  notification.reshow();
-  is(notification.options.pluginData.size, 1, "Test 5: Only the blocked plugin should be present in the notification");
-  ok(PopupNotifications.panel.firstChild._buttonContainer.hidden, "Part 5: The blocked plugins notification should not have any buttons visible.");
+  // Prime the blocklist service, the remote service doesn't launch on startup.
+  yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+  let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+  ok(!exmsg, "exception: " + exmsg);
 
-  var pluginNode = gTestBrowser.contentDocument.getElementById("test");
-  ok(pluginNode, "Test 5, Found plugin in page");
-  var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED, "Test 5, plugin fallback type should be PLUGIN_BLOCKLISTED");
-
-  prepareTest(runAfterPluginBindingAttached(test6), gTestRoot + "plugin_both.html");
-}
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
 
-// Tests a page with a blocked and unknown plugin in it.
-function test6() {
-  prepareTest(runAfterPluginBindingAttached(test7), gTestRoot + "plugin_both2.html");
-}
+// Tests a page with an unknown plugin in it.
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_unknown.html");
 
-// Tests a page with a blocked and unknown plugin in it (alternate order to above).
-function test7() {
-  var plugin = getTestPlugin();
-  plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
-  getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-  setAndUpdateBlocklist(gHttpTestRoot + "blockNoPlugins.xml", function() {
-    prepareTest(runAfterPluginBindingAttached(test8), gTestRoot + "plugin_test.html");
-  });
-}
-
-// Tests a page with a working plugin that is click-to-play
-function test8() {
-  ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser), "Test 8, Should have a click-to-play notification");
+  let pluginInfo = yield promiseForPluginInfo("unknown");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED,
+     "Test 1a, plugin fallback type should be PLUGIN_UNSUPPORTED");
+});
 
-  var pluginNode = gTestBrowser.contentDocument.getElementById("test");
-  ok(pluginNode, "Test 8, Found plugin in page");
-  var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 8, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
-
-  prepareTest(runAfterPluginBindingAttached(test11a), gTestRoot + "plugin_test3.html");
-}
-
-// Tests 9 & 10 removed
+// Tests that going back will reshow the notification for click-to-play plugins
+add_task(function* () {
+  updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
 
-// Tests that the going back will reshow the notification for click-to-play plugins (part 1/4)
-function test11a() {
-  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "Test 11a, Should have a click-to-play notification");
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  yield promisePopupNotification("click-to-play-plugins");
 
-  prepareTest(test11b, "about:blank");
-}
+  yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>");
 
-// Tests that the going back will reshow the notification for click-to-play plugins (part 2/4)
-function test11b() {
-  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(!popupNotification, "Test 11b, Should not have a click-to-play notification");
+  // make sure the notification is gone
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(!notification, "Test 11b, Should not have a click-to-play notification");
 
-  Services.obs.addObserver(test11c, "PopupNotifications-updateNotShowing", false);
   gTestBrowser.contentWindow.history.back();
-}
 
-// Tests that the going back will reshow the notification for click-to-play plugins (part 3/4)
-function test11c() {
-  Services.obs.removeObserver(test11c, "PopupNotifications-updateNotShowing");
-  var condition = function() PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  waitForCondition(condition, test11d, "Test 11c, waited too long for click-to-play-plugin notification");
-}
-
-// Tests that the going back will reshow the notification for click-to-play plugins (part 4/4)
-function test11d() {
-  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "Test 11d, Should have a click-to-play notification");
-
-  prepareTest(runAfterPluginBindingAttached(test12a), gHttpTestRoot + "plugin_clickToPlayAllow.html");
-}
+  yield promisePopupNotification("click-to-play-plugins");
+});
 
 // Tests that the "Allow Always" permission works for click-to-play plugins
-function test12a() {
-  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "Test 12a, Should have a click-to-play notification");
-  var plugin = gTestBrowser.contentDocument.getElementById("test");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 12a, Plugin should not be activated");
+add_task(function* () {
+  updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  yield promisePopupNotification("click-to-play-plugins");
+
+  let pluginInfo = yield promiseForPluginInfo("test");
+  ok(!pluginInfo.activated, "Test 12a, Plugin should not be activated");
 
   // Simulate clicking the "Allow Always" button.
-  popupNotification.reshow();
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+
+  yield promiseForNotificationShown(notification);
+
   PopupNotifications.panel.firstChild._primaryButton.click();
 
-  var condition = function() objLoadingContent.activated;
-  waitForCondition(condition, test12b, "Test 12a, Waited too long for plugin to activate");
-}
-
-function test12b() {
-  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "Test 12d, Should have a click-to-play notification");
-  prepareTest(runAfterPluginBindingAttached(test12c), gHttpTestRoot + "plugin_two_types.html");
-}
+  pluginInfo = yield promiseForPluginInfo("test");
+  ok(pluginInfo.activated, "Test 12a, Plugin should be activated");
+});
 
 // Test that the "Always" permission, when set for just the Test plugin,
 // does not also allow the Second Test plugin.
-function test12c() {
-  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "Test 12d, Should have a click-to-play notification");
-  var test = gTestBrowser.contentDocument.getElementById("test");
-  var secondtestA = gTestBrowser.contentDocument.getElementById("secondtestA");
-  var secondtestB = gTestBrowser.contentDocument.getElementById("secondtestB");
-  ok(test.activated, "Test 12d, Test plugin should be activated");
-  ok(!secondtestA.activated, "Test 12d, Second Test plugin (A) should not be activated");
-  ok(!secondtestB.activated, "Test 12d, Second Test plugin (B) should not be activated");
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  yield promisePopupNotification("click-to-play-plugins");
+
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let test = content.document.getElementById("test");
+    let secondtestA = content.document.getElementById("secondtestA");
+    let secondtestB = content.document.getElementById("secondtestB");
+    return test.activated && !secondtestA.activated && !secondtestB.activated;
+  });
+  ok(result, "Content plugins are set up");
 
   clearAllPluginPermissions();
-  var plugin = getTestPlugin();
-  plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
-  prepareTest(test14, gTestRoot + "plugin_test2.html");
-}
+});
 
-// Test 13 removed
+// Tests that the plugin's "activated" property is true for working plugins
+// with click-to-play disabled.
+add_task(function* () {
+  updateAllTestPlugins(Ci.nsIPluginTag.STATE_ENABLED);
 
-// 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");
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test2.html");
 
-  var plugin = getTestPlugin();
-  plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
-  getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
-  prepareTest(runAfterPluginBindingAttached(test15), gTestRoot + "plugin_alternate_content.html");
-}
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let pluginInfo = yield promiseForPluginInfo("test1");
+  ok(pluginInfo.activated, "Test 14, Plugin should be activated");
+});
 
 // 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;
-  var mainBox = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(mainBox, "Test 15, Plugin with id=" + plugin.id + " overlay should exist");
+// plugins are click to play.
+add_task(function* () {
+  updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_alternate_content.html");
 
-  prepareTest(runAfterPluginBindingAttached(test17), gTestRoot + "plugin_bug749455.html");
-}
-
-// Test 16 removed
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let mainBox = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return !!mainBox;
+  });
+  ok(result, "Test 15, Plugin overlay should exist");
+});
 
 // Tests that mContentType is used for click-to-play plugins, and not the
 // inspected type.
-function test17() {
-  var clickToPlayNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(clickToPlayNotification, "Test 17, Should have a click-to-play notification");
-
-  setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableUpdatable.xml",
-  function() {
-    prepareTest(runAfterPluginBindingAttached(test18a), gHttpTestRoot + "plugin_test.html");
-  });
-}
-
-// Tests a vulnerable, updatable plugin
-function test18a() {
-  var clickToPlayNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(clickToPlayNotification, "Test 18a, Should have a click-to-play notification");
-  var doc = gTestBrowser.contentDocument;
-  var plugin = doc.getElementById("test");
-  ok(plugin, "Test 18a, Found plugin in page");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "Test 18a, plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE");
-  ok(!objLoadingContent.activated, "Test 18a, Plugin should not be activated");
-  var overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(overlay.classList.contains("visible"), "Test 18a, Plugin overlay should exist, not be hidden");
-  var updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink");
-  ok(updateLink.style.visibility != "hidden", "Test 18a, Plugin should have an update link");
-
-  var pluginUpdateURL = Services.urlFormatter.formatURLPref("plugins.update.url");
-  var tabOpenListener = new TabOpenListener(pluginUpdateURL, function(tab) {
-    gBrowser.removeTab(tab);
-  }, test18b);
-  EventUtils.synthesizeMouseAtCenter(updateLink, {}, gTestBrowser.contentWindow);
-}
-
-function test18b() {
-  // clicking the update link should not activate the plugin
-  var doc = gTestBrowser.contentDocument;
-  var plugin = doc.getElementById("test");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 18b, Plugin should not be activated");
-  var overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(overlay.classList.contains("visible"), "Test 18b, Plugin overlay should exist, not be hidden");
-
-  setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableNoUpdate.xml",
-  function() {
-    prepareTest(runAfterPluginBindingAttached(test18c), gHttpTestRoot + "plugin_test.html");
-  });
-}
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_bug749455.html");
 
-// Tests a vulnerable plugin with no update
-function test18c() {
-  var clickToPlayNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(clickToPlayNotification, "Test 18c, Should have a click-to-play notification");
-  var doc = gTestBrowser.contentDocument;
-  var plugin = doc.getElementById("test");
-  ok(plugin, "Test 18c, Found plugin in page");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE, "Test 18c, plugin fallback type should be PLUGIN_VULNERABLE_NO_UPDATE");
-  ok(!objLoadingContent.activated, "Test 18c, Plugin should not be activated");
-  var overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(overlay.classList.contains("visible"), "Test 18c, Plugin overlay should exist, not be hidden");
-  var updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink");
-  ok(updateLink.style.display != "block", "Test 18c, Plugin should not have an update link");
-
-  // check that click "Always allow" works with blocklisted plugins
-  clickToPlayNotification.reshow();
-  PopupNotifications.panel.firstChild._primaryButton.click();
-
-  var condition = function() objLoadingContent.activated;
-  waitForCondition(condition, test18d, "Test 18d, Waited too long for plugin to activate");
-}
-
-// continue testing "Always allow"
-function test18d() {
-  var plugin = gTestBrowser.contentDocument.getElementById("test");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(objLoadingContent.activated, "Test 18d, Plugin should be activated");
-
-  prepareTest(test18e, gHttpTestRoot + "plugin_test.html");
-}
-
-// continue testing "Always allow"
-function test18e() {
-  var plugin = gTestBrowser.contentDocument.getElementById("test");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(objLoadingContent.activated, "Test 18e, Plugin should be activated");
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-  clearAllPluginPermissions();
-  prepareTest(runAfterPluginBindingAttached(test18f), gHttpTestRoot + "plugin_test.html");
-}
-
-// clicking the in-content overlay of a vulnerable plugin should bring
-// up the notification and not directly activate the plugin
-function test18f() {
-  var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(notification, "Test 18f, Should have a click-to-play notification");
-  ok(notification.dismissed, "Test 18f, notification should start dismissed");
-  var plugin = gTestBrowser.contentDocument.getElementById("test");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 18f, Plugin should not be activated");
-
-  var oldEventCallback = notification.options.eventCallback;
-  notification.options.eventCallback = function() {
-    oldEventCallback();
-    executeSoon(test18g);
-  };
-  EventUtils.synthesizeMouseAtCenter(plugin, {}, gTestBrowser.contentWindow);
-}
-
-function test18g() {
-  var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(notification, "Test 18g, Should have a click-to-play notification");
-  ok(!notification.dismissed, "Test 18g, notification should be open");
-  notification.options.eventCallback = null;
-  var plugin = gTestBrowser.contentDocument.getElementById("test");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 18g, Plugin should not be activated");
-
-  setAndUpdateBlocklist(gHttpTestRoot + "blockNoPlugins.xml",
-  function() {
-    resetBlocklist();
-    prepareTest(runAfterPluginBindingAttached(test19a), gTestRoot + "plugin_test.html");
-  });
-}
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 17, Should have a click-to-play notification");
+});
 
 // Tests that clicking the icon of the overlay activates the doorhanger
-function test19a() {
-  var doc = gTestBrowser.contentDocument;
-  var plugin = doc.getElementById("test");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 19a, Plugin should not be activated");
-  ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed, "Test 19a, Doorhanger should start out dismissed");
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let pluginInfo = yield promiseForPluginInfo("test");
+  ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated");
+
+  ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed,
+     "Test 19a, Doorhanger should start out dismissed");
 
-  var icon = doc.getAnonymousElementByAttribute(plugin, "class", "icon");
-  EventUtils.synthesizeMouseAtCenter(icon, {}, gTestBrowser.contentWindow);
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let icon = doc.getAnonymousElementByAttribute(plugin, "class", "icon");
+    let bounds = icon.getBoundingClientRect();
+    let left = (bounds.left + bounds.right) / 2;
+    let top = (bounds.top + bounds.bottom) / 2;
+    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+  });
+
   let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed;
-  waitForCondition(condition, test19b, "Test 19a, Waited too long for doorhanger to activate");
-}
-
-function test19b() {
-  prepareTest(runAfterPluginBindingAttached(test19c), gTestRoot + "plugin_test.html");
-}
+  yield promiseForCondition(condition);
+});
 
 // Tests that clicking the text of the overlay activates the plugin
-function test19c() {
-  var doc = gTestBrowser.contentDocument;
-  var plugin = doc.getElementById("test");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 19c, Plugin should not be activated");
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-  ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed, "Test 19c, Doorhanger should start out dismissed");
+  let pluginInfo = yield promiseForPluginInfo("test");
+  ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated");
+
+  ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed,
+     "Test 19c, Doorhanger should start out dismissed");
 
-  var text = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgClickToPlay");
-  EventUtils.synthesizeMouseAtCenter(text, {}, gTestBrowser.contentWindow);
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let text = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgClickToPlay");
+    let bounds = text.getBoundingClientRect();
+    let left = (bounds.left + bounds.right) / 2;
+    let top = (bounds.top + bounds.bottom) / 2;
+    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+  });
+
   let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed;
-  waitForCondition(condition, test19d, "Test 19c, Waited too long for doorhanger to activate");
-}
-
-function test19d() {
-  prepareTest(runAfterPluginBindingAttached(test19e), gTestRoot + "plugin_test.html");
-}
+  yield promiseForCondition(condition);
+});
 
 // Tests that clicking the box of the overlay activates the doorhanger
 // (just to be thorough)
-function test19e() {
-  var doc = gTestBrowser.contentDocument;
-  var plugin = doc.getElementById("test");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 19e, Plugin should not be activated");
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-  ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed, "Test 19e, Doorhanger should start out dismissed");
+  let pluginInfo = yield promiseForPluginInfo("test");
+  ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated");
+
+  ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed,
+     "Test 19e, Doorhanger should start out dismissed");
 
-  EventUtils.synthesizeMouse(plugin, 50, 50, {}, gTestBrowser.contentWindow);
-  let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed;
-  waitForCondition(condition, test19f, "Test 19e, Waited too long for plugin to activate");
-}
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("mousedown", 50, 50, 0, 1, 0, false, 0, 0);
+    utils.sendMouseEvent("mouseup", 50, 50, 0, 1, 0, false, 0, 0);
+  });
 
-function test19f() {
-  prepareTest(test20a, gTestRoot + "plugin_hidden_to_visible.html");
-}
+  let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
+    PopupNotifications.panel.firstChild;
+  yield promiseForCondition(condition);
+  PopupNotifications.panel.firstChild._primaryButton.click();
+
+  pluginInfo = yield promiseForPluginInfo("test");
+  ok(pluginInfo.activated, "Test 19e, Plugin should not be activated");
+
+  clearAllPluginPermissions();
+});
 
 // Tests that a plugin in a div that goes from style="display: none" to
 // "display: block" can be clicked to activate.
-function test20a() {
-  var clickToPlayNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(!clickToPlayNotification, "Test 20a, Should not have a click-to-play notification");
-  var doc = gTestBrowser.contentDocument;
-  var plugin = doc.getElementById("plugin");
-  var mainBox = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
-  ok(mainBox, "Test 20a, plugin overlay should not be null");
-  var pluginRect = mainBox.getBoundingClientRect();
-  ok(pluginRect.width == 0, "Test 20a, plugin should have an overlay with 0px width");
-  ok(pluginRect.height == 0, "Test 20a, plugin should have an overlay with 0px height");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 20a, plugin should not be activated");
-  var div = doc.getElementById("container");
-  ok(div.style.display == "none", "Test 20a, container div should be display: none");
+add_task(function* () {
+  updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_hidden_to_visible.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 20a, Should have a click-to-play notification");
+
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    return !!overlay;
+  });
+  ok(result, "Test 20a, Plugin overlay should exist");
 
-  div.style.display = "block";
-  var condition = function() {
-    var pluginRect = mainBox.getBoundingClientRect();
-    return (pluginRect.width == 200);
-  }
-  waitForCondition(condition, test20b, "Test 20a, Waited too long for plugin to become visible");
-}
+  result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let mainBox = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    let overlayRect = mainBox.getBoundingClientRect();
+    return overlayRect.width == 0 && overlayRect.height == 0;
+  });
+  ok(result, "Test 20a, plugin should have an overlay with 0px width and height");
+
+  let pluginInfo = yield promiseForPluginInfo("test");
+  ok(!pluginInfo.activated, "Test 20b, plugin should not be activated");
+
+  result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let div = doc.getElementById("container");
+    return div.style.display == "none";
+  });
+  ok(result, "Test 20b, container div should be display: none");
 
-function test20b() {
-  var clickToPlayNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(clickToPlayNotification, "Test 20b, Should now have a click-to-play notification");
-  var doc = gTestBrowser.contentDocument;
-  var plugin = doc.getElementById("plugin");
-  var pluginRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
-  ok(pluginRect.width == 200, "Test 20b, plugin should have an overlay with 200px width");
-  ok(pluginRect.height == 200, "Test 20b, plugin should have an overlay with 200px height");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Test 20b, plugin should not be activated");
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let div = doc.getElementById("container");
+    div.style.display = "block";
+  });
 
-  ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed, "Test 20b, Doorhanger should start out dismissed");
+  result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let mainBox = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+    let overlayRect = mainBox.getBoundingClientRect();
+    return overlayRect.width == 200 && overlayRect.height == 200;
+  });
+  ok(result, "Test 20b, Waited too long for plugin to become visible");
+
+  pluginInfo = yield promiseForPluginInfo("test");
+  ok(!pluginInfo.activated, "Test 20b, plugin should not be activated");
+
+  ok(notification.dismissed, "Test 20c, Doorhanger should start out dismissed");
 
-  EventUtils.synthesizeMouseAtCenter(plugin, {}, gTestBrowser.contentWindow);
-  let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed;
-  waitForCondition(condition, test20c, "Test 20b, Waited too long for plugin to activate");
-}
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let bounds = plugin.getBoundingClientRect();
+    let left = (bounds.left + bounds.right) / 2;
+    let top = (bounds.top + bounds.bottom) / 2;
+    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+  });
 
-function test20c() {
+  let condition = function() !notification.dismissed && !!PopupNotifications.panel.firstChild;
+  yield promiseForCondition(condition);
   PopupNotifications.panel.firstChild._primaryButton.click();
-  var doc = gTestBrowser.contentDocument;
-  var plugin = doc.getElementById("plugin");
-  let condition = function() plugin.activated;
-  waitForCondition(condition, test20d, "Test 20c", "Waiting for plugin to activate");
-}
+
+  pluginInfo = yield promiseForPluginInfo("test");
+  ok(pluginInfo.activated, "Test 20c, plugin should be activated");
 
-function test20d() {
-  var doc = gTestBrowser.contentDocument;
-  var plugin = doc.getElementById("plugin");
-  var pluginRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
-  ok(pluginRect.width == 0, "Test 20d, plugin should have click-to-play overlay with zero width");
-  ok(pluginRect.height == 0, "Test 20d, plugin should have click-to-play overlay with zero height");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(objLoadingContent.activated, "Test 20d, plugin should be activated");
+  result = ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+    return overlayRect.width == 0 && overlayRect.height == 0;
+  });
+  ok(result, "Test 20c, plugin should have overlay dims of 200px");
 
   clearAllPluginPermissions();
-
-  prepareTest(runAfterPluginBindingAttached(test21a), gTestRoot + "plugin_two_types.html");
-}
+});
 
 // Test having multiple different types of plugin on one page
-function test21a() {
-  var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+add_task(function* () {
+  // contains three plugins, application/x-test, application/x-second-test x 2
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html");
+
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(notification, "Test 21a, Should have a click-to-play notification");
 
-  var doc = gTestBrowser.contentDocument;
-  var ids = ["test", "secondtestA", "secondtestB"];
-  for (var id of ids) {
-    var plugin = doc.getElementById(id);
-    var rect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
-    ok(rect.width == 200, "Test 21a, Plugin with id=" + plugin.id + " overlay rect should have 200px width before being clicked");
-    ok(rect.height == 200, "Test 21a, Plugin with id=" + plugin.id + " overlay rect should have 200px height before being clicked");
-    var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-    ok(!objLoadingContent.activated, "Test 21a, Plugin with id=" + plugin.id + " should not be activated");
+  // confirm all three are blocked by ctp at this point
+  let ids = ["test", "secondtestA", "secondtestB"];
+  for (let id of ids) {
+    let result = yield ContentTask.spawn(gTestBrowser, {aId: id}, function* () {
+      let doc = content.document;
+      let plugin = doc.getElementById(arguments[0].aId);
+      let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+      return overlayRect.width == 200 && overlayRect.height == 200;
+    });
+    ok(result, "Test 21a, plugin " + id + " should have click-to-play overlay with dims");
+
+    let pluginInfo = yield promiseForPluginInfo(id);
+    ok(!pluginInfo.activated, "Test 21a, Plugin with id=" + id + " should not be activated");
   }
 
+  notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 21a, Should have a click-to-play notification");
+
   // we have to actually show the panel to get the bindings to instantiate
-  notification.reshow();
+  yield promiseForNotificationShown(notification);
+
   is(notification.options.pluginData.size, 2, "Test 21a, Should have two types of plugin in the notification");
 
-  var centerAction = null;
-  for (var action of notification.options.pluginData.values()) {
+  let centerAction = null;
+  for (let action of notification.options.pluginData.values()) {
     if (action.pluginName == "Test") {
       centerAction = action;
       break;
     }
   }
   ok(centerAction, "Test 21b, found center action for the Test plugin");
 
-  var centerItem = null;
-  for (var item of PopupNotifications.panel.firstChild.childNodes) {
+  let centerItem = null;
+  for (let item of PopupNotifications.panel.firstChild.childNodes) {
     is(item.value, "block", "Test 21b, all plugins should start out blocked");
     if (item.action == centerAction) {
       centerItem = item;
       break;
     }
   }
   ok(centerItem, "Test 21b, found center item for the Test plugin");
 
+  // Select the allow now option in the select drop down for Test Plugin
+  centerItem.value = "allownow";
+
   // "click" the button to activate the Test plugin
-  centerItem.value = "allownow";
   PopupNotifications.panel.firstChild._primaryButton.click();
 
-  var doc = gTestBrowser.contentDocument;
-  var plugin = doc.getElementById("test");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  var condition = function() objLoadingContent.activated;
-  waitForCondition(condition, test21c, "Test 21b, Waited too long for plugin to activate");
-}
+  let pluginInfo = yield promiseForPluginInfo("test");
+  ok(pluginInfo.activated, "Test 21b, plugin should be activated");
 
-function test21c() {
-  var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(notification, "Test 21c, Should have a click-to-play notification");
+  notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 21b, Should have a click-to-play notification");
 
-  notification.reshow();
+  yield promiseForNotificationShown(notification);
+
   ok(notification.options.pluginData.size == 2, "Test 21c, Should have one type of plugin in the notification");
 
-  var doc = gTestBrowser.contentDocument;
-  var plugin = doc.getElementById("test");
-  var rect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
-  ok(rect.width == 0, "Test 21c, Plugin with id=" + plugin.id + " overlay rect should have 0px width after being clicked");
-  ok(rect.height == 0, "Test 21c, Plugin with id=" + plugin.id + " overlay rect should have 0px height after being clicked");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(objLoadingContent.activated, "Test 21c, Plugin with id=" + plugin.id + " should be activated");
+  let result = ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+    return overlayRect.width == 0 && overlayRect.height == 0;
+  });
+  ok(result, "Test 21c, plugin should have overlay dims of 0px");
 
-  var ids = ["secondtestA", "secondtestB"];
-  for (var id of ids) {
-    var plugin = doc.getElementById(id);
-    var rect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
-    ok(rect.width == 200, "Test 21c, Plugin with id=" + plugin.id + " overlay rect should have 200px width before being clicked");
-    ok(rect.height == 200, "Test 21c, Plugin with id=" + plugin.id + " overlay rect should have 200px height before being clicked");
-    var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-    ok(!objLoadingContent.activated, "Test 21c, Plugin with id=" + plugin.id + " should not be activated");
+  ids = ["secondtestA", "secondtestB"];
+  for (let id of ids) {
+    let result = yield ContentTask.spawn(gTestBrowser, {aId: id}, function* () {
+      let doc = content.document;
+      let plugin = doc.getElementById(arguments[0].aId);
+      let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+      return overlayRect.width == 200 && overlayRect.height == 200;
+    });
+    ok(result, "Test 21c, plugin " + id + " should have click-to-play overlay with zero dims");
+
+    let pluginInfo = yield promiseForPluginInfo(id);
+    ok(!pluginInfo.activated, "Test 21c, Plugin with id=" + id + " should not be activated");
   }
 
-  var centerAction = null;
-  for (var action of notification.options.pluginData.values()) {
+  centerAction = null;
+  for (let action of notification.options.pluginData.values()) {
     if (action.pluginName == "Second Test") {
       centerAction = action;
       break;
     }
   }
   ok(centerAction, "Test 21d, found center action for the Second Test plugin");
 
-  var centerItem = null;
-  for (var item of PopupNotifications.panel.firstChild.childNodes) {
+  centerItem = null;
+  for (let item of PopupNotifications.panel.firstChild.childNodes) {
     if (item.action == centerAction) {
       is(item.value, "block", "Test 21d, test plugin 2 should start blocked");
       centerItem = item;
       break;
     }
     else {
       is(item.value, "allownow", "Test 21d, test plugin should be enabled");
     }
   }
   ok(centerItem, "Test 21d, found center item for the Second Test plugin");
 
+  // Select the allow now option in the select drop down for Second Test Plguins
+  centerItem.value = "allownow";
+
   // "click" the button to activate the Second Test plugins
-  centerItem.value = "allownow";
   PopupNotifications.panel.firstChild._primaryButton.click();
 
-  var doc = gTestBrowser.contentDocument;
-  var plugin = doc.getElementById("secondtestA");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  var condition = function() objLoadingContent.activated;
-  waitForCondition(condition, test21e, "Test 21d, Waited too long for plugin to activate");
-}
+  notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 21d, Should have a click-to-play notification");
 
-function test21e() {
-  var doc = gTestBrowser.contentDocument;
-  var ids = ["test", "secondtestA", "secondtestB"];
-  for (var id of ids) {
-    var plugin = doc.getElementById(id);
-    var rect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").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");
+  ids = ["test", "secondtestA", "secondtestB"];
+  for (let id of ids) {
+    let result = yield ContentTask.spawn(gTestBrowser, {aId: id}, function* () {
+      let doc = content.document;
+      let plugin = doc.getElementById(arguments[0].aId);
+      let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+      return overlayRect.width == 0 && overlayRect.height == 0;
+    });
+    ok(result, "Test 21d, plugin " + id + " should have click-to-play overlay with zero dims");
+
+    let pluginInfo = yield promiseForPluginInfo(id);
+    ok(pluginInfo.activated, "Test 21d, Plugin with id=" + id + " should not be activated");
   }
+});
 
-  getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
-  getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
-
+// Tests that a click-to-play plugin resets its activated state when changing types
+add_task(function* () {
   clearAllPluginPermissions();
 
-  prepareTest(runAfterPluginBindingAttached(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");
+  updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
 
-  // Plugin should start as CTP
-  var pluginNode = gTestBrowser.contentDocument.getElementById("test");
-  ok(pluginNode, "Test 22, Found plugin in page");
-  var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 22, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
-
-  // Activate
-  objLoadingContent.playPlugin();
-  is(objLoadingContent.displayedType, Ci.nsIObjectLoadingContent.TYPE_PLUGIN, "Test 22, plugin should have started");
-  ok(pluginNode.activated, "Test 22, plugin should be activated");
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
 
-  // Spin event loop for plugin to finish spawning
-  executeSoon(function() {
-    var oldVal = pluginNode.getObjectValue();
-    pluginNode.src = pluginNode.src;
-    is(objLoadingContent.displayedType, Ci.nsIObjectLoadingContent.TYPE_PLUGIN, "Test 22, Plugin should have retained activated state");
-    ok(pluginNode.activated, "Test 22, plugin should have remained activated");
-    // Sanity, ensure that we actually reloaded the instance, since this behavior might change in the future.
-    var pluginsDiffer;
-    try {
-      pluginNode.checkObjectValue(oldVal);
-    } catch (e) {
-      pluginsDiffer = true;
-    }
-    ok(pluginsDiffer, "Test 22, plugin should have reloaded");
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
 
-    prepareTest(runAfterPluginBindingAttached(test23), gTestRoot + "plugin_test.html");
-  });
-}
-
-// Tests that a click-to-play plugin resets its activated state when changing types
-function test23() {
-  ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser), "Test 23, Should have a click-to-play notification");
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 22, Should have a click-to-play notification");
 
   // Plugin should start as CTP
-  var pluginNode = gTestBrowser.contentDocument.getElementById("test");
-  ok(pluginNode, "Test 23, Found plugin in page");
-  var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 23, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
-
-  // Activate
-  objLoadingContent.playPlugin();
-  is(objLoadingContent.displayedType, Ci.nsIObjectLoadingContent.TYPE_PLUGIN, "Test 23, plugin should have started");
-  ok(pluginNode.activated, "Test 23, plugin should be activated");
-
-  // Reload plugin (this may need RunSoon() in the future when plugins change state asynchronously)
-  pluginNode.type = null;
-  // We currently don't properly change state just on type change,
-  // so rebind the plugin to tree. bug 767631
-  pluginNode.parentNode.appendChild(pluginNode);
-  is(objLoadingContent.displayedType, Ci.nsIObjectLoadingContent.TYPE_NULL, "Test 23, plugin should be unloaded");
-  pluginNode.type = "application/x-test";
-  pluginNode.parentNode.appendChild(pluginNode);
-  is(objLoadingContent.displayedType, Ci.nsIObjectLoadingContent.TYPE_NULL, "Test 23, Plugin should not have activated");
-  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 23, Plugin should be click-to-play");
-  ok(!pluginNode.activated, "Test 23, plugin node should not be activated");
+  let pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+     "Test 23, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
 
-  prepareTest(runAfterPluginBindingAttached(test24a), gHttpTestRoot + "plugin_test.html");
-}
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    plugin.type = null;
+    // We currently don't properly change state just on type change,
+    // so rebind the plugin to tree. bug 767631
+    plugin.parentNode.appendChild(plugin);
+  });
 
-// Test that "always allow"-ing a plugin will not allow it when it becomes
-// blocklisted.
-function test24a() {
-  var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(notification, "Test 24a, Should have a click-to-play notification");
-  var plugin = gTestBrowser.contentDocument.getElementById("test");
-  ok(plugin, "Test 24a, Found plugin in page");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 24a, Plugin should be click-to-play");
-  ok(!objLoadingContent.activated, "Test 24a, plugin should not be activated");
-
-  // simulate "always allow"
-  notification.reshow();
-  PopupNotifications.panel.firstChild._primaryButton.click();
-  waitForCondition(() => objLoadingContent.activated, () => {
-    prepareTest(test24b, gHttpTestRoot + "plugin_test.html");
-  }, "Test 24a, plugin should now be activated.");
-
-}
+  pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_NULL, "Test 23, plugin should be TYPE_NULL");
 
-// did the "always allow" work as intended?
-function test24b() {
-  var plugin = gTestBrowser.contentDocument.getElementById("test");
-  ok(plugin, "Test 24b, Found plugin in page");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  waitForCondition(() => objLoadingContent.activated, () => {
-    setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableUpdatable.xml", () => {
-      prepareTest(runAfterPluginBindingAttached(test24c), gHttpTestRoot + "plugin_test.html");
-    });
-  }, "Test 24b, plugin should be activated");
-}
+  let result = yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    let doc = content.document;
+    let plugin = doc.getElementById("test");
+    plugin.type = "application/x-test";
+    plugin.parentNode.appendChild(plugin);
+  });
 
-// the plugin is now blocklisted, so it should not automatically load
-function test24c() {
-  var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(notification, "Test 24c, Should have a click-to-play notification");
-  var plugin = gTestBrowser.contentDocument.getElementById("test");
-  ok(plugin, "Test 24c, Found plugin in page");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "Test 24c, Plugin should be vulnerable/updatable");
-  waitForCondition(() => !objLoadingContent.activated, () => {
-    // simulate "always allow"
-    notification.reshow();
-    PopupNotifications.panel.firstChild._primaryButton.click();
-
-    prepareTest(test24d, gHttpTestRoot + "plugin_test.html");
-  }, "Test 24c, plugin should not be activated");
-}
+  pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_NULL, "Test 23, plugin should be TYPE_NULL");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+     "Test 23, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+  ok(!pluginInfo.activated, "Test 23, plugin node should not be activated");
+});
 
-// We should still be able to always allow a plugin after we've seen that it's
-// blocklisted.
-function test24d() {
-  var plugin = gTestBrowser.contentDocument.getElementById("test");
-  ok(plugin, "Test 24d, Found plugin in page");
-  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  waitForCondition(() => objLoadingContent.activated, () => {
-    // this resets the vulnerable plugin permission
-    setAndUpdateBlocklist(gHttpTestRoot + "blockNoPlugins.xml", () => {
-      clearAllPluginPermissions();
-      resetBlocklist();
-      prepareTest(test25, gTestRoot + "plugin_syncRemoved.html");
-    });
-  }, "Test 24d, plugin should be activated");
-}
+// Plugin sync removal test. Note this test produces a notification drop down since
+// the plugin we add has zero dims.
+add_task(function* () {
+  updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
 
-function test25() {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_syncRemoved.html");
+
+  // Maybe there some better trick here, we need to wait for the page load, then
+  // wait for the js to execute in the page.
+  yield waitForMs(500);
+
   let notification = PopupNotifications.getNotification("click-to-play-plugins");
   ok(notification, "Test 25: There should be a plugin notification even if the plugin was immediately removed");
   ok(notification.dismissed, "Test 25: The notification should be dismissed by default");
 
-  prepareTest26();
-}
+  yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>");
+});
+
+// Tests a page with a blocked plugin in it and make sure the infoURL property
+// the blocklist file gets used.
+add_task(function* () {
+  clearAllPluginPermissions();
 
-function prepareTest26() {
-  info("prepareTest26");
-  let plugin = getTestPlugin();
-  plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
-  setAndUpdateBlocklist(gHttpTestRoot + "blockPluginInfoURL.xml",
-    function() {
-      info("prepareTest26 callback");
-      prepareTest(runAfterPluginBindingAttached(test26), gTestRoot + "plugin_test.html");
-  });
-}
+  yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginInfoURL.xml", gTestBrowser);
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
 
-// Tests a page with a blocked plugin in it and make sure the
-// infoURL property from the blocklist file gets used.
-function test26() {
-  info("test26 - Test infoURL");
+  // Work around for delayed PluginBindingAttached
+  yield promiseUpdatePluginBindings(gTestBrowser);
+
   let notification = PopupNotifications.getNotification("click-to-play-plugins");
 
   // Since the plugin notification is dismissed by default, reshow it.
-  notification.reshow();
+  yield promiseForNotificationShown(notification);
 
-  let pluginNode = gTestBrowser.contentDocument.getElementById("test");
-  ok(pluginNode, "Test 26, Found plugin in page");
-  let objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
-  is(objLoadingContent.pluginFallbackType,
-     Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED,
+  let pluginInfo = yield promiseForPluginInfo("test");
+  is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED,
      "Test 26, plugin fallback type should be PLUGIN_BLOCKLISTED");
+  ok(!pluginInfo.activated, "Plugin should be activated.");
 
   const testUrl = "http://test.url.com/";
 
-  let doc = gTestBrowser.contentDocument;
   let firstPanelChild = PopupNotifications.panel.firstChild;
-
-  let infoLink = doc.getAnonymousElementByAttribute(
-    firstPanelChild, "anonid", "click-to-play-plugins-notification-link");
-
+  let infoLink = document.getAnonymousElementByAttribute(firstPanelChild, "anonid",
+    "click-to-play-plugins-notification-link");
   is(infoLink.href, testUrl,
     "Test 26, the notification URL needs to match the infoURL from the blocklist file.");
-
-  finishTest();
-}
+});
--- a/browser/base/content/test/plugins/browser_pluginplaypreview.js
+++ b/browser/base/content/test/plugins/browser_pluginplaypreview.js
@@ -1,12 +1,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/. */
-
 var rootDir = getRootDirectory(gTestPath);
 const gTestRoot = rootDir;
 
 var gTestBrowser = null;
 var gNextTest = null;
 var gNextTestSkip = 0;
 var gPlayPreviewPluginActualEvents = 0;
 var gPlayPreviewPluginExpectedEvents = 1;
--- a/browser/base/content/test/plugins/browser_pluginplaypreview2.js
+++ b/browser/base/content/test/plugins/browser_pluginplaypreview2.js
@@ -1,12 +1,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/. */
-
 var rootDir = getRootDirectory(gTestPath);
 const gTestRoot = rootDir;
 
 var gTestBrowser = null;
 var gNextTest = null;
 var gNextTestSkip = 0;
 var gPlayPreviewPluginActualEvents = 0;
 var gPlayPreviewPluginExpectedEvents = 1;
--- a/browser/base/content/test/plugins/browser_pluginplaypreview3.js
+++ b/browser/base/content/test/plugins/browser_pluginplaypreview3.js
@@ -1,12 +1,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/. */
-
 var rootDir = getRootDirectory(gTestPath);
 const gTestRoot = rootDir;
 
 var gTestBrowser = null;
 var gNextTest = null;
 var gNextTestSkip = 0;
 
 var gPlayPreviewRegistration = null;
--- a/browser/base/content/test/plugins/browser_plugins_added_dynamically.js
+++ b/browser/base/content/test/plugins/browser_plugins_added_dynamically.js
@@ -1,178 +1,137 @@
 var rootDir = getRootDirectory(gTestPath);
 const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://mochi.test:8888/");
-
-let gTestBrowser = null;
-let gNextTest = null;
 let gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+let gTestBrowser = null;
 
-// Let's do the XPCNativeWrapper dance!
-function addPlugin(browser, type) {
-  let contentWindow = XPCNativeWrapper.unwrap(browser.contentWindow);
-  contentWindow.addPlugin(type);
-}
-
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
+add_task(function* () {
+  registerCleanupFunction(Task.async(function*() {
     clearAllPluginPermissions();
     Services.prefs.clearUserPref("plugins.click_to_play");
-  });
-  Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+    yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+    resetBlocklist();
+    gBrowser.removeCurrentTab();
+    window.focus();
+    gTestBrowser = null;
+  }));
+});
 
+// "Activate" of a given type -> plugins of that type dynamically added should
+// automatically play.
+add_task(function* () {
   let newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
   gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
-  prepareTest(testActivateAddSameTypePart1, gTestRoot + "plugin_add_dynamically.html");
-}
 
-function finishTest() {
-  gTestBrowser.removeEventListener("load", pageLoad, true);
-  gBrowser.removeCurrentTab();
-  window.focus();
-  finish();
-}
+  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
 
-function pageLoad() {
-  // The plugin events are async dispatched and can come after the load event
-  // This just allows the events to fire before we then go on to test the states
-  executeSoon(gNextTest);
-}
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+});
 
-function prepareTest(nextTest, url) {
-  gNextTest = nextTest;
-  gTestBrowser.contentWindow.location = url;
-}
+add_task(function* () {
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_add_dynamically.html");
+
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(!notification, "Test 1a, Should not have a click-to-play notification");
 
-// "Activate" of a given type -> plugins of that type dynamically added should
-// automatically play.
-function testActivateAddSameTypePart1() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(!popupNotification, "testActivateAddSameTypePart1: should not have a click-to-play notification");
-  addPlugin(gTestBrowser);
-  let condition = function() PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  waitForCondition(condition, testActivateAddSameTypePart2, "testActivateAddSameTypePart1: waited too long for click-to-play-plugin notification");
-}
+  // Add a plugin of type test
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("pluginone", "application/x-test"));
+  });
+
+  yield promisePopupNotification("click-to-play-plugins");
 
-function testActivateAddSameTypePart2() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "testActivateAddSameTypePart2: should have a click-to-play notification");
+  notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 1a, Should not have a click-to-play notification");
 
-  popupNotification.reshow();
-  testActivateAddSameTypePart3();
-}
+  yield promiseForNotificationShown(notification);
 
-function testActivateAddSameTypePart3() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   let centerAction = null;
-  for (let action of popupNotification.options.pluginData.values()) {
+  for (let action of notification.options.pluginData.values()) {
     if (action.pluginName == "Test") {
       centerAction = action;
       break;
     }
   }
-  ok(centerAction, "testActivateAddSameTypePart3: found center action for the Test plugin");
+  ok(centerAction, "Test 2, found center action for the Test plugin");
 
   let centerItem = null;
   for (let item of PopupNotifications.panel.firstChild.childNodes) {
-    if (item.action && item.action == centerAction) {
+    is(item.value, "block", "Test 3, all plugins should start out blocked");
+    if (item.action == centerAction) {
       centerItem = item;
       break;
     }
   }
-  ok(centerItem, "testActivateAddSameTypePart3: found center item for the Test plugin");
-
-  let plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[0];
-  ok(!plugin.activated, "testActivateAddSameTypePart3: plugin should not be activated");
-
-  // Change the state and click the ok button to activate the Test plugin
-  centerItem.value = "allownow";
-  PopupNotifications.panel.firstChild._primaryButton.click();
-
-  let condition = function() plugin.activated;
-  waitForCondition(condition, testActivateAddSameTypePart4, "testActivateAddSameTypePart3: waited too long for plugin to activate");
-}
-
-function testActivateAddSameTypePart4() {
-  let plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[0];
-  ok(plugin.activated, "testActivateAddSameTypePart4: plugin should be activated");
-
-  addPlugin(gTestBrowser);
-  let condition = function() {
-    let embeds = gTestBrowser.contentDocument.getElementsByTagName("embed");
-    let allActivated = true;
-    for (let embed of embeds) {
-      if (!embed.activated)
-        allActivated = false;
-    }
-    return allActivated && embeds.length == 2;
-  };
-  waitForCondition(condition, testActivateAddSameTypePart5, "testActivateAddSameTypePart4: waited too long for second plugin to activate"); }
+  ok(centerItem, "Test 4, found center item for the Test plugin");
 
-function testActivateAddSameTypePart5() {
-  let embeds = gTestBrowser.contentDocument.getElementsByTagName("embed");
-  for (let embed of embeds) {
-    ok(embed.activated, "testActivateAddSameTypePart5: all plugins should be activated");
-  }
-  clearAllPluginPermissions();
-  prepareTest(testActivateAddDifferentTypePart1, gTestRoot + "plugin_add_dynamically.html");
-}
-
-// "Activate" of a given type -> plugins of other types dynamically added
-// should not automatically play.
-function testActivateAddDifferentTypePart1() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(!popupNotification, "testActivateAddDifferentTypePart1: should not have a click-to-play notification");
-  addPlugin(gTestBrowser);
-  let condition = function() PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  waitForCondition(condition, testActivateAddDifferentTypePart2, "testActivateAddDifferentTypePart1: waited too long for click-to-play-plugin notification");
-}
-
-function testActivateAddDifferentTypePart2() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "testActivateAddDifferentTypePart2: should have a click-to-play notification");
-
-  // we have to actually show the panel to get the bindings to instantiate
-  popupNotification.reshow();
-  testActivateAddDifferentTypePart3();
-}
-
-function testActivateAddDifferentTypePart3() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  is(popupNotification.options.pluginData.size, 1, "Should be one plugin action");
-
-  let plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[0];
-  ok(!plugin.activated, "testActivateAddDifferentTypePart3: plugin should not be activated");
+  // Select the allow now option in the select drop down for Test Plugin
+  centerItem.value = "allownow";
 
   // "click" the button to activate the Test plugin
   PopupNotifications.panel.firstChild._primaryButton.click();
 
-  let condition = function() plugin.activated;
-  waitForCondition(condition, testActivateAddDifferentTypePart4, "testActivateAddDifferentTypePart3: waited too long for plugin to activate");
-}
+  let pluginInfo = yield promiseForPluginInfo("pluginone");
+  ok(pluginInfo.activated, "Test 5, plugin should be activated");
+
+  // Add another plugin of type test
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("plugintwo", "application/x-test"));
+  });
+
+  pluginInfo = yield promiseForPluginInfo("plugintwo");
+  ok(pluginInfo.activated, "Test 6, plugins should be activated");
+});
 
-function testActivateAddDifferentTypePart4() {
-  let plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[0];
-  ok(plugin.activated, "testActivateAddDifferentTypePart4: plugin should be activated");
+// "Activate" of a given type -> plugins of other types dynamically added
+// should not automatically play.
+add_task(function* () {
+  clearAllPluginPermissions();
+
+  yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_add_dynamically.html");
 
-  addPlugin(gTestBrowser);
-  let condition = function() PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  waitForCondition(condition, testActivateAddDifferentTypePart5, "testActivateAddDifferentTypePart5: waited too long for popup notification");
-}
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(!notification, "Test 7, Should not have a click-to-play notification");
+
+  // Add a plugin of type test
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("pluginone", "application/x-test"));
+  });
+
+  yield promisePopupNotification("click-to-play-plugins");
 
-function testActivateAddDifferentTypePart5() {
-  ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser), "testActivateAddDifferentTypePart5: should have popup notification");
-  let embeds = gTestBrowser.contentDocument.getElementsByTagName("embed");
-  for (let embed of embeds) {
-    if (embed.type == "application/x-test")
-      ok(embed.activated, "testActivateAddDifferentTypePart5: Test plugin should be activated");
-    else if (embed.type == "application/x-second-test")
-      ok(!embed.activated, "testActivateAddDifferentTypePart5: Second Test plugin should not be activated");
-  }
+  notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 8, Should not have a click-to-play notification");
+
+  yield promiseForNotificationShown(notification);
+
+  is(notification.options.pluginData.size, 1, "Should be one plugin action");
+
+  let pluginInfo = yield promiseForPluginInfo("pluginone");
+  ok(!pluginInfo.activated, "Test 8, test plugin should be activated");
+
+  let condition = function() !notification.dismissed &&
+    PopupNotifications.panel.firstChild;
+  yield promiseForCondition(condition);
 
-  finishTest();
-}
+  // "click" the button to activate the Test plugin
+  PopupNotifications.panel.firstChild._primaryButton.click();
+
+  pluginInfo = yield promiseForPluginInfo("pluginone");
+  ok(pluginInfo.activated, "Test 9, test plugin should be activated");
+
+  yield ContentTask.spawn(gTestBrowser, {}, function* () {
+    new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("plugintwo", "application/x-second-test"));
+  });
+
+  yield promisePopupNotification("click-to-play-plugins");
+
+  pluginInfo = yield promiseForPluginInfo("pluginone");
+  ok(pluginInfo.activated, "Test 10, plugins should be activated");
+  pluginInfo = yield promiseForPluginInfo("plugintwo");
+  ok(!pluginInfo.activated, "Test 11, plugins should be activated");
+});
--- a/browser/base/content/test/plugins/head.js
+++ b/browser/base/content/test/plugins/head.js
@@ -2,128 +2,335 @@ Components.utils.import("resource://gre/
 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
   "resource://gre/modules/PlacesUtils.jsm");
 
-function whenDelayedStartupFinished(aWindow, aCallback) {
-  Services.obs.addObserver(function observer(aSubject, aTopic) {
-    if (aWindow == aSubject) {
-      Services.obs.removeObserver(observer, aTopic);
-      executeSoon(aCallback);
+// The blocklist shim running in the content process does not initialize at
+// start up, so it's not active until we load content that needs to do a
+// check. This helper bypasses the delay to get the svc up and running
+// immediately. Note, call this after remote content has loaded.
+function promiseInitContentBlocklistSvc(aBrowser)
+{
+  return ContentTask.spawn(aBrowser, {}, function* () {
+    try {
+      let bls = Cc["@mozilla.org/extensions/blocklist;1"]
+                          .getService(Ci.nsIBlocklistService);
+    } catch (ex) {
+      return ex.message;
     }
-  }, "browser-delayed-startup-finished", false);
+    return null;
+  });
+}
+
+/**
+  * Waits a specified number of miliseconds.
+  *
+  * Usage:
+  *    let wait = yield waitForMs(2000);
+  *    ok(wait, "2 seconds should now have elapsed");
+  *
+  * @param aMs the number of miliseconds to wait for
+  * @returns a Promise that resolves to true after the time has elapsed
+  */
+function waitForMs(aMs) {
+  let deferred = Promise.defer();
+  let startTime = Date.now();
+  setTimeout(done, aMs);
+  function done() {
+    deferred.resolve(true);
+  }
+  return deferred.promise;
 }
 
-function findChromeWindowByURI(aURI) {
-  let windows = Services.wm.getEnumerator(null);
-  while (windows.hasMoreElements()) {
-    let win = windows.getNext();
-    if (win.location.href == aURI)
-      return win;
-  }
-  return null;
+
+// DOM Promise fails for unknown reasons here, so we're using
+// resource://gre/modules/Promise.jsm.
+function waitForEvent(subject, eventName, checkFn, useCapture, useUntrusted) {
+  return new Promise((resolve, reject) => {
+    subject.addEventListener(eventName, function listener(event) {
+      try {
+        if (checkFn && !checkFn(event)) {
+          return;
+        }
+        subject.removeEventListener(eventName, listener, useCapture);
+        resolve(event);
+      } catch (ex) {
+        try {
+          subject.removeEventListener(eventName, listener, useCapture);
+        } catch (ex2) {
+          // Maybe the provided object does not support removeEventListener.
+        }
+        reject(ex);
+      }
+    }, useCapture, useUntrusted);
+  });
 }
 
-function waitForCondition(condition, nextTest, errorMsg) {
-  var tries = 0;
-  var interval = setInterval(function() {
-    if (tries >= 30) {
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ *        The tab to load into.
+ * @param [optional] url
+ *        The url to load, or the current url.
+ * @param [optional] event
+ *        The load event type to wait for.  Defaults to "load".
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url, eventType="load") {
+  let deferred = Promise.defer();
+  info("Wait tab event: " + eventType);
+
+  function handle(event) {
+    if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+        event.target.location.href == "about:blank" ||
+        (url && event.target.location.href != url)) {
+      info("Skipping spurious '" + eventType + "'' event" +
+            " for " + event.target.location.href);
+      return;
+    }
+    clearTimeout(timeout);
+    tab.linkedBrowser.removeEventListener(eventType, handle, true);
+    info("Tab event received: " + eventType);
+    deferred.resolve(event);
+  }
+
+  let timeout = setTimeout(() => {
+    tab.linkedBrowser.removeEventListener(eventType, handle, true);
+    deferred.reject(new Error("Timed out while waiting for a '" + eventType + "'' event"));
+  }, 30000);
+
+  tab.linkedBrowser.addEventListener(eventType, handle, true, true);
+  if (url) {
+    tab.linkedBrowser.loadURI(url);
+  }
+  return deferred.promise;
+}
+
+function waitForCondition(condition, nextTest, errorMsg, aTries, aWait) {
+  let tries = 0;
+  let maxTries = aTries || 100; // 100 tries
+  let maxWait = aWait || 100; // 100 msec x 100 tries = ten seconds
+  let interval = setInterval(function() {
+    if (tries >= maxTries) {
       ok(false, errorMsg);
       moveOn();
     }
-    var conditionPassed;
+    let conditionPassed;
     try {
       conditionPassed = condition();
     } catch (e) {
       ok(false, e + "\n" + e.stack);
       conditionPassed = false;
     }
     if (conditionPassed) {
       moveOn();
     }
     tries++;
-  }, 100);
-  var moveOn = function() { clearInterval(interval); nextTest(); };
+  }, maxWait);
+  let moveOn = function() { clearInterval(interval); nextTest(); };
 }
 
+// Waits for a conditional function defined by the caller to return true.
+function promiseForCondition(aConditionFn, aMessage, aTries, aWait) {
+  let deferred = Promise.defer();
+  waitForCondition(aConditionFn, deferred.resolve,
+                   (aMessage || "Condition didn't pass."),
+                   aTries, aWait);
+  return deferred.promise;
+}
+
+// Returns the chrome side nsIPluginTag for this plugin
 function getTestPlugin(aName) {
-  var pluginName = aName || "Test Plug-in";
-  var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
-  var tags = ph.getPluginTags();
+  let pluginName = aName || "Test Plug-in";
+  let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+  let tags = ph.getPluginTags();
 
   // Find the test plugin
-  for (var i = 0; i < tags.length; i++) {
+  for (let i = 0; i < tags.length; i++) {
     if (tags[i].name == pluginName)
       return tags[i];
   }
   ok(false, "Unable to find plugin");
   return null;
 }
 
-// call this to set the test plugin(s) initially expected enabled state.
-// it will automatically be reset to it's previous value after the test
-// ends
+// Set the 'enabledState' on the nsIPluginTag stored in the main or chrome
+// process.
 function setTestPluginEnabledState(newEnabledState, pluginName) {
-  var plugin = getTestPlugin(pluginName);
-  var oldEnabledState = plugin.enabledState;
+  let name = pluginName || "Test Plug-in";
+  let plugin = getTestPlugin(name);
   plugin.enabledState = newEnabledState;
-  SimpleTest.registerCleanupFunction(function() {
-    getTestPlugin(pluginName).enabledState = oldEnabledState;
+}
+
+// Get the 'enabledState' on the nsIPluginTag stored in the main or chrome
+// process.
+function getTestPluginEnabledState(pluginName) {
+  let name = pluginName || "Test Plug-in";
+  let plugin = getTestPlugin(name);
+  return plugin.enabledState;
+}
+
+// Returns a promise for nsIObjectLoadingContent props data.
+function promiseForPluginInfo(aId, aBrowser) {
+  let browser = aBrowser || gTestBrowser;
+  return ContentTask.spawn(browser, aId, function* (aId) {
+    let plugin = content.document.getElementById(aId);
+    if (!(plugin instanceof Ci.nsIObjectLoadingContent))
+      throw new Error("no plugin found");
+    return {
+      pluginFallbackType: plugin.pluginFallbackType,
+      activated: plugin.activated,
+      hasRunningPlugin: plugin.hasRunningPlugin,
+      displayedType: plugin.displayedType,
+    };
+  });
+}
+
+// Return a promise and call the plugin's nsIObjectLoadingContent
+// playPlugin() method.
+function promisePlayObject(aId, aBrowser) {
+  let browser = aBrowser || gTestBrowser;
+  return ContentTask.spawn(browser, aId, function* (aId) {
+    let plugin = content.document.getElementById(aId);
+    let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    objLoadingContent.playPlugin();
+  });
+}
+
+function promiseCrashObject(aId, aBrowser) {
+  let browser = aBrowser || gTestBrowser;
+  return ContentTask.spawn(browser, aId, function* (aId) {
+    let plugin = content.document.getElementById(aId);
+    let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    Components.utils.waiveXrays(plugin).crash();
+  });
+}
+
+// Return a promise and call the plugin's getObjectValue() method.
+function promiseObjectValueResult(aId, aBrowser) {
+  let browser = aBrowser || gTestBrowser;
+  return ContentTask.spawn(browser, aId, function* (aId) {
+    let plugin = content.document.getElementById(aId);
+    return Components.utils.waiveXrays(plugin).getObjectValue();
+  });
+}
+
+// Return a promise and reload the target plugin in the page
+function promiseReloadPlugin(aId, aBrowser) {
+  let browser = aBrowser || gTestBrowser;
+  return ContentTask.spawn(browser, aId, function* (aId) {
+    let plugin = content.document.getElementById(aId);
+    plugin.src = plugin.src;
   });
 }
 
 // after a test is done using the plugin doorhanger, we should just clear
 // any permissions that may have crept in
 function clearAllPluginPermissions() {
   let perms = Services.perms.enumerator;
   while (perms.hasMoreElements()) {
     let perm = perms.getNext();
     if (perm.type.startsWith('plugin')) {
+      info("removing permission:" + perm.host + " " + perm.type + "\n");
       Services.perms.remove(perm.host, perm.type);
     }
   }
 }
 
 function updateBlocklist(aCallback) {
-  var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
+  let blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
                           .getService(Ci.nsITimerCallback);
-  var observer = function() {
+  let observer = function() {
     Services.obs.removeObserver(observer, "blocklist-updated");
     SimpleTest.executeSoon(aCallback);
   };
   Services.obs.addObserver(observer, "blocklist-updated", false);
   blocklistNotifier.notify(null);
 }
 
-var _originalTestBlocklistURL = null;
+let _originalTestBlocklistURL = null;
 function setAndUpdateBlocklist(aURL, aCallback) {
-  if (!_originalTestBlocklistURL)
+  if (!_originalTestBlocklistURL) {
     _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url");
+  }
   Services.prefs.setCharPref("extensions.blocklist.url", aURL);
   updateBlocklist(aCallback);
 }
 
+// A generator that insures a new blocklist is loaded (in both
+// processes if applicable).
+function* asyncSetAndUpdateBlocklist(aURL, aBrowser) {
+  info("*** loading new blocklist: " + aURL);
+  let doTestRemote = aBrowser ? aBrowser.isRemoteBrowser : false;
+  if (!_originalTestBlocklistURL) {
+    _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url");
+  }
+  Services.prefs.setCharPref("extensions.blocklist.url", aURL);
+  let localPromise = TestUtils.topicObserved("blocklist-updated");
+  let remotePromise;
+  if (doTestRemote) {
+    remotePromise = TestUtils.topicObserved("content-blocklist-updated");
+  }
+  let blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
+                            .getService(Ci.nsITimerCallback);
+  blocklistNotifier.notify(null);
+  info("*** waiting on local load");
+  yield localPromise;
+  if (doTestRemote) {
+    info("*** waiting on remote load");
+    yield remotePromise;
+  }
+  info("*** blocklist loaded.");
+}
+
+// Reset back to the blocklist we had at the start of the test run.
 function resetBlocklist() {
   Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL);
 }
 
-function waitForNotificationPopup(notificationID, browser, callback) {
-  let notification;
-  waitForCondition(
-    () => (notification = PopupNotifications.getNotification(notificationID, browser)),
-    () => {
-      ok(notification, `Successfully got the ${notificationID} notification popup`);
-      callback(notification);
-    },
-    `Waited too long for the ${notificationID} notification popup`
-  );
+// Insure there's a popup notification present. This test does not indicate
+// open state. aBrowser can be undefined.
+function promisePopupNotification(aName, aBrowser) {
+  let deferred = Promise.defer();
+
+  waitForCondition(() => PopupNotifications.getNotification(aName, aBrowser),
+                   () => {
+    ok(!!PopupNotifications.getNotification(aName, aBrowser),
+       aName + " notification appeared");
+
+    deferred.resolve();
+  }, "timeout waiting for popup notification " + aName);
+
+  return deferred.promise;
+}
+
+/**
+ * Allows setting focus on a window, and waiting for that window to achieve
+ * focus.
+ *
+ * @param aWindow
+ *        The window to focus and wait for.
+ *
+ * @return {Promise}
+ * @resolves When the window is focused.
+ * @rejects Never.
+ */
+function promiseWaitForFocus(aWindow) {
+  return new Promise((resolve) => {
+    waitForFocus(resolve, aWindow);
+  });
 }
 
 /**
  * Returns a Promise that resolves when a notification bar
  * for a browser is shown. Alternatively, for old-style callers,
  * can automatically call a callback before it resolves.
  *
  * @param notificationID
@@ -148,71 +355,60 @@ function waitForNotificationBar(notifica
         }
         resolve(notification);
       },
       `Waited too long for the ${notificationID} notification bar`
     );
   });
 }
 
+function promiseForNotificationBar(notificationID, browser) {
+  let deferred = Promise.defer();
+  waitForNotificationBar(notificationID, browser, deferred.resolve);
+  return deferred.promise;
+}
+
 /**
  * Reshow a notification and call a callback when it is reshown.
  * @param notification
  *        The notification to reshow
  * @param callback
  *        A function to be called when the notification has been reshown
  */
-function waitForNotificationShown(notification, callback)
-{
+function waitForNotificationShown(notification, callback) {
   if (PopupNotifications.panel.state == "open") {
     executeSoon(callback);
     return;
   }
   PopupNotifications.panel.addEventListener("popupshown", function onShown(e) {
     PopupNotifications.panel.removeEventListener("popupshown", onShown);
     callback();
   }, false);
   notification.reshow();
 }
 
-/**
- * Due to layout being async, "PluginBindAttached" may trigger later.
- * This returns a Promise that resolves once we've forced a layout
- * flush, which triggers the PluginBindAttached event to fire.
- *
- * @param browser
- *        The browser to force plugin bindings in.
- *
- * @return Promise
- */
-function forcePluginBindingAttached(browser) {
-  return new Promise((resolve, reject) => {
-    let doc = browser.contentDocument;
-    let elems = doc.getElementsByTagName('embed');
-    if (elems.length < 1) {
-      elems = doc.getElementsByTagName('object');
-    }
-    elems[0].clientTop;
-    executeSoon(resolve);
-  });
+function promiseForNotificationShown(notification) {
+  let deferred = Promise.defer();
+  waitForNotificationShown(notification, deferred.resolve);
+  return deferred.promise;
 }
 
 /**
- * Loads a page in a browser, and returns a Promise that
- * resolves once the "load" event has been fired for that
- * browser.
- *
+ * Due to layout being async, "PluginBindAttached" may trigger later. This
+ * returns a Promise that resolves once we've forced a layout flush, which
+ * triggers the PluginBindAttached event to fire. This trick only works if
+ * there is some sort of plugin in the page.
  * @param browser
- *        The browser to load the page in.
- * @param uri
- *        The URI to load.
- *
+ *        The browser to force plugin bindings in.
  * @return Promise
  */
-function loadPage(browser, uri) {
-  return new Promise((resolve, reject) => {
-    browser.addEventListener("load", function onLoad(event) {
-      browser.removeEventListener("load", onLoad, true);
-      resolve();
-    }, true);
-    browser.loadURI(uri);
+function promiseUpdatePluginBindings(browser) {
+  return ContentTask.spawn(browser, {}, function* () {
+    let doc = content.document;
+    let elems = doc.getElementsByTagName('embed');
+    if (!elems || elems.length < 1) {
+      elems = doc.getElementsByTagName('object');
+    }
+    if (elems && elems.length > 0) {
+      elems[0].clientTop;
+    }
   });
 }
--- a/browser/base/content/test/plugins/plugin_add_dynamically.html
+++ b/browser/base/content/test/plugins/plugin_add_dynamically.html
@@ -1,17 +1,18 @@
-<!DOCTYPE html>
+<!DOCTYPE html>
 <html>
 <head>
 <meta charset="utf-8">
 </head>
 <body>
 <script>
-function addPlugin(type="application/x-test") {
+function addPlugin(aId, aType="application/x-test") {
   var embed = document.createElement("embed");
+  embed.setAttribute("id", aId);
   embed.style.width = "200px";
   embed.style.height = "200px";
-  embed.setAttribute("type", type);
+  embed.setAttribute("type", aType);
   return document.body.appendChild(embed);
 }
 </script>
 </body>
 </html>
--- a/browser/base/content/test/plugins/plugin_both.html
+++ b/browser/base/content/test/plugins/plugin_both.html
@@ -1,10 +1,10 @@
-<!DOCTYPE html>
+<!DOCTYPE html>
 <html>
-<head>
-<meta charset="utf-8">
+<head>
+<meta charset="utf-8">
 </head>
 <body>
 <embed id="unknown" style="width: 100px; height: 100px" type="application/x-unknown">
 <embed id="test" style="width: 100px; height: 100px" type="application/x-test">
 </body>
 </html>
--- a/browser/base/content/test/plugins/plugin_hidden_to_visible.html
+++ b/browser/base/content/test/plugins/plugin_hidden_to_visible.html
@@ -1,11 +1,11 @@
-<!DOCTYPE html>
+<!DOCTYPE html>
 <html>
 <head>
 <meta charset="utf-8">
 </head>
 <body>
   <div id="container" style="display: none">
-    <object id="plugin" type="application/x-test" style="width: 200px; height: 200px;"></object>
+    <object id="test" type="application/x-test" style="width: 200px; height: 200px;"></object>
   </div>
 </body>
 </html>
--- a/browser/base/content/test/plugins/plugin_syncRemoved.html
+++ b/browser/base/content/test/plugins/plugin_syncRemoved.html
@@ -1,14 +1,15 @@
-<!DOCTYPE html>
+<!DOCTYPE html>
 <html>
 <head>
 <meta charset="utf-8">
 <body>
 <script type="text/javascript">
   // create an embed, insert it in the doc and immediately remove it
   var embed = document.createElement('embed');
+  embed.setAttribute("id", "test");
   embed.setAttribute("type", "application/x-test");
   embed.setAttribute("style", "width: 0px; height: 0px;");
   document.body.appendChild(embed);
   window.getComputedStyle(embed, null).top;
   document.body.remove(embed);
 </script>
--- a/browser/base/content/test/plugins/plugin_test.html
+++ b/browser/base/content/test/plugins/plugin_test.html
@@ -1,9 +1,9 @@
-<!DOCTYPE html>
+<!DOCTYPE html>
 <html>
-<head>
-<meta charset="utf-8">
+<head>
+<meta charset="utf-8">
 </head>
 <body>
 <embed id="test" style="width: 200px; height: 200px" type="application/x-test">
 </body>
 </html>
--- a/browser/base/content/test/plugins/plugin_test3.html
+++ b/browser/base/content/test/plugins/plugin_test3.html
@@ -1,9 +1,9 @@
-<!DOCTYPE html>
+<!DOCTYPE html>
 <html>
-<head>
-<meta charset="utf-8">
+<head>
+<meta charset="utf-8">
 </head>
 <body>
 <embed id="test" style="width: 0px; height: 0px" type="application/x-test">
 </body>
 </html>
--- a/browser/base/content/test/plugins/plugin_unknown.html
+++ b/browser/base/content/test/plugins/plugin_unknown.html
@@ -1,9 +1,9 @@
-<!DOCTYPE html>
+<!DOCTYPE html>
 <html>
-<head>
-<meta charset="utf-8">
+<head>
+<meta charset="utf-8">
 </head>
 <body>
 <embed id="unknown" style="width: 100px; height: 100px" type="application/x-unknown">
 </body>
 </html>
--- a/browser/base/content/test/social/browser_social_activation.js
+++ b/browser/base/content/test/social/browser_social_activation.js
@@ -84,28 +84,25 @@ function activateIFrameProvider(domain, 
     sendActivationEvent(tab, callback, false);
   });
 }
 
 function waitForProviderLoad(cb) {
     waitForCondition(function() {
       let sbrowser = document.getElementById("social-sidebar-browser");
       let provider = SocialSidebar.provider;
-      let postActivation = provider && gBrowser.contentDocument.location.href == provider.origin + "/browser/browser/base/content/test/social/social_postActivation.html";
+      let postActivation = provider && gBrowser.contentDocument &&
+                            gBrowser.contentDocument.location.href == provider.origin + "/browser/browser/base/content/test/social/social_postActivation.html";
 
-      return provider &&
-             provider.profile &&
-             provider.profile.displayName &&
-             postActivation &&
-             sbrowser.docShellIsActive;
+      return postActivation && sbrowser.docShellIsActive;
     }, function() {
       // executeSoon to let the browser UI observers run first
       executeSoon(cb);
     },
-    "waitForProviderLoad: provider profile was not set");
+    "waitForProviderLoad: provider was not loaded");
 }
 
 
 function getAddonItemInList(aId, aList) {
   var item = aList.firstChild;
   while (item) {
     if ("mAddon" in item && item.mAddon.id == aId) {
       aList.ensureElementIsVisible(item);
@@ -173,32 +170,29 @@ function activateOneProvider(manifest, f
   });
 }
 
 let gTestDomains = ["https://example.com", "https://test1.example.com", "https://test2.example.com"];
 let gProviders = [
   {
     name: "provider 1",
     origin: "https://example.com",
-    sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html?provider1",
-    workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js#no-profile,no-recommend",
+    sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar_empty.html?provider1",
     iconURL: "chrome://branding/content/icon48.png"
   },
   {
     name: "provider 2",
     origin: "https://test1.example.com",
-    sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html?provider2",
-    workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js#no-profile,no-recommend",
+    sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar_empty.html?provider2",
     iconURL: "chrome://branding/content/icon64.png"
   },
   {
     name: "provider 3",
     origin: "https://test2.example.com",
-    sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html?provider2",
-    workerURL: "https://test2.example.com/browser/browser/base/content/test/social/social_worker.js#no-profile,no-recommend",
+    sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar_empty.html?provider2",
     iconURL: "chrome://branding/content/about-logo.png"
   }
 ];
 
 
 function test() {
   waitForExplicitFinish();
   runSocialTests(tests, undefined, postTestCleanup);
--- a/browser/components/loop/modules/LoopRooms.jsm
+++ b/browser/components/loop/modules/LoopRooms.jsm
@@ -213,21 +213,16 @@ let LoopRoomsInternal = {
       serverRoomData.roomName = roomData.decryptedContext.roomName;
 
       return {
         all: roomData,
         encrypted: serverRoomData
       };
     }
 
-    // For now, disable encryption/context if context is disabled
-    if (!MozLoopService.getLoopPref("contextInConverations.enabled")) {
-      return getUnencryptedData();
-    }
-
     var newRoomData = extend({}, roomData);
 
     if (!newRoomData.context) {
       newRoomData.context = {};
     }
 
     // First get the room key.
     let key = yield this.promiseGetOrCreateRoomKey(newRoomData);
@@ -695,17 +690,18 @@ let LoopRoomsInternal = {
       let {all, encrypted} = yield this.promiseEncryptRoomData(roomData);
 
       // For patch, we only send the context data.
       let sendData = {
         context: encrypted.context
       };
 
       // If we're not encrypting currently, then only send the roomName.
-      if (!Services.prefs.getBoolPref("loop.contextInConverations.enabled")) {
+      // XXX This should go away once bug 1153788 is fixed.
+      if (!sendData.context) {
         sendData = {
           roomName: newRoomName
         };
       }
 
       let response = yield MozLoopService.hawkRequest(this.sessionType,
           url, "PATCH", sendData);
 
--- a/browser/components/loop/test/xpcshell/test_looprooms.js
+++ b/browser/components/loop/test/xpcshell/test_looprooms.js
@@ -6,18 +6,16 @@
 
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource:///modules/loop/LoopRooms.jsm");
 Cu.import("resource:///modules/Chat.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
 let openChatOrig = Chat.open;
 
-const kContextEnabledPref = "loop.contextInConverations.enabled";
-
 const kGuestKey = "uGIs-kGbYt1hBBwjyW7MLQ";
 
 // Rooms details as responded by the server.
 const kRoomsResponses = new Map([
   ["_nxD4V4FflQ", {
     roomToken: "_nxD4V4FflQ",
     // Encrypted with roomKey "FliIGLUolW-xkKZVWstqKw".
     // roomKey is wrapped with kGuestKey.
@@ -294,24 +292,20 @@ add_task(function* setup_server() {
   loopServer.registerPathHandler("/rooms", (req, res) => {
     res.setStatusLine(null, 200, "OK");
 
     if (req.method == "POST") {
       Assert.ok(req.bodyInputStream, "POST request should have a payload");
       let body = CommonUtils.readBytesFromInputStream(req.bodyInputStream);
       let data = JSON.parse(body);
 
-      if (Services.prefs.getBoolPref(kContextEnabledPref)) {
-        Assert.equal(data.roomOwner, kCreateRoomProps.roomOwner);
-        Assert.equal(data.maxSize, kCreateRoomProps.maxSize);
-        Assert.ok(!("decryptedContext" in data), "should not have any decrypted data");
-        Assert.ok("context" in data, "should have context");
-      } else {
-        Assert.deepEqual(data, kCreateRoomUnencryptedProps);
-      }
+      Assert.equal(data.roomOwner, kCreateRoomProps.roomOwner);
+      Assert.equal(data.maxSize, kCreateRoomProps.maxSize);
+      Assert.ok(!("decryptedContext" in data), "should not have any decrypted data");
+      Assert.ok("context" in data, "should have context");
 
       res.write(JSON.stringify(kCreateRoomData));
     } else {
       if (req.queryString) {
         let qs = parseQueryString(req.queryString);
         let room = kRoomsResponses.get("_nxD4V4FflQ");
         room.participants = kRoomUpdates[qs.version].participants;
         room.deleted = kRoomUpdates[qs.version].deleted;
@@ -348,25 +342,21 @@ add_task(function* setup_server() {
       if (req.method == "POST") {
         let data = getJSONData(req.bodyInputStream);
         res.setStatusLine(null, 200, "OK");
         res.write(JSON.stringify(data));
         res.processAsync();
         res.finish();
       } else if (req.method == "PATCH") {
         let data = getJSONData(req.bodyInputStream);
-        if (Services.prefs.getBoolPref(kContextEnabledPref)) {
-          Assert.ok("context" in data, "should have encrypted context");
-          // We return a fake encrypted name here as the context is
-          // encrypted.
-          returnRoomDetails(res, "fakeEncrypted");
-        } else {
-          Assert.ok(!("context" in data), "should not have encrypted context");
-          returnRoomDetails(res, data.roomName);
-        }
+
+        Assert.ok("context" in data, "should have encrypted context");
+        // We return a fake encrypted name here as the context is
+        // encrypted.
+        returnRoomDetails(res, "fakeEncrypted");
       } else {
         roomDetail.context = room.context;
         res.setStatusLine(null, 200, "OK");
         res.write(JSON.stringify(roomDetail));
         res.processAsync();
         res.finish();
       }
     });
@@ -410,30 +400,16 @@ add_task(function* test_getRoom() {
 // Test if fetching a room with incorrect token or return values yields an error.
 add_task(function* test_errorStates() {
   yield Assert.rejects(LoopRooms.promise("get", "error401"), /Not Found/, "Fetching a non-existent room should fail");
   yield Assert.rejects(LoopRooms.promise("get", "errorMalformed"), /SyntaxError/, "Wrong message format should reject");
 });
 
 // Test if creating a new room works as expected.
 add_task(function* test_createRoom() {
-  Services.prefs.setBoolPref(kContextEnabledPref, true);
-
-  var expectedRoom = extend({}, kCreateRoomProps);
-  expectedRoom.roomToken = kCreateRoomData.roomToken;
-
-  gExpectedAdds.push(expectedRoom);
-  let room = yield LoopRooms.promise("create", kCreateRoomProps);
-  compareRooms(room, expectedRoom);
-});
-
-// XXX Test unencrypted rooms. This will go away once we switch encryption on.
-add_task(function* test_createRoom_unencrypted() {
-  Services.prefs.setBoolPref(kContextEnabledPref, false);
-
   var expectedRoom = extend({}, kCreateRoomProps);
   expectedRoom.roomToken = kCreateRoomData.roomToken;
 
   gExpectedAdds.push(expectedRoom);
   let room = yield LoopRooms.promise("create", kCreateRoomProps);
   compareRooms(room, expectedRoom);
 });
 
@@ -596,29 +572,21 @@ add_task(function* test_sendConnectionSt
 
   extraData.action = "status";
   extraData.sessionToken = "fakeStatusSessionToken";
   Assert.deepEqual(statusData, extraData);
 });
 
 // Test if renaming a room works as expected.
 add_task(function* test_renameRoom() {
-  Services.prefs.setBoolPref(kContextEnabledPref, true);
   let roomToken = "_nxD4V4FflQ";
   let renameData = yield LoopRooms.promise("rename", roomToken, "fakeName");
   Assert.equal(renameData.roomName, "fakeEncrypted", "should have set the new name");
 });
 
-add_task(function* test_renameRoom_unencrpyted() {
-  Services.prefs.setBoolPref(kContextEnabledPref, false);
-  let roomToken = "_nxD4V4FflQ";
-  let renameData = yield LoopRooms.promise("rename", roomToken, "fakeName");
-  Assert.equal(renameData.roomName, "fakeName", "should have set the new name");
-});
-
 add_task(function* test_roomDeleteNotifications() {
   gExpectedDeletes.push("_nxD4V4FflQ");
   roomsPushNotification("5", kChannelGuest);
   yield waitForCondition(() => gExpectedDeletes.length === 0);
 });
 
 // Test if deleting a room works as expected.
 add_task(function* test_deleteRoom() {
@@ -652,17 +620,16 @@ function run_test() {
   LoopRooms.on("delete", onRoomDeleted);
   LoopRooms.on("joined", onRoomJoined);
   LoopRooms.on("left", onRoomLeft);
   LoopRooms.on("refresh", onRefresh);
 
   do_register_cleanup(function () {
     // Revert original Chat.open implementation
     Chat.open = openChatOrig;
-    Services.prefs.clearUserPref(kContextEnabledPref);
     Services.prefs.clearUserPref("loop.key");
 
     MozLoopServiceInternal.fxAOAuthTokenData = null;
     MozLoopServiceInternal.fxAOAuthProfile = null;
 
     LoopRooms.off("add", onRoomAdded);
     LoopRooms.off("update", onRoomUpdated);
     LoopRooms.off("delete", onRoomDeleted);
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -1824,17 +1824,25 @@ MarkupContainer.prototype = {
       return;
     }
 
     // target is the MarkupContainer itself.
     this._isMouseDown = true;
     this.hovered = false;
     this.markup.navigate(this);
     event.stopPropagation();
-    event.preventDefault();
+
+    // Preventing the default behavior will avoid the body to gain focus on
+    // mouseup (through bubbling) when clicking on a non focusable node in the
+    // line. So, if the click happened outside of a focusable element, do
+    // prevent the default behavior, so that the tagname or textcontent gains
+    // focus.
+    if (!target.closest(".open [tabindex]")) {
+      event.preventDefault();
+    }
 
     // Start dragging the container after a delay.
     this.markup._dragStartEl = target;
     setTimeout(() => {
       // Make sure the mouse is still down and on target.
       if (!this._isMouseDown || this.markup._dragStartEl !== target ||
           this.node.isPseudoElement || this.node.isAnonymous ||
           !this.win.getSelection().isCollapsed) {
--- a/browser/devtools/markupview/test/browser_markupview_keybindings_03.js
+++ b/browser/devtools/markupview/test/browser_markupview_keybindings_03.js
@@ -3,17 +3,17 @@
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Tests that selecting a node with the mouse (by clicking on the line) focuses
 // the first focusable element in the corresponding MarkupContainer so that the
 // keyboard can be used immediately.
 
-const TEST_URL = "data:text/html;charset=utf8,<div></div>Text node";
+const TEST_URL = "data:text/html;charset=utf8,<div class='test-class'></div>Text node";
 
 add_task(function*() {
   let {inspector, toolbox} = yield addTab(TEST_URL).then(openInspector);
   let {walker} = inspector;
 
   info("Select the test node to have the 2 test containers visible");
   yield selectNode("div", inspector);
 
@@ -26,9 +26,23 @@ add_task(function*() {
      getContainerForNodeFront(textFront, inspector).editor.value,
      "The currently focused element is the node's text content");
 
   info("Click on the MarkupContainer element for the <div> node");
   yield clickContainer(divFront, inspector);
   is(inspector.markup.doc.activeElement,
      getContainerForNodeFront(divFront, inspector).editor.tag,
      "The currently focused element is the div's tagname");
+
+  info("Click on the test-class attribute, to make sure it gets focused");
+  let editor = getContainerForNodeFront(divFront, inspector).editor;
+  let attributeEditor = editor.attrElements.get("class").querySelector(".editable");
+
+  let onFocus = once(attributeEditor, "focus");
+  EventUtils.synthesizeMouseAtCenter(attributeEditor, {type: "mousedown"},
+    inspector.markup.doc.defaultView);
+  EventUtils.synthesizeMouseAtCenter(attributeEditor, {type: "mouseup"},
+    inspector.markup.doc.defaultView);
+  yield onFocus;
+
+  is(inspector.markup.doc.activeElement, attributeEditor,
+     "The currently focused element is the div's class attribute");
 });
--- a/browser/devtools/performance/modules/graphs.js
+++ b/browser/devtools/performance/modules/graphs.js
@@ -261,30 +261,30 @@ GraphsController.prototype = {
     }
 
     // If there was rendering, wait until the most recent render cycle
     // has finished
     if (this._rendering) {
       yield this._rendering.promise;
     }
 
-    for (let graphName in this._graphs) {
-      yield this._graphs[graphName].destroy();
+    for (let graph of this.getWidgets()) {
+      yield graph.destroy();
     }
   }),
 
   /**
    * Applies the theme to the underlying graphs. Optionally takes
    * a `redraw` boolean in the options to force redraw.
    */
   setTheme: function (options={}) {
     let theme = options.theme || this._getTheme();
-    for (let graph in this._graphs) {
-      this._graphs[graph].setTheme(theme);
-      this._graphs[graph].refresh({ force: options.redraw });
+    for (let graph of this.getWidgets()) {
+      graph.setTheme(theme);
+      graph.refresh({ force: options.redraw });
     }
   },
 
   /**
    * Sets up the graph, if needed. Returns a promise resolving
    * to the graph if it is enabled once it's ready, or otherwise returns
    * null if disabled.
    */
@@ -344,16 +344,24 @@ GraphsController.prototype = {
     return this._getPrimaryLink().setMappedSelection(selection, { mapStart, mapEnd });
   },
 
   getMappedSelection: function ({ mapStart, mapEnd }) {
     return this._getPrimaryLink().getMappedSelection({ mapStart, mapEnd });
   },
 
   /**
+   * Returns an array of graphs that have been created, not necessarily
+   * enabled currently.
+   */
+  getWidgets: function () {
+    return Object.keys(this._graphs).map(name => this._graphs[name]);
+  },
+
+  /**
    * Drops the selection.
    */
   dropSelection: function () {
     if (this._getPrimaryLink()) {
       return this._getPrimaryLink().dropSelection();
     }
   },
 
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -38,16 +38,17 @@ support-files =
 [browser_perf-details-memory-calltree-render.js]
 [browser_perf-details-memory-flamegraph-render.js]
 [browser_perf-details-waterfall-render.js]
 [browser_perf-details-01.js]
 [browser_perf-details-02.js]
 [browser_perf-details-03.js]
 [browser_perf-details-04.js]
 [browser_perf-details-05.js]
+[browser_perf-details-06.js]
 [browser_perf-events-calltree.js]
 [browser_perf-front-basic-profiler-01.js]
 [browser_perf-front-basic-timeline-01.js]
 #[browser_perf-front-profiler-01.js] bug 1077464
 [browser_perf-front-profiler-02.js]
 [browser_perf-front-profiler-03.js]
 [browser_perf-front-profiler-04.js]
 #[browser_perf-front-profiler-05.js] bug 1077464
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_perf-details-06.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the views with `shouldUpdateWhileMouseIsActive` works as intended.
+ */
+function spawnTest () {
+  let { panel } = yield initPerformance(SIMPLE_URL);
+  let { EVENTS, PerformanceController, OverviewView, DetailsView, WaterfallView, JsFlameGraphView } = panel.panelWin;
+
+  yield startRecording(panel);
+  yield stopRecording(panel);
+
+  // Set the debounce on WaterfallView and JsFlameGraphView to 0
+  WaterfallView.rangeChangeDebounceTime = 0;
+  JsFlameGraphView.rangeChangeDebounceTime = 0;
+
+  yield DetailsView.selectView("js-flamegraph");
+  let duration = PerformanceController.getCurrentRecording().getDuration();
+
+  // Fake an active mouse
+  Object.defineProperty(OverviewView, "isMouseActive", { value: true });
+
+  // Flame Graph should update on every selection, debounced, while mouse is down
+  let flamegraphRendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
+  OverviewView.emit(EVENTS.OVERVIEW_RANGE_SELECTED, { startTime: 0, endTime: duration });
+  yield flamegraphRendered;
+  ok(true, "FlameGraph rerenders when mouse is active (1)");
+
+  flamegraphRendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
+  OverviewView.emit(EVENTS.OVERVIEW_RANGE_SELECTED, { startTime: 0, endTime: duration });
+  yield flamegraphRendered;
+  ok(true, "FlameGraph rerenders when mouse is active (2)");
+
+  ok(OverviewView.isMouseActive, "Fake mouse is still active");
+
+  // Fake an inactive mouse for rerender
+  Object.defineProperty(OverviewView, "isMouseActive", { value: false });
+  yield DetailsView.selectView("waterfall");
+
+  // Fake an active mouse for rerender
+  Object.defineProperty(OverviewView, "isMouseActive", { value: true });
+
+  let oneSecondElapsed = false;
+  let waterfallRendered = false;
+
+  WaterfallView.on(EVENTS.WATERFALL_RENDERED, () => waterfallRendered = true);
+
+  // Keep firing range selection events for one second
+  idleWait(1000).then(() => oneSecondElapsed = true);
+  yield waitUntil(() => {
+    OverviewView.emit(EVENTS.OVERVIEW_RANGE_SELECTED, { startTime: 0, endTime: duration });
+    return oneSecondElapsed;
+  });
+
+  ok(OverviewView.isMouseActive, "Fake mouse is still active");
+  ok(!waterfallRendered, "the waterfall view should not have been rendered while mouse is active.");
+
+  yield teardown(panel);
+  finish();
+}
--- a/browser/devtools/performance/views/overview.js
+++ b/browser/devtools/performance/views/overview.js
@@ -92,19 +92,20 @@ let OverviewView = {
     yield this.graphs.destroy();
   }),
 
   /**
    * Returns true if any of the overview graphs have mouse dragging active,
    * false otherwise.
    */
   get isMouseActive() {
-    return (this.markersOverview && this.markersOverview.isMouseActive) ||
-           (this.memoryOverview && this.memoryOverview.isMouseActive) ||
-           (this.framerateGraph && this.framerateGraph.isMouseActive);
+    // Fetch all graphs currently stored in the GraphsController.
+    // These graphs are not necessarily active, but will not have
+    // an active mouse, in that case.
+    return !!this.graphs.getWidgets().some(e => e.isMouseActive);
   },
 
   /**
    * Disabled in the event we're using a Timeline mock, so we'll have no
    * timeline, ticks or memory data to show, so just block rendering and hide
    * the panel.
    */
   disable: function () {
--- a/browser/devtools/responsivedesign/test/browser_responsiveuiaddcustompreset.js
+++ b/browser/devtools/responsivedesign/test/browser_responsiveuiaddcustompreset.js
@@ -114,17 +114,19 @@ function test() {
     is(container.getAttribute("responsivemode"), "true", "In responsive mode.");
 
     instance = mgr.getResponsiveUIForTab(gBrowser.selectedTab);
 
     let customPresetIndex = getPresetIndex("456" + "\u00D7" + "123 (Testing preset)");
     info(customPresetIndex);
     ok(customPresetIndex >= 0, "is the previously added preset (idx = " + customPresetIndex + ") in the list of items");
 
+    let resizePromise = instance._test_notifyOnResize();
     instance.menulist.selectedIndex = customPresetIndex;
+    yield resizePromise;
 
     is(content.innerWidth, 456, "add preset, and selected in the list, dimension valid (width)");
     is(content.innerHeight, 123, "add preset, and selected in the list, dimension valid (height)");
 
     instance.removebutton.doCommand();
 
     instance.menulist.selectedIndex = 2;
     deletedPresetA = instance.menulist.selectedItem.getAttribute("label");
--- a/browser/devtools/shared/profiler/frame-utils.js
+++ b/browser/devtools/shared/profiler/frame-utils.js
@@ -49,25 +49,40 @@ exports.parseLocation = function parseLo
     hostName: hostName,
     url: url,
     line: line,
     column: column
   };
 },
 
 /**
+ * Determines if the given frame is the (root) frame.
+ *
+ * @param object frame
+ * @return boolean
+ */
+exports.isRoot = function isRoot({ location }) {
+  return location === "(root)";
+};
+
+/**
 * Checks if the specified function represents a chrome or content frame.
 *
 * @param object frame
 *        The { category, location } properties of the frame.
 * @return boolean
 *         True if a content frame, false if a chrome frame.
 */
-exports.isContent = function isContent ({ category, location }) {
+exports.isContent = function isContent (frame) {
+  if (exports.isRoot(frame)) {
+    return true;
+  }
+
   // Only C++ stack frames have associated category information.
+  const { category, location } = frame;
   return !!(!category &&
     !CHROME_SCHEMES.find(e => location.includes(e)) &&
     CONTENT_SCHEMES.find(e => location.includes(e)));
 }
 
 /**
  * This filters out platform data frames in a sample. With latest performance
  * tool in Fx40, when displaying only content, we still filter out all platform data,
--- a/browser/devtools/shared/profiler/tree-model.js
+++ b/browser/devtools/shared/profiler/tree-model.js
@@ -88,25 +88,30 @@ ThreadNode.prototype = {
     let optimizations = options.optimizations;
     let sampleTime = sample.time;
     if (!sampleTime || sampleTime < startTime || sampleTime > endTime) {
       return;
     }
 
     let sampleFrames = sample.frames;
 
+    if (!options.invertTree) {
+      // Remove the (root) node if the tree is not inverted: we will synthesize
+      // our own root in the view. However, for inverted trees, we wish to be
+      // able to differentiate between (root)->A->B->C and (root)->B->C stacks,
+      // so we need the root node in that case.
+      sampleFrames = sampleFrames.slice(1);
+    }
+
     // Filter out platform frames if only content-related function calls
     // should be taken into consideration.
     if (options.contentOnly) {
-      // The (root) node is not considered a content function, it'll be removed.
       sampleFrames = FrameUtils.filterPlatformData(sampleFrames);
-    } else {
-      // Remove the (root) node manually.
-      sampleFrames = sampleFrames.slice(1);
     }
+
     // If no frames remain after filtering, then this is a leaf node, no need
     // to continue.
     if (!sampleFrames.length) {
       return;
     }
     // Invert the tree after filtering, if preferred.
     if (options.invertTree) {
       sampleFrames.reverse();
--- a/browser/devtools/webconsole/test/browser_bug_865871_variables_view_close_on_esc_key.js
+++ b/browser/devtools/webconsole/test/browser_bug_865871_variables_view_close_on_esc_key.js
@@ -15,84 +15,61 @@ function test()
   let hud;
 
   Task.spawn(runner).then(finishTest);
 
   function* runner() {
     let {tab} = yield loadTab(TEST_URI);
     hud = yield openConsole(tab);
     let jsterm = hud.jsterm;
-
-    let msg = yield execute("fooObj");
-    ok(msg, "output message found");
-
-    let anchor = msg.querySelector("a");
-    let body = msg.querySelector(".message-body");
-    ok(anchor, "object anchor");
-    ok(body, "message body");
-    ok(body.textContent.includes('testProp: "testValue"'), "message text check");
+    let result;
+    let vview;
+    let msg;
 
-    msg.scrollIntoView();
-    executeSoon(() => {
-      EventUtils.synthesizeMouse(anchor, 2, 2, {}, hud.iframeWindow);
-    });
-
-    let vviewVar = yield jsterm.once("variablesview-fetched");
-    let vview = vviewVar._variablesView;
-    ok(vview, "variables view object");
-
-    let [result] = yield findVariableViewProperties(vviewVar, [
-      { name: "testProp", value: "testValue" },
-    ], { webconsole: hud });
+    yield openSidebar("fooObj",
+                      'testProp: "testValue"',
+                      { name: "testProp", value: "testValue" });
 
     let prop = result.matchedProp;
     ok(prop, "matched the |testProp| property in the variables view");
 
     vview.window.focus();
 
-    executeSoon(() => {
-      EventUtils.synthesizeKey("VK_ESCAPE", {});
-    });
-    yield jsterm.once("sidebar-closed");
+    let sidebarClosed = jsterm.once("sidebar-closed");
+    EventUtils.synthesizeKey("VK_ESCAPE", {});
+    yield sidebarClosed;
 
     jsterm.clearOutput();
 
-    msg = yield execute("window.location");
-    ok(msg, "output message found");
-
-    body = msg.querySelector(".message-body");
-    ok(body, "message body");
-    anchor = msg.querySelector("a");
-    ok(anchor, "object anchor");
-    ok(body.textContent.includes("Location \u2192 http://example.com/browser/"),
-       "message text check");
-
-    msg.scrollIntoView();
-    executeSoon(() => {
-      EventUtils.synthesizeMouse(anchor, 2, 2, {}, hud.iframeWindow)
-    });
-    vviewVar = yield jsterm.once("variablesview-fetched");
-
-    vview = vviewVar._variablesView;
-    ok(vview, "variables view object");
-
-    yield findVariableViewProperties(vviewVar, [
-      { name: "host", value: "example.com" },
-    ], { webconsole: hud });
+    yield openSidebar("window.location",
+                      "Location \u2192 http://example.com/browser/",
+                      { name: "host", value: "example.com" });
 
     vview.window.focus();
 
     msg.scrollIntoView();
-    executeSoon(() => {
-      EventUtils.synthesizeKey("VK_ESCAPE", {});
-    });
+    sidebarClosed = jsterm.once("sidebar-closed");
+    EventUtils.synthesizeKey("VK_ESCAPE", {});
+    yield sidebarClosed;
 
-    yield jsterm.once("sidebar-closed");
-  }
+    function* openSidebar(objName, expectedText, expectedObj) {
+      msg = yield jsterm.execute(objName);
+      ok(msg, "output message found");
 
-  function execute(str) {
-    let deferred = promise.defer();
-    hud.jsterm.execute(str, (msg) => {
-      deferred.resolve(msg);
-    });
-    return deferred.promise;
+      let anchor = msg.querySelector("a");
+      let body = msg.querySelector(".message-body");
+      ok(anchor, "object anchor");
+      ok(body, "message body");
+      ok(body.textContent.includes(expectedText), "message text check");
+
+      msg.scrollIntoView();
+      yield EventUtils.synthesizeMouse(anchor, 2, 2, {}, hud.iframeWindow);
+
+      let vviewVar = yield jsterm.once("variablesview-fetched");
+      vview = vviewVar._variablesView;
+      ok(vview, "variables view object exists");
+
+      [result] = yield findVariableViewProperties(vviewVar, [
+        expectedObj,
+      ], { webconsole: hud });
+    }
   }
 }
--- a/browser/devtools/webconsole/test/browser_console_clear_on_reload.js
+++ b/browser/devtools/webconsole/test/browser_console_clear_on_reload.js
@@ -1,43 +1,54 @@
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Check that clear output on page reload works - bug 705921.
+// Check that clear output and page reload remove the sidebar - bug 971967.
 
 "use strict";
 
 let test = asyncTest(function*() {
   const PREF = "devtools.webconsole.persistlog";
   const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
 
   Services.prefs.setBoolPref(PREF, false);
   registerCleanupFunction(() => Services.prefs.clearUserPref(PREF));
 
   yield loadTab(TEST_URI);
 
   let hud = yield openConsole();
   ok(hud, "Web Console opened");
 
+  yield openSidebar("fooObj", { name: "testProp", value: "testValue" });
+
+  let sidebarClosed = hud.jsterm.once("sidebar-closed");
   hud.jsterm.clearOutput();
+  yield sidebarClosed;
+
   hud.jsterm.execute("console.log('foobarz1')");
 
   yield waitForMessages({
     webconsole: hud,
     messages: [{
       text: "foobarz1",
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
     }],
   });
 
+  yield openSidebar("fooObj", { name: "testProp", value: "testValue" });
+
   BrowserReload();
-  yield loadBrowser(gBrowser.selectedBrowser);
+
+  sidebarClosed = hud.jsterm.once("sidebar-closed");
+  loadBrowser(gBrowser.selectedBrowser);
+  yield sidebarClosed;
 
   hud.jsterm.execute("console.log('foobarz2')");
 
   yield waitForMessages({
     webconsole: hud,
     messages: [{
       text: "test-console.html",
       category: CATEGORY_NETWORK,
@@ -46,9 +57,29 @@ let test = asyncTest(function*() {
       text: "foobarz2",
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
     }],
   });
 
   is(hud.outputNode.textContent.indexOf("foobarz1"), -1,
      "foobarz1 has been removed from output");
+
+  function* openSidebar(objName, expectedObj) {
+    let msg = yield hud.jsterm.execute(objName);
+    ok(msg, "output message found");
+
+    let anchor = msg.querySelector("a");
+    let body = msg.querySelector(".message-body");
+    ok(anchor, "object anchor");
+    ok(body, "message body");
+
+    yield EventUtils.synthesizeMouse(anchor, 2, 2, {}, hud.iframeWindow);
+
+    let vviewVar = yield hud.jsterm.once("variablesview-fetched");
+    let vview = vviewVar._variablesView;
+    ok(vview, "variables view object exists");
+
+    yield findVariableViewProperties(vviewVar, [
+      expectedObj,
+    ], { webconsole: hud });
+  }
 });
--- a/browser/devtools/webconsole/test/test-console.html
+++ b/browser/devtools/webconsole/test/test-console.html
@@ -1,13 +1,17 @@
 <!DOCTYPE HTML>
 <html dir="ltr" xml:lang="en-US" lang="en-US"><head>
     <meta charset="utf-8">
     <title>Console test</title>
     <script type="text/javascript">
+      var fooObj = {
+        testProp: "testValue"
+      };
+
       function test() {
         var str = "Dolske Digs Bacon, Now and Forevermore."
         for (var i=0; i < 5; i++) {
           console.log(str);
         }
       }
       console.info("INLINE SCRIPT:");
       test();
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -3974,16 +3974,18 @@ JSTerm.prototype = {
     hud._outputQueue = [];
     hud._networkRequests = {};
     hud._repeatNodes = {};
 
     if (aClearStorage) {
       this.webConsoleClient.clearMessagesCache();
     }
 
+    this._sidebarDestroy();
+
     this.emit("messages-cleared");
   },
 
   /**
    * Remove all of the private messages from the Web Console output.
    *
    * This method emits the "private-messages-cleared" notification.
    */
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -423,16 +423,17 @@
 @RESPATH@/components/NetworkGeolocationProvider.manifest
 @RESPATH@/components/NetworkGeolocationProvider.js
 @RESPATH@/components/extensions.manifest
 @RESPATH@/components/addonManager.js
 @RESPATH@/components/amContentHandler.js
 @RESPATH@/components/amInstallTrigger.js
 @RESPATH@/components/amWebInstallListener.js
 @RESPATH@/components/nsBlocklistService.js
+@RESPATH@/components/nsBlocklistServiceContent.js
 #ifdef MOZ_UPDATER
 @RESPATH@/components/nsUpdateService.manifest
 @RESPATH@/components/nsUpdateService.js
 @RESPATH@/components/nsUpdateServiceStub.js
 #endif
 @RESPATH@/components/nsUpdateTimerManager.manifest
 @RESPATH@/components/nsUpdateTimerManager.js
 @RESPATH@/components/addoncompat.manifest
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -3253,18 +3253,19 @@ nsObjectLoadingContent::ShouldPlay(Fallb
   pluginHost->GetStateForType(mContentType, nsPluginHost::eExcludeNone,
                               &enabledState);
   if (nsIPluginTag::STATE_DISABLED == enabledState) {
     aReason = eFallbackDisabled;
     return false;
   }
 
   // Before we check permissions, get the blocklist state of this plugin to set
-  // the fallback reason correctly.
-  uint32_t blocklistState = nsIBlocklistService::STATE_NOT_BLOCKED;
+  // the fallback reason correctly. In the content process this will involve
+  // an ipc call to chrome.
+  uint32_t blocklistState = nsIBlocklistService::STATE_BLOCKED;
   pluginHost->GetBlocklistStateForType(mContentType,
                                        nsPluginHost::eExcludeNone,
                                        &blocklistState);
   if (blocklistState == nsIBlocklistService::STATE_BLOCKED) {
     // no override possible
     aReason = eFallbackBlocklisted;
     return false;
   }
--- a/dom/cache/Cache.cpp
+++ b/dom/cache/Cache.cpp
@@ -224,17 +224,20 @@ Cache::Cache(nsIGlobalObject* aGlobal, C
   MOZ_ASSERT(mActor);
   mActor->SetListener(this);
 }
 
 already_AddRefed<Promise>
 Cache::Match(const RequestOrUSVString& aRequest,
              const CacheQueryOptions& aOptions, ErrorResult& aRv)
 {
-  MOZ_ASSERT(mActor);
+  if (NS_WARN_IF(!mActor)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
 
   nsRefPtr<InternalRequest> ir = ToInternalRequest(aRequest, IgnoreBody, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   CacheQueryParams params;
   ToCacheQueryParams(params, aOptions);
@@ -248,17 +251,20 @@ Cache::Match(const RequestOrUSVString& a
 
   return ExecuteOp(args, aRv);
 }
 
 already_AddRefed<Promise>
 Cache::MatchAll(const Optional<RequestOrUSVString>& aRequest,
                 const CacheQueryOptions& aOptions, ErrorResult& aRv)
 {
-  MOZ_ASSERT(mActor);
+  if (NS_WARN_IF(!mActor)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
 
   CacheQueryParams params;
   ToCacheQueryParams(params, aOptions);
 
   AutoChildOpArgs args(this, CacheMatchAllArgs(void_t(), params));
 
   if (aRequest.WasPassed()) {
     nsRefPtr<InternalRequest> ir = ToInternalRequest(aRequest.Value(),
@@ -275,151 +281,171 @@ Cache::MatchAll(const Optional<RequestOr
 
   return ExecuteOp(args, aRv);
 }
 
 already_AddRefed<Promise>
 Cache::Add(JSContext* aContext, const RequestOrUSVString& aRequest,
            ErrorResult& aRv)
 {
+  if (NS_WARN_IF(!mActor)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
   if (!IsValidPutRequestMethod(aRequest, aRv)) {
     return nullptr;
   }
 
   GlobalObject global(aContext, mGlobal->GetGlobalJSObject());
   MOZ_ASSERT(!global.Failed());
 
   nsTArray<nsRefPtr<Request>> requestList(1);
   nsRefPtr<Request> request = Request::Constructor(global, aRequest,
                                                    RequestInit(), aRv);
-  if (aRv.Failed()) {
+  if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   nsAutoString url;
   request->GetUrl(url);
-  if (!IsValidPutRequestURL(url, aRv)) {
+  if (NS_WARN_IF(!IsValidPutRequestURL(url, aRv))) {
     return nullptr;
   }
 
   requestList.AppendElement(Move(request));
   return AddAll(global, Move(requestList), aRv);
 }
 
 already_AddRefed<Promise>
 Cache::AddAll(JSContext* aContext,
               const Sequence<OwningRequestOrUSVString>& aRequestList,
               ErrorResult& aRv)
 {
+  if (NS_WARN_IF(!mActor)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
   GlobalObject global(aContext, mGlobal->GetGlobalJSObject());
   MOZ_ASSERT(!global.Failed());
 
   nsTArray<nsRefPtr<Request>> requestList(aRequestList.Length());
   for (uint32_t i = 0; i < aRequestList.Length(); ++i) {
     RequestOrUSVString requestOrString;
 
     if (aRequestList[i].IsRequest()) {
       requestOrString.SetAsRequest() = aRequestList[i].GetAsRequest();
-      if (!IsValidPutRequestMethod(requestOrString.GetAsRequest(), aRv)) {
+      if (NS_WARN_IF(!IsValidPutRequestMethod(requestOrString.GetAsRequest(),
+                     aRv))) {
         return nullptr;
       }
     } else {
       requestOrString.SetAsUSVString().Rebind(
         aRequestList[i].GetAsUSVString().Data(),
         aRequestList[i].GetAsUSVString().Length());
     }
 
     nsRefPtr<Request> request = Request::Constructor(global, requestOrString,
                                                      RequestInit(), aRv);
-    if (aRv.Failed()) {
+    if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
 
     nsAutoString url;
     request->GetUrl(url);
-    if (!IsValidPutRequestURL(url, aRv)) {
+    if (NS_WARN_IF(!IsValidPutRequestURL(url, aRv))) {
       return nullptr;
     }
 
     requestList.AppendElement(Move(request));
   }
 
   return AddAll(global, Move(requestList), aRv);
 }
 
 already_AddRefed<Promise>
 Cache::Put(const RequestOrUSVString& aRequest, Response& aResponse,
            ErrorResult& aRv)
 {
-  MOZ_ASSERT(mActor);
+  if (NS_WARN_IF(!mActor)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
 
-  if (!IsValidPutRequestMethod(aRequest, aRv)) {
+  if (NS_WARN_IF(!IsValidPutRequestMethod(aRequest, aRv))) {
     return nullptr;
   }
 
   nsRefPtr<InternalRequest> ir = ToInternalRequest(aRequest, ReadBody, aRv);
-  if (aRv.Failed()) {
+  if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   AutoChildOpArgs args(this, CachePutAllArgs());
 
   args.Add(ir, ReadBody, TypeErrorOnInvalidScheme,
            aResponse, aRv);
-  if (aRv.Failed()) {
+  if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   return ExecuteOp(args, aRv);
 }
 
 already_AddRefed<Promise>
 Cache::Delete(const RequestOrUSVString& aRequest,
               const CacheQueryOptions& aOptions, ErrorResult& aRv)
 {
-  MOZ_ASSERT(mActor);
+  if (NS_WARN_IF(!mActor)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
 
   nsRefPtr<InternalRequest> ir = ToInternalRequest(aRequest, IgnoreBody, aRv);
-  if (aRv.Failed()) {
+  if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   CacheQueryParams params;
   ToCacheQueryParams(params, aOptions);
 
   AutoChildOpArgs args(this, CacheDeleteArgs(CacheRequest(), params));
 
   args.Add(ir, IgnoreBody, IgnoreInvalidScheme, aRv);
-  if (aRv.Failed()) {
+  if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   return ExecuteOp(args, aRv);
 }
 
 already_AddRefed<Promise>
 Cache::Keys(const Optional<RequestOrUSVString>& aRequest,
             const CacheQueryOptions& aOptions, ErrorResult& aRv)
 {
-  MOZ_ASSERT(mActor);
+  if (NS_WARN_IF(!mActor)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
 
   CacheQueryParams params;
   ToCacheQueryParams(params, aOptions);
 
   AutoChildOpArgs args(this, CacheKeysArgs(void_t(), params));
 
   if (aRequest.WasPassed()) {
     nsRefPtr<InternalRequest> ir = ToInternalRequest(aRequest.Value(),
                                                      IgnoreBody, aRv);
-    if (aRv.Failed()) {
+    if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
 
     args.Add(ir, IgnoreBody, IgnoreInvalidScheme, aRv);
-    if (aRv.Failed()) {
+    if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
   }
 
   return ExecuteOp(args, aRv);
 }
 
 // static
@@ -488,45 +514,45 @@ Cache::CreatePushStream(nsIAsyncInputStr
   MOZ_ASSERT(aStream);
   return mActor->CreatePushStream(aStream);
 }
 
 Cache::~Cache()
 {
   NS_ASSERT_OWNINGTHREAD(Cache);
   if (mActor) {
-    mActor->StartDestroy();
-    // DestroyInternal() is called synchronously by StartDestroy().  So we
-    // should have already cleared the mActor.
+    mActor->StartDestroyFromListener();
+    // DestroyInternal() is called synchronously by StartDestroyFromListener().
+    // So we should have already cleared the mActor.
     MOZ_ASSERT(!mActor);
   }
 }
 
 already_AddRefed<Promise>
 Cache::ExecuteOp(AutoChildOpArgs& aOpArgs, ErrorResult& aRv)
 {
   nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
-  if (!promise) {
+  if (NS_WARN_IF(!promise)) {
     return nullptr;
   }
 
-  mActor->ExecuteOp(mGlobal, promise, aOpArgs.SendAsOpArgs());
+  mActor->ExecuteOp(mGlobal, promise, this, aOpArgs.SendAsOpArgs());
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 Cache::AddAll(const GlobalObject& aGlobal,
               nsTArray<nsRefPtr<Request>>&& aRequestList, ErrorResult& aRv)
 {
   MOZ_ASSERT(mActor);
 
   // If there is no work to do, then resolve immediately
   if (aRequestList.IsEmpty()) {
     nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
-    if (!promise) {
+    if (NS_WARN_IF(!promise)) {
       return nullptr;
     }
 
     promise->MaybeResolve(JS::UndefinedHandleValue);
     return promise.forget();
   }
 
   nsAutoTArray<nsRefPtr<Promise>, 256> fetchList;
@@ -536,54 +562,58 @@ Cache::AddAll(const GlobalObject& aGloba
   // abandon our previous fetch calls.  In theory we could cancel them in the
   // future once fetch supports it.
 
   for (uint32_t i = 0; i < aRequestList.Length(); ++i) {
     RequestOrUSVString requestOrString;
     requestOrString.SetAsRequest() = aRequestList[i];
     nsRefPtr<Promise> fetch = FetchRequest(mGlobal, requestOrString,
                                            RequestInit(), aRv);
-    if (aRv.Failed()) {
+    if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
 
     fetchList.AppendElement(Move(fetch));
   }
 
   nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
-  if (aRv.Failed()) {
+  if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   nsRefPtr<FetchHandler> handler = new FetchHandler(mActor->GetFeature(), this,
                                                     Move(aRequestList), promise);
 
   nsRefPtr<Promise> fetchPromise = Promise::All(aGlobal, fetchList, aRv);
-  if (aRv.Failed()) {
+  if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
   fetchPromise->AppendNativeHandler(handler);
 
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 Cache::PutAll(const nsTArray<nsRefPtr<Request>>& aRequestList,
               const nsTArray<nsRefPtr<Response>>& aResponseList,
               ErrorResult& aRv)
 {
-  MOZ_ASSERT(mActor);
   MOZ_ASSERT(aRequestList.Length() == aResponseList.Length());
 
+  if (NS_WARN_IF(!mActor)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
   AutoChildOpArgs args(this, CachePutAllArgs());
 
   for (uint32_t i = 0; i < aRequestList.Length(); ++i) {
     nsRefPtr<InternalRequest> ir = aRequestList[i]->GetInternalRequest();
     args.Add(ir, ReadBody, TypeErrorOnInvalidScheme, *aResponseList[i], aRv);
-    if (aRv.Failed()) {
+    if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
   }
 
   return ExecuteOp(args, aRv);
 }
 
 } // namespace cache
--- a/dom/cache/CacheChild.cpp
+++ b/dom/cache/CacheChild.cpp
@@ -28,16 +28,17 @@ void
 DeallocPCacheChild(PCacheChild* aActor)
 {
   delete aActor;
 }
 
 CacheChild::CacheChild()
   : mListener(nullptr)
   , mNumChildActors(0)
+  , mDelayedDestroy(false)
 {
   MOZ_COUNT_CTOR(cache::CacheChild);
 }
 
 CacheChild::~CacheChild()
 {
   MOZ_COUNT_DTOR(cache::CacheChild);
   NS_ASSERT_OWNINGTHREAD(CacheChild);
@@ -59,58 +60,74 @@ CacheChild::ClearListener()
 {
   NS_ASSERT_OWNINGTHREAD(CacheChild);
   MOZ_ASSERT(mListener);
   mListener = nullptr;
 }
 
 void
 CacheChild::ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise,
-                      const CacheOpArgs& aArgs)
+                      nsISupports* aParent, const CacheOpArgs& aArgs)
 {
   mNumChildActors += 1;
   MOZ_ALWAYS_TRUE(SendPCacheOpConstructor(
-    new CacheOpChild(GetFeature(), aGlobal, aPromise), aArgs));
+    new CacheOpChild(GetFeature(), aGlobal, aParent, aPromise), aArgs));
 }
 
 CachePushStreamChild*
 CacheChild::CreatePushStream(nsIAsyncInputStream* aStream)
 {
   mNumChildActors += 1;
   auto actor = SendPCachePushStreamConstructor(
     new CachePushStreamChild(GetFeature(), aStream));
   MOZ_ASSERT(actor);
   return static_cast<CachePushStreamChild*>(actor);
 }
 
 void
+CacheChild::StartDestroyFromListener()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheChild);
+
+  // The listener should be held alive by any async operations, so if it
+  // is going away then there must not be any child actors.  This in turn
+  // ensures that StartDestroy() will not trigger the delayed path.
+  MOZ_ASSERT(!mNumChildActors);
+
+  StartDestroy();
+}
+
+void
 CacheChild::StartDestroy()
 {
+  NS_ASSERT_OWNINGTHREAD(CacheChild);
+
+  // If we have outstanding child actors, then don't destroy ourself yet.
+  // The child actors should be short lived and we should allow them to complete
+  // if possible.  NoteDeletedActor() will call back into this Shutdown()
+  // method when the last child actor is gone.
+  if (mNumChildActors) {
+    mDelayedDestroy = true;
+    return;
+  }
+
   nsRefPtr<Cache> listener = mListener;
 
   // StartDestroy() can get called from either Cache or the Feature.
   // Theoretically we can get double called if the right race happens.  Handle
   // that by just ignoring the second StartDestroy() call.
   if (!listener) {
     return;
   }
 
   listener->DestroyInternal(this);
 
   // Cache listener should call ClearListener() in DestroyInternal()
   MOZ_ASSERT(!mListener);
 
-  // If we have outstanding child actors, then don't destroy ourself yet.
-  // The child actors should be short lived and we should allow them to complete
-  // if possible.  SendTeardown() will be called when the count drops to zero
-  // in NoteDeletedActor().
-  if (mNumChildActors) {
-    return;
-  }
-
   // Start actor destruction from parent process
   unused << SendTeardown();
 }
 
 void
 CacheChild::ActorDestroy(ActorDestroyReason aReason)
 {
   NS_ASSERT_OWNINGTHREAD(CacheChild);
@@ -153,16 +170,16 @@ CacheChild::DeallocPCachePushStreamChild
   NoteDeletedActor();
   return true;
 }
 
 void
 CacheChild::NoteDeletedActor()
 {
   mNumChildActors -= 1;
-  if (!mNumChildActors && !mListener) {
-    unused << SendTeardown();
+  if (!mNumChildActors && mDelayedDestroy) {
+    StartDestroy();
   }
 }
 
 } // namespace cache
 } // namespace dom
 } // namesapce mozilla
--- a/dom/cache/CacheChild.h
+++ b/dom/cache/CacheChild.h
@@ -28,35 +28,37 @@ class CacheChild final : public PCacheCh
                        , public ActorChild
 {
 public:
   CacheChild();
   ~CacheChild();
 
   void SetListener(Cache* aListener);
 
-  // Must be called by the associated Cache listener in its ActorDestroy()
-  // method.  Also, Cache must Send__delete__() the actor in its destructor to
-  // trigger ActorDestroy() if it has not been called yet.
+  // Must be called by the associated Cache listener in its DestroyInternal()
+  // method.  Also, Cache must call StartDestroyFromListener() on the actor in
+  // its destructor to trigger ActorDestroy() if it has not been called yet.
   void ClearListener();
 
   void
   ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise,
-            const CacheOpArgs& aArgs);
+            nsISupports* aParent, const CacheOpArgs& aArgs);
 
   CachePushStreamChild*
   CreatePushStream(nsIAsyncInputStream* aStream);
 
+  // Our parent Listener object has gone out of scope and is being destroyed.
+  void StartDestroyFromListener();
+
+private:
   // ActorChild methods
 
-  // Synchronously call ActorDestroy on our Cache listener and then start the
-  // actor destruction asynchronously from the parent-side.
+  // Feature is trying to destroy due to worker shutdown.
   virtual void StartDestroy() override;
 
-private:
   // PCacheChild methods
   virtual void
   ActorDestroy(ActorDestroyReason aReason) override;
 
   virtual PCacheOpChild*
   AllocPCacheOpChild(const CacheOpArgs& aOpArgs) override;
 
   virtual bool
@@ -72,16 +74,17 @@ private:
   void
   NoteDeletedActor();
 
   // Use a weak ref so actor does not hold DOM object alive past content use.
   // The Cache object must call ClearListener() to null this before its
   // destroyed.
   Cache* MOZ_NON_OWNING_REF mListener;
   uint32_t mNumChildActors;
+  bool mDelayedDestroy;
 
   NS_DECL_OWNINGTHREAD
 };
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/cache/CacheOpChild.cpp
+++ b/dom/cache/CacheOpChild.cpp
@@ -52,21 +52,23 @@ AddFeatureToStreamChild(const CacheReque
   }
 
   AddFeatureToStreamChild(aRequest.body().get_CacheReadStream(), aFeature);
 }
 
 } // anonymous namespace
 
 CacheOpChild::CacheOpChild(Feature* aFeature, nsIGlobalObject* aGlobal,
-                           Promise* aPromise)
+                           nsISupports* aParent, Promise* aPromise)
   : mGlobal(aGlobal)
+  , mParent(aParent)
   , mPromise(aPromise)
 {
   MOZ_ASSERT(mGlobal);
+  MOZ_ASSERT(mParent);
   MOZ_ASSERT(mPromise);
 
   MOZ_ASSERT_IF(!NS_IsMainThread(), aFeature);
   SetFeature(aFeature);
 }
 
 CacheOpChild::~CacheOpChild()
 {
@@ -90,17 +92,17 @@ CacheOpChild::ActorDestroy(ActorDestroyR
 }
 
 bool
 CacheOpChild::Recv__delete__(const ErrorResult& aRv,
                              const CacheOpResult& aResult)
 {
   NS_ASSERT_OWNINGTHREAD(CacheOpChild);
 
-  if (aRv.Failed()) {
+  if (NS_WARN_IF(aRv.Failed())) {
     MOZ_ASSERT(aResult.type() == CacheOpResult::Tvoid_t);
     // TODO: Remove this const_cast (bug 1152078).
     // It is safe for now since this ErrorResult is handed off to us by IPDL
     // and is thrown into the trash afterwards.
     mPromise->MaybeReject(const_cast<ErrorResult&>(aRv));
     mPromise = nullptr;
     return true;
   }
--- a/dom/cache/CacheOpChild.h
+++ b/dom/cache/CacheOpChild.h
@@ -26,17 +26,18 @@ class CacheOpChild final : public PCache
                          , public TypeUtils
 {
   friend class CacheChild;
   friend class CacheStorageChild;
 
 private:
   // This class must be constructed by CacheChild or CacheStorageChild using
   // their ExecuteOp() factory method.
-  CacheOpChild(Feature* aFeature, nsIGlobalObject* aGlobal, Promise* aPromise);
+  CacheOpChild(Feature* aFeature, nsIGlobalObject* aGlobal,
+               nsISupports* aParent, Promise* aPromise);
   ~CacheOpChild();
 
   // PCacheOpChild methods
   virtual void
   ActorDestroy(ActorDestroyReason aReason) override;
 
   virtual bool
   Recv__delete__(const ErrorResult& aRv, const CacheOpResult& aResult) override;
@@ -63,16 +64,19 @@ private:
 
   void
   HandleResponseList(const nsTArray<CacheResponse>& aResponseList);
 
   void
   HandleRequestList(const nsTArray<CacheRequest>& aRequestList);
 
   nsCOMPtr<nsIGlobalObject> mGlobal;
+  // Hold the parent Cache or CacheStorage object alive until this async
+  // operation completes.
+  nsCOMPtr<nsISupports> mParent;
   nsRefPtr<Promise> mPromise;
 
   NS_DECL_OWNINGTHREAD
 };
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/CacheOpParent.cpp
+++ b/dom/cache/CacheOpParent.cpp
@@ -159,17 +159,17 @@ CacheOpParent::OnOpComplete(ErrorResult&
                             StreamList* aStreamList)
 {
   NS_ASSERT_OWNINGTHREAD(CacheOpParent);
   MOZ_ASSERT(mIpcManager);
   MOZ_ASSERT(mManager);
 
   // Never send an op-specific result if we have an error.  Instead, send
   // void_t() to ensure that we don't leak actors on the child side.
-  if (aRv.Failed()) {
+  if (NS_WARN_IF(aRv.Failed())) {
     unused << Send__delete__(this, aRv, void_t());
     aRv.SuppressException(); // We serialiazed it, as best we could.
     return;
   }
 
   // The result must contain the appropriate type at this point.  It may
   // or may not contain the additional result data yet.  For types that
   // do not need special processing, it should already be set.  If the
--- a/dom/cache/CachePushStreamChild.cpp
+++ b/dom/cache/CachePushStreamChild.cpp
@@ -113,18 +113,19 @@ void
 CachePushStreamChild::Start()
 {
   DoRead();
 }
 
 void
 CachePushStreamChild::StartDestroy()
 {
-  // called if we are running on a Worker and the thread gets shutdown
-  OnEnd(NS_ERROR_ABORT);
+  // The worker has signaled its shutting down, but continue streaming.  The
+  // Cache is now designed to hold the worker open until all async operations
+  // complete.
 }
 
 void
 CachePushStreamChild::ActorDestroy(ActorDestroyReason aReason)
 {
   NS_ASSERT_OWNINGTHREAD(CachePushStreamChild);
 
   // If the parent side runs into a problem then the actor will be destroyed.
--- a/dom/cache/CacheStorage.cpp
+++ b/dom/cache/CacheStorage.cpp
@@ -161,40 +161,40 @@ CacheStorage::CacheStorage(Namespace aNa
     ActorCreated(actor);
     return;
   }
 
   // Otherwise we must begin the PBackground initialization process and
   // wait for the async ActorCreated() callback.
   MOZ_ASSERT(NS_IsMainThread());
   bool ok = BackgroundChild::GetOrCreateForCurrentThread(this);
-  if (!ok) {
+  if (NS_WARN_IF(!ok)) {
     ActorFailed();
   }
 }
 
 already_AddRefed<Promise>
 CacheStorage::Match(const RequestOrUSVString& aRequest,
                     const CacheQueryOptions& aOptions, ErrorResult& aRv)
 {
   NS_ASSERT_OWNINGTHREAD(CacheStorage);
 
-  if (mFailedActor) {
+  if (NS_WARN_IF(mFailedActor)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsRefPtr<InternalRequest> request = ToInternalRequest(aRequest, IgnoreBody,
                                                         aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
-  if (!promise) {
+  if (NS_WARN_IF(!promise)) {
     return nullptr;
   }
 
   CacheQueryParams params;
   ToCacheQueryParams(params, aOptions);
 
   nsAutoPtr<Entry> entry(new Entry());
   entry->mPromise = promise;
@@ -207,23 +207,23 @@ CacheStorage::Match(const RequestOrUSVSt
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 CacheStorage::Has(const nsAString& aKey, ErrorResult& aRv)
 {
   NS_ASSERT_OWNINGTHREAD(CacheStorage);
 
-  if (mFailedActor) {
+  if (NS_WARN_IF(mFailedActor)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
-  if (!promise) {
+  if (NS_WARN_IF(!promise)) {
     return nullptr;
   }
 
   nsAutoPtr<Entry> entry(new Entry());
   entry->mPromise = promise;
   entry->mArgs = StorageHasArgs(nsString(aKey));
 
   mPendingRequests.AppendElement(entry.forget());
@@ -232,23 +232,23 @@ CacheStorage::Has(const nsAString& aKey,
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 CacheStorage::Open(const nsAString& aKey, ErrorResult& aRv)
 {
   NS_ASSERT_OWNINGTHREAD(CacheStorage);
 
-  if (mFailedActor) {
+  if (NS_WARN_IF(mFailedActor)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
-  if (!promise) {
+  if (NS_WARN_IF(!promise)) {
     return nullptr;
   }
 
   nsAutoPtr<Entry> entry(new Entry());
   entry->mPromise = promise;
   entry->mArgs = StorageOpenArgs(nsString(aKey));
 
   mPendingRequests.AppendElement(entry.forget());
@@ -257,23 +257,23 @@ CacheStorage::Open(const nsAString& aKey
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 CacheStorage::Delete(const nsAString& aKey, ErrorResult& aRv)
 {
   NS_ASSERT_OWNINGTHREAD(CacheStorage);
 
-  if (mFailedActor) {
+  if (NS_WARN_IF(mFailedActor)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
-  if (!promise) {
+  if (NS_WARN_IF(!promise)) {
     return nullptr;
   }
 
   nsAutoPtr<Entry> entry(new Entry());
   entry->mPromise = promise;
   entry->mArgs = StorageDeleteArgs(nsString(aKey));
 
   mPendingRequests.AppendElement(entry.forget());
@@ -282,23 +282,23 @@ CacheStorage::Delete(const nsAString& aK
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 CacheStorage::Keys(ErrorResult& aRv)
 {
   NS_ASSERT_OWNINGTHREAD(CacheStorage);
 
-  if (mFailedActor) {
+  if (NS_WARN_IF(mFailedActor)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
-  if (!promise) {
+  if (NS_WARN_IF(!promise)) {
     return nullptr;
   }
 
   nsAutoPtr<Entry> entry(new Entry());
   entry->mPromise = promise;
   entry->mArgs = StorageKeysArgs();
 
   mPendingRequests.AppendElement(entry.forget());
@@ -408,19 +408,19 @@ CacheStorage::CreatePushStream(nsIAsyncI
   // This is true because CacheStorage always uses IgnoreBody for requests.
   MOZ_CRASH("CacheStorage should never create a push stream.");
 }
 
 CacheStorage::~CacheStorage()
 {
   NS_ASSERT_OWNINGTHREAD(CacheStorage);
   if (mActor) {
-    mActor->StartDestroy();
-    // DestroyInternal() is called synchronously by StartDestroy().  So we
-    // should have already cleared the mActor.
+    mActor->StartDestroyFromListener();
+    // DestroyInternal() is called synchronously by StartDestroyFromListener().
+    // So we should have already cleared the mActor.
     MOZ_ASSERT(!mActor);
   }
 }
 
 void
 CacheStorage::MaybeRunPendingRequests()
 {
   if (!mActor) {
@@ -429,20 +429,20 @@ CacheStorage::MaybeRunPendingRequests()
 
   for (uint32_t i = 0; i < mPendingRequests.Length(); ++i) {
     ErrorResult rv;
     nsAutoPtr<Entry> entry(mPendingRequests[i].forget());
     AutoChildOpArgs args(this, entry->mArgs);
     if (entry->mRequest) {
       args.Add(entry->mRequest, IgnoreBody, IgnoreInvalidScheme, rv);
     }
-    if (rv.Failed()) {
+    if (NS_WARN_IF(rv.Failed())) {
       entry->mPromise->MaybeReject(rv);
       continue;
     }
-    mActor->ExecuteOp(mGlobal, entry->mPromise, args.SendAsOpArgs());
+    mActor->ExecuteOp(mGlobal, entry->mPromise, this, args.SendAsOpArgs());
   }
   mPendingRequests.Clear();
 }
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/CacheStorageChild.cpp
+++ b/dom/cache/CacheStorageChild.cpp
@@ -20,16 +20,17 @@ void
 DeallocPCacheStorageChild(PCacheStorageChild* aActor)
 {
   delete aActor;
 }
 
 CacheStorageChild::CacheStorageChild(CacheStorage* aListener, Feature* aFeature)
   : mListener(aListener)
   , mNumChildActors(0)
+  , mDelayedDestroy(false)
 {
   MOZ_COUNT_CTOR(cache::CacheStorageChild);
   MOZ_ASSERT(mListener);
 
   SetFeature(aFeature);
 }
 
 CacheStorageChild::~CacheStorageChild()
@@ -44,50 +45,64 @@ CacheStorageChild::ClearListener()
 {
   NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
   MOZ_ASSERT(mListener);
   mListener = nullptr;
 }
 
 void
 CacheStorageChild::ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise,
-                             const CacheOpArgs& aArgs)
+                             nsISupports* aParent, const CacheOpArgs& aArgs)
 {
   mNumChildActors += 1;
   unused << SendPCacheOpConstructor(
-    new CacheOpChild(GetFeature(), aGlobal, aPromise), aArgs);
+    new CacheOpChild(GetFeature(), aGlobal, aParent, aPromise), aArgs);
+}
+
+void
+CacheStorageChild::StartDestroyFromListener()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
+
+  // The listener should be held alive by any async operations, so if it
+  // is going away then there must not be any child actors.  This in turn
+  // ensures that StartDestroy() will not trigger the delayed path.
+  MOZ_ASSERT(!mNumChildActors);
+
+  StartDestroy();
 }
 
 void
 CacheStorageChild::StartDestroy()
 {
   NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
 
+  // If we have outstanding child actors, then don't destroy ourself yet.
+  // The child actors should be short lived and we should allow them to complete
+  // if possible.  NoteDeletedActor() will call back into this Shutdown()
+  // method when the last child actor is gone.
+  if (mNumChildActors) {
+    mDelayedDestroy = true;
+    return;
+  }
+
   nsRefPtr<CacheStorage> listener = mListener;
 
   // StartDestroy() can get called from either CacheStorage or the Feature.
   // Theoretically we can get double called if the right race happens.  Handle
   // that by just ignoring the second StartDestroy() call.
   if (!listener) {
     return;
   }
 
   listener->DestroyInternal(this);
 
   // CacheStorage listener should call ClearListener() in DestroyInternal()
   MOZ_ASSERT(!mListener);
 
-  // If we have outstanding child actors, then don't destroy ourself yet.
-  // The child actors should be short lived and we should allow them to complete
-  // if possible.  SendTeardown() will be called when the count drops to zero
-  // in NoteDeletedActor().
-  if (mNumChildActors) {
-    return;
-  }
-
   // Start actor destruction from parent process
   unused << SendTeardown();
 }
 
 void
 CacheStorageChild::ActorDestroy(ActorDestroyReason aReason)
 {
   NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
@@ -116,16 +131,16 @@ CacheStorageChild::DeallocPCacheOpChild(
   return true;
 }
 
 void
 CacheStorageChild::NoteDeletedActor()
 {
   MOZ_ASSERT(mNumChildActors);
   mNumChildActors -= 1;
-  if (!mNumChildActors && !mListener) {
-    unused << SendTeardown();
+  if (!mNumChildActors && mDelayedDestroy) {
+    StartDestroy();
   }
 }
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/CacheStorageChild.h
+++ b/dom/cache/CacheStorageChild.h
@@ -28,32 +28,34 @@ class Feature;
 class CacheStorageChild final : public PCacheStorageChild
                               , public ActorChild
 {
 public:
   CacheStorageChild(CacheStorage* aListener, Feature* aFeature);
   ~CacheStorageChild();
 
   // Must be called by the associated CacheStorage listener in its
-  // ActorDestroy() method.  Also, CacheStorage must call SendDestroy() on the
-  // actor in its destructor to trigger ActorDestroy() if it has not been
-  // called yet.
+  // DestroyInternal() method.  Also, CacheStorage must call
+  // SendDestroyFromListener() on the actor in its destructor to trigger
+  // ActorDestroy() if it has not been called yet.
   void ClearListener();
 
   void
   ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise,
-            const CacheOpArgs& aArgs);
+            nsISupports* aParent, const CacheOpArgs& aArgs);
 
+  // Our parent Listener object has gone out of scope and is being destroyed.
+  void StartDestroyFromListener();
+
+private:
   // ActorChild methods
 
-  // Synchronously call ActorDestroy on our CacheStorage listener and then start
-  // the actor destruction asynchronously from the parent-side.
+  // Feature is trying to destroy due to worker shutdown.
   virtual void StartDestroy() override;
 
-private:
   // PCacheStorageChild methods
   virtual void ActorDestroy(ActorDestroyReason aReason) override;
 
   virtual PCacheOpChild*
   AllocPCacheOpChild(const CacheOpArgs& aOpArgs) override;
 
   virtual bool
   DeallocPCacheOpChild(PCacheOpChild* aActor) override;
@@ -62,16 +64,17 @@ private:
   void
   NoteDeletedActor();
 
   // Use a weak ref so actor does not hold DOM object alive past content use.
   // The CacheStorage object must call ClearListener() to null this before its
   // destroyed.
   CacheStorage* MOZ_NON_OWNING_REF mListener;
   uint32_t mNumChildActors;
+  bool mDelayedDestroy;
 
   NS_DECL_OWNINGTHREAD
 };
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/cache/CacheStorageParent.cpp
+++ b/dom/cache/CacheStorageParent.cpp
@@ -95,17 +95,17 @@ CacheStorageParent::RecvPCacheOpConstruc
   auto actor = static_cast<CacheOpParent*>(aActor);
 
   if (mVerifier) {
     MOZ_ASSERT(!mManagerId);
     actor->WaitForVerification(mVerifier);
     return true;
   }
 
-  if (NS_FAILED(mVerifiedStatus)) {
+  if (NS_WARN_IF(NS_FAILED(mVerifiedStatus))) {
     unused << CacheOpParent::Send__delete__(actor, ErrorResult(mVerifiedStatus),
                                             void_t());
     return true;
   }
 
   MOZ_ASSERT(mManagerId);
   actor->Execute(mManagerId);
   return true;
--- a/dom/cache/Feature.cpp
+++ b/dom/cache/Feature.cpp
@@ -8,17 +8,17 @@
 
 #include "mozilla/dom/cache/ActorChild.h"
 #include "WorkerPrivate.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
-using mozilla::dom::workers::Running;
+using mozilla::dom::workers::Canceling;
 using mozilla::dom::workers::Status;
 using mozilla::dom::workers::WorkerPrivate;
 
 // static
 already_AddRefed<Feature>
 Feature::Create(WorkerPrivate* aWorkerPrivate)
 {
   MOZ_ASSERT(aWorkerPrivate);
@@ -68,17 +68,17 @@ Feature::Notified() const
   return mNotified;
 }
 
 bool
 Feature::Notify(JSContext* aCx, Status aStatus)
 {
   NS_ASSERT_OWNINGTHREAD(Feature);
 
-  if (aStatus <= Running || mNotified) {
+  if (aStatus < Canceling || mNotified) {
     return true;
   }
 
   mNotified = true;
 
   // Start the asynchronous destruction of our actors.  These will call back
   // into RemoveActor() once the actor is destroyed.
   for (uint32_t i = 0; i < mActorList.Length(); ++i) {
--- a/dom/cache/Manager.cpp
+++ b/dom/cache/Manager.cpp
@@ -1618,17 +1618,17 @@ Manager::RemoveStreamList(StreamList* aS
 void
 Manager::ExecuteCacheOp(Listener* aListener, CacheId aCacheId,
                         const CacheOpArgs& aOpArgs)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   MOZ_ASSERT(aListener);
   MOZ_ASSERT(aOpArgs.type() != CacheOpArgs::TCachePutAllArgs);
 
-  if (mState == Closing) {
+  if (NS_WARN_IF(mState == Closing)) {
     aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t());
     return;
   }
 
   nsRefPtr<Context> context = mContext;
   MOZ_ASSERT(!context->IsCanceled());
 
   nsRefPtr<StreamList> streamList = new StreamList(this, context);
@@ -1662,17 +1662,17 @@ Manager::ExecuteCacheOp(Listener* aListe
 
 void
 Manager::ExecuteStorageOp(Listener* aListener, Namespace aNamespace,
                           const CacheOpArgs& aOpArgs)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   MOZ_ASSERT(aListener);
 
-  if (mState == Closing) {
+  if (NS_WARN_IF(mState == Closing)) {
     aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t());
     return;
   }
 
   nsRefPtr<Context> context = mContext;
   MOZ_ASSERT(!context->IsCanceled());
 
   nsRefPtr<StreamList> streamList = new StreamList(this, context);
@@ -1711,17 +1711,17 @@ void
 Manager::ExecutePutAll(Listener* aListener, CacheId aCacheId,
                        const nsTArray<CacheRequestResponse>& aPutList,
                        const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
                        const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   MOZ_ASSERT(aListener);
 
-  if (mState == Closing) {
+  if (NS_WARN_IF(mState == Closing)) {
     aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), CachePutAllResult());
     return;
   }
 
   nsRefPtr<Context> context = mContext;
   MOZ_ASSERT(!context->IsCanceled());
 
   ListenerId listenerId = SaveListener(aListener);
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -156,16 +156,19 @@
 #include "nsIDocShell.h"
 #include "nsDocShell.h"
 #include "mozilla/net/NeckoMessageUtils.h"
 #include "gfxPrefs.h"
 #include "prio.h"
 #include "private/pprio.h"
 #include "ContentProcessManager.h"
 #include "mozilla/psm/PSMContentListener.h"
+#include "nsPluginHost.h"
+#include "nsPluginTags.h"
+#include "nsIBlocklistService.h"
 
 #include "nsIBidiKeyboard.h"
 
 #if defined(ANDROID) || defined(LINUX)
 #include "nsSystemInfo.h"
 #endif
 
 #if defined(XP_LINUX)
@@ -1079,16 +1082,35 @@ ContentParent::RecvConnectPluginBridge(c
     // We don't need to get the run ID for the plugin, since we already got it
     // in the first call to SetupBridge in RecvLoadPlugin, so we pass in a dummy
     // pointer and just throw it away.
     uint32_t dummy = 0;
     return mozilla::plugins::SetupBridge(aPluginId, this, true, aRv, &dummy);
 }
 
 bool
+ContentParent::RecvGetBlocklistState(const uint32_t& aPluginId,
+                                     uint32_t* aState)
+{
+    *aState = nsIBlocklistService::STATE_BLOCKED;
+
+    nsRefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+    if (!pluginHost) {
+        return false;
+    }
+    nsPluginTag* tag =  pluginHost->PluginWithId(aPluginId);
+
+    if (!tag) {
+        return false;
+    }
+
+    return NS_SUCCEEDED(tag->GetBlocklistState(aState));
+}
+
+bool
 ContentParent::RecvFindPlugins(const uint32_t& aPluginEpoch,
                                nsTArray<PluginTag>* aPlugins,
                                uint32_t* aNewPluginEpoch)
 {
     return mozilla::plugins::FindPluginsForContent(aPluginEpoch, aPlugins, aNewPluginEpoch);
 }
 
 /*static*/ TabParent*
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -168,16 +168,17 @@ public:
     virtual bool RecvCreateGMPService() override;
     virtual bool RecvGetGMPPluginVersionForAPI(const nsCString& aAPI,
                                                nsTArray<nsCString>&& aTags,
                                                bool* aHasPlugin,
                                                nsCString* aVersion) override;
 
     virtual bool RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID) override;
     virtual bool RecvConnectPluginBridge(const uint32_t& aPluginId, nsresult* aRv) override;
+    virtual bool RecvGetBlocklistState(const uint32_t& aPluginId, uint32_t* aIsBlocklisted) override;
     virtual bool RecvFindPlugins(const uint32_t& aPluginEpoch,
                                  nsTArray<PluginTag>* aPlugins,
                                  uint32_t* aNewPluginEpoch) override;
 
     NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ContentParent, nsIObserver)
 
     NS_DECL_CYCLE_COLLECTING_ISUPPORTS
     NS_DECL_NSIOBSERVER
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -695,16 +695,21 @@ parent:
      * This call is used by asynchronous plugin instantiation to notify the
      * content parent that it is now safe to initiate the plugin bridge for
      * the specified plugin id. When this call returns, the requested bridge
      * connection has been made.
      */
     sync ConnectPluginBridge(uint32_t aPluginId) returns (nsresult rv);
 
     /**
+     * Return the current blocklist state for a particular plugin.
+     */
+    sync GetBlocklistState(uint32_t aPluginId) returns (uint32_t aState);
+
+    /**
      * This call returns the set of plugins loaded in the chrome
      * process. However, in many cases this set will not have changed since the
      * last FindPlugins message. Consequently, the chrome process increments an
      * epoch number every time the set of plugins changes. The content process
      * sends up the last epoch it observed. If the epochs are the same, the
      * chrome process returns no plugins. Otherwise it returns a complete list.
      *
      * |pluginEpoch| is the epoch last observed by the content
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1845,16 +1845,18 @@ MediaManager::GetUserMedia(
       // the requesting document is not from a host on the whitelist, or
       // we're on Mac OSX 10.6 and WinXP until proved that they work
       if (!Preferences::GetBool(((src == dom::MediaSourceEnum::Browser)?
                                 "media.getusermedia.browser.enabled" :
                                 "media.getusermedia.screensharing.enabled"),
                                 false) ||
 #if defined(XP_MACOSX) || defined(XP_WIN)
           (
+            // Allow tab sharing for all platforms including XP and OSX 10.6
+            (src != dom::MediaSourceEnum::Browser) &&
             !Preferences::GetBool("media.getusermedia.screensharing.allow_on_old_platforms",
                                   false) &&
 #if defined(XP_MACOSX)
             !nsCocoaFeatures::OnLionOrLater()
 #endif
 #if defined (XP_WIN)
             !IsVistaOrLater()
 #endif
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -105,16 +105,18 @@
 #include <android/log.h>
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args)
 #endif
 
 #if MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 
+#include "npapi.h"
+
 using namespace mozilla;
 using mozilla::TimeStamp;
 using mozilla::plugins::PluginTag;
 using mozilla::plugins::PluginAsyncSurrogate;
 
 // Null out a strong ref to a linked list iteratively to avoid
 // exhausting the stack (bug 486349).
 #define NS_ITERATIVE_UNREF_LIST(type_, list_, mNext_)                \
@@ -1049,17 +1051,16 @@ nsPluginHost::GetBlocklistStateForType(c
                                        uint32_t aExcludeFlags,
                                        uint32_t *aState)
 {
   nsCOMPtr<nsIPluginTag> tag;
   nsresult rv = GetPluginTagForType(aMimeType,
                                     aExcludeFlags,
                                     getter_AddRefs(tag));
   NS_ENSURE_SUCCESS(rv, rv);
-
   return tag->GetBlocklistState(aState);
 }
 
 NS_IMETHODIMP
 nsPluginHost::GetPermissionStringForType(const nsACString &aMimeType,
                                          uint32_t aExcludeFlags,
                                          nsACString &aPermissionString)
 {
@@ -1270,16 +1271,23 @@ nsPluginHost::GetPluginForContentProcess
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
   // If plugins haven't been scanned yet, do so now
   LoadPlugins();
 
   nsPluginTag* pluginTag = PluginWithId(aPluginId);
   if (pluginTag) {
+    // When setting up a bridge, double check with chrome to see if this plugin
+    // is blocked hard. Note this does not protect against vulnerable plugins
+    // that the user has explicitly allowed. :(
+    if (pluginTag->IsBlocklisted()) {
+      return NS_ERROR_PLUGIN_BLOCKLISTED;
+    }
+
     nsresult rv = EnsurePluginLoaded(pluginTag);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     // We only get here if a content process doesn't have a PluginModuleParent
     // for the given plugin already. Therefore, this counter is counting the
     // number of outstanding PluginModuleParents for the plugin, excluding the
--- a/dom/plugins/base/nsPluginHost.h
+++ b/dom/plugins/base/nsPluginHost.h
@@ -6,17 +6,16 @@
 #ifndef nsPluginHost_h_
 #define nsPluginHost_h_
 
 #include "nsIPluginHost.h"
 #include "nsIObserver.h"
 #include "nsCOMPtr.h"
 #include "prlink.h"
 #include "prclist.h"
-#include "npapi.h"
 #include "nsIPluginTag.h"
 #include "nsPluginsDir.h"
 #include "nsPluginDirServiceProvider.h"
 #include "nsAutoPtr.h"
 #include "nsWeakPtr.h"
 #include "nsIPrompt.h"
 #include "nsWeakReference.h"
 #include "MainThreadUtils.h"
@@ -25,16 +24,17 @@
 #include "nsITimer.h"
 #include "nsPluginTags.h"
 #include "nsPluginPlayPreviewInfo.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIIDNService.h"
 #include "nsCRT.h"
 
 #ifdef XP_WIN
+#include <minwindef.h>
 #include "nsIWindowsRegKey.h"
 #endif
 
 namespace mozilla {
 namespace plugins {
 class PluginAsyncSurrogate;
 class PluginTag;
 } // namespace mozilla
@@ -47,16 +47,20 @@ class nsPluginNativeWindow;
 class nsObjectLoadingContent;
 class nsPluginInstanceOwner;
 class nsPluginUnloadRunnable;
 class nsNPAPIPluginInstance;
 class nsNPAPIPluginStreamListener;
 class nsIPluginInstanceOwner;
 class nsIInputStream;
 class nsIStreamListener;
+#ifndef npapi_h_
+struct _NPP;
+typedef _NPP* NPP;
+#endif
 
 class nsInvalidPluginTag : public nsISupports
 {
   virtual ~nsInvalidPluginTag();
 public:
   explicit nsInvalidPluginTag(const char* aFullPath, int64_t aLastModifiedTime = 0);
 
   NS_DECL_ISUPPORTS
--- a/dom/plugins/base/nsPluginTags.cpp
+++ b/dom/plugins/base/nsPluginTags.cpp
@@ -15,16 +15,17 @@
 #include "nsIPlatformCharset.h"
 #include "nsPluginLogging.h"
 #include "nsNPAPIPlugin.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/unused.h"
 #include <cctype>
 #include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/ContentChild.h"
 
 using mozilla::dom::EncodingUtils;
 using namespace mozilla;
 
 // These legacy flags are used in the plugin registry. The states are now
 // stored in prefs, but we still need to be able to import them.
 #define NS_PLUGIN_FLAG_ENABLED      0x0001    // is this plugin enabled?
 // no longer used                   0x0002    // reuse only if regenerating pluginreg.dat
@@ -640,37 +641,43 @@ void nsPluginTag::ImportFlagsToPrefs(uin
 NS_IMETHODIMP
 nsPluginTag::GetBlocklistState(uint32_t *aResult)
 {
   if (mCachedBlocklistStateValid) {
     *aResult = mCachedBlocklistState;
     return NS_OK;
   }
 
-  nsCOMPtr<nsIBlocklistService> blocklist =
-    do_GetService("@mozilla.org/extensions/blocklist;1");
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    *aResult = nsIBlocklistService::STATE_BLOCKED;
+    dom::ContentChild* cp = dom::ContentChild::GetSingleton();
+    if (!cp->SendGetBlocklistState(mId, aResult)) {
+      return NS_OK;
+    }
+  } else {
+    nsCOMPtr<nsIBlocklistService> blocklist =
+      do_GetService("@mozilla.org/extensions/blocklist;1");
 
-  if (!blocklist) {
-    *aResult = nsIBlocklistService::STATE_NOT_BLOCKED;
-    return NS_OK;
+    if (!blocklist) {
+      *aResult = nsIBlocklistService::STATE_NOT_BLOCKED;
+      return NS_OK;
+    }
+
+    // The EmptyString()s are so we use the currently running application
+    // and toolkit versions
+    if (NS_FAILED(blocklist->GetPluginBlocklistState(this, EmptyString(),
+                                                     EmptyString(), aResult))) {
+      *aResult = nsIBlocklistService::STATE_NOT_BLOCKED;
+      return NS_OK;
+    }
   }
 
-  // The EmptyString()s are so we use the currently running application
-  // and toolkit versions
-  uint32_t state;
-  if (NS_FAILED(blocklist->GetPluginBlocklistState(this, EmptyString(),
-                                                   EmptyString(), &state))) {
-    *aResult = nsIBlocklistService::STATE_NOT_BLOCKED;
-    return NS_OK;
-  }
-
-  MOZ_ASSERT(state <= UINT16_MAX);
-  mCachedBlocklistState = (uint16_t) state;
+  MOZ_ASSERT(*aResult <= UINT16_MAX);
+  mCachedBlocklistState = (uint16_t) *aResult;
   mCachedBlocklistStateValid = true;
-  *aResult = state;
   return NS_OK;
 }
 
 bool
 nsPluginTag::HasMimeType(const nsACString & aMimeType) const
 {
   return mMimeTypes.Contains(aMimeType,
                              nsCaseInsensitiveCStringArrayComparator());
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -1038,26 +1038,29 @@ ServiceWorkerManager::AppendPendingOpera
 
 /*
  * Used to handle ExtendableEvent::waitUntil() and proceed with
  * installation/activation.
  */
 class LifecycleEventPromiseHandler final : public PromiseNativeHandler
 {
   nsMainThreadPtrHandle<ContinueLifecycleTask> mTask;
+  nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
   bool mActivateImmediately;
 
   virtual
   ~LifecycleEventPromiseHandler()
   { }
 
 public:
   LifecycleEventPromiseHandler(const nsMainThreadPtrHandle<ContinueLifecycleTask>& aTask,
+                               const nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
                                bool aActivateImmediately)
     : mTask(aTask)
+    , mServiceWorker(aServiceWorker)
     , mActivateImmediately(aActivateImmediately)
   {
     MOZ_ASSERT(!NS_IsMainThread());
   }
 
   void
   ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
   {
@@ -1130,17 +1133,17 @@ LifecycleEventWorkerRunnable::DispatchLi
                                        JS::UndefinedHandleValue, result);
   }
 
   if (result.Failed()) {
     return false;
   }
 
   nsRefPtr<LifecycleEventPromiseHandler> handler =
-    new LifecycleEventPromiseHandler(mTask, false /* activateImmediately */);
+    new LifecycleEventPromiseHandler(mTask, mServiceWorker, false /* activateImmediately */);
   waitUntilPromise->AppendNativeHandler(handler);
   return true;
 }
 
 void
 ServiceWorkerRegistrationInfo::TryToActivate()
 {
   if (!IsControllingDocuments()) {
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -13,16 +13,17 @@
 #include "nsIScrollableFrame.h"
 #include "nsLayoutUtils.h"
 #include "nsIDOMElement.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsIDOMWindow.h"
 #include "nsRefreshDriver.h"
+#include "nsView.h"
 
 #define APZCCH_LOG(...)
 // #define APZCCH_LOG(...) printf_stderr("APZCCH: " __VA_ARGS__)
 
 namespace mozilla {
 namespace layers {
 
 using dom::TabParent;
@@ -494,33 +495,52 @@ GetDisplayportElementFor(nsIScrollableFr
   // nearest ancestor scrollable frame. The element corresponding to this
   // frame should be the one with the displayport set on it, so find that
   // element and return it.
   nsIContent* content = scrolledFrame->GetContent();
   MOZ_ASSERT(content->IsElement()); // roc says this must be true
   return content->AsElement();
 }
 
+
+static dom::Element*
+GetRootDocumentElementFor(nsIWidget* aWidget)
+{
+  // This returns the root element that ChromeProcessController sets the
+  // displayport on during initialization.
+  if (nsView* view = nsView::GetViewFor(aWidget)) {
+    if (nsIPresShell* shell = view->GetPresShell()) {
+      MOZ_ASSERT(shell->GetDocument());
+      return shell->GetDocument()->GetDocumentElement();
+    }
+  }
+  return nullptr;
+}
+
 // Determine the scrollable target frame for the given point and add it to
 // the target list. If the frame doesn't have a displayport, set one.
 // Return whether or not a displayport was set.
 static bool
 PrepareForSetTargetAPZCNotification(nsIWidget* aWidget,
                                     const ScrollableLayerGuid& aGuid,
                                     nsIFrame* aRootFrame,
                                     const LayoutDeviceIntPoint& aRefPoint,
                                     nsTArray<ScrollableLayerGuid>* aTargets)
 {
   ScrollableLayerGuid guid(aGuid.mLayersId, 0, FrameMetrics::NULL_SCROLL_ID);
   nsPoint point =
     nsLayoutUtils::GetEventCoordinatesRelativeTo(aWidget, aRefPoint, aRootFrame);
   nsIFrame* target =
     nsLayoutUtils::GetFrameForPoint(aRootFrame, point, nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME);
   nsIScrollableFrame* scrollAncestor = GetScrollableAncestorFrame(target);
-  nsCOMPtr<dom::Element> dpElement = GetDisplayportElementFor(scrollAncestor);
+
+  // Assuming that if there's no scrollAncestor, there's already a displayPort.
+  nsCOMPtr<dom::Element> dpElement = scrollAncestor
+    ? GetDisplayportElementFor(scrollAncestor)
+    : GetRootDocumentElementFor(aWidget);
 
   nsAutoString dpElementDesc;
   if (dpElement) {
     dpElement->Describe(dpElementDesc);
   }
   APZCCH_LOG("For event at %s found scrollable element %p (%s)\n",
       Stringify(aRefPoint).c_str(), dpElement.get(),
       NS_LossyConvertUTF16toASCII(dpElementDesc).get());
@@ -529,16 +549,17 @@ PrepareForSetTargetAPZCNotification(nsIW
     dpElement, &(guid.mPresShellId), &(guid.mScrollId));
   aTargets->AppendElement(guid);
 
   if (!guidIsValid || nsLayoutUtils::GetDisplayPort(dpElement, nullptr)) {
     return false;
   }
 
   APZCCH_LOG("%p didn't have a displayport, so setting one...\n", dpElement.get());
+  MOZ_ASSERT(scrollAncestor);
   return nsLayoutUtils::CalculateAndSetDisplayPortMargins(
       scrollAncestor, nsLayoutUtils::RepaintMode::Repaint);
 }
 
 static void
 SendLayersDependentApzcTargetConfirmation(nsIPresShell* aShell, uint64_t aInputBlockId,
                                           const nsTArray<ScrollableLayerGuid>& aTargets)
 {
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -601,16 +601,27 @@ AsyncCompositionManager::ApplyAsyncConte
     }
 
     const FrameMetrics& metrics = aLayer->GetFrameMetrics(i);
     ScreenPoint offset(0, 0);
     // TODO: When we enable APZ on Fennec, we'll need to call SyncFrameMetrics here.
     // When doing so, it might be useful to look at how it was called here before
     // bug 1036967 removed the (dead) call.
 
+#if defined(MOZ_ANDROID_APZ)
+    if (mIsFirstPaint) {
+      CSSToLayerScale geckoZoom = metrics.LayersPixelsPerCSSPixel().ToScaleFactor();
+      LayerIntPoint scrollOffsetLayerPixels = RoundedToInt(metrics.GetScrollOffset() * geckoZoom);
+      mContentRect = metrics.GetScrollableRect();
+      SetFirstPaintViewport(scrollOffsetLayerPixels,
+                            geckoZoom,
+                            mContentRect);
+    }
+#endif
+
     mIsFirstPaint = false;
     mLayersUpdated = false;
 
     // Apply the render offset
     mLayerManager->GetCompositor()->SetScreenRenderOffset(offset);
 
     combinedAsyncTransformWithoutOverscroll *= asyncTransformWithoutOverscroll;
     combinedAsyncTransform *= (Matrix4x4(asyncTransformWithoutOverscroll) * overscrollTransform);
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -78,16 +78,17 @@
 
 using namespace mozilla;
 using namespace mozilla::layers;
 using namespace mozilla::dom;
 using namespace mozilla::layout;
 using namespace mozilla::gfx;
 
 typedef FrameMetrics::ViewID ViewID;
+typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox;
 
 #ifdef DEBUG
 static bool
 SpammyLayoutWarningsEnabled()
 {
   static bool sValue = false;
   static bool sValueInitialized = false;
 
@@ -110,17 +111,17 @@ static inline CSSAngle
 MakeCSSAngle(const nsCSSValue& aValue)
 {
   return CSSAngle(aValue.GetAngleValue(), aValue.GetUnit());
 }
 
 static void AddTransformFunctions(nsCSSValueList* aList,
                                   nsStyleContext* aContext,
                                   nsPresContext* aPresContext,
-                                  nsRect& aBounds,
+                                  TransformReferenceBox& aRefBox,
                                   InfallibleTArray<TransformFunction>& aFunctions)
 {
   if (aList->mValue.GetUnit() == eCSSUnit_None) {
     return;
   }
 
   for (const nsCSSValueList* curr = aList; curr; curr = curr->mNext) {
     const nsCSSValue& currElem = curr->mValue;
@@ -195,62 +196,62 @@ static void AddTransformFunctions(nsCSSV
         double z = array->Item(3).GetFloatValue();
         aFunctions.AppendElement(Scale(x, y, z));
         break;
       }
       case eCSSKeyword_translatex:
       {
         double x = nsStyleTransformMatrix::ProcessTranslatePart(
           array->Item(1), aContext, aPresContext, canStoreInRuleTree,
-          aBounds.Width());
+          &aRefBox, &TransformReferenceBox::Width);
         aFunctions.AppendElement(Translation(x, 0, 0));
         break;
       }
       case eCSSKeyword_translatey:
       {
         double y = nsStyleTransformMatrix::ProcessTranslatePart(
           array->Item(1), aContext, aPresContext, canStoreInRuleTree,
-          aBounds.Height());
+          &aRefBox, &TransformReferenceBox::Height);
         aFunctions.AppendElement(Translation(0, y, 0));
         break;
       }
       case eCSSKeyword_translatez:
       {
         double z = nsStyleTransformMatrix::ProcessTranslatePart(
           array->Item(1), aContext, aPresContext, canStoreInRuleTree,
-          0);
+          nullptr);
         aFunctions.AppendElement(Translation(0, 0, z));
         break;
       }
       case eCSSKeyword_translate:
       {
         double x = nsStyleTransformMatrix::ProcessTranslatePart(
           array->Item(1), aContext, aPresContext, canStoreInRuleTree,
-          aBounds.Width());
+          &aRefBox, &TransformReferenceBox::Width);
         // translate(x) is shorthand for translate(x, 0)
         double y = 0;
         if (array->Count() == 3) {
            y = nsStyleTransformMatrix::ProcessTranslatePart(
             array->Item(2), aContext, aPresContext, canStoreInRuleTree,
-            aBounds.Height());
+            &aRefBox, &TransformReferenceBox::Height);
         }
         aFunctions.AppendElement(Translation(x, y, 0));
         break;
       }
       case eCSSKeyword_translate3d:
       {
         double x = nsStyleTransformMatrix::ProcessTranslatePart(
           array->Item(1), aContext, aPresContext, canStoreInRuleTree,
-          aBounds.Width());
+          &aRefBox, &TransformReferenceBox::Width);
         double y = nsStyleTransformMatrix::ProcessTranslatePart(
           array->Item(2), aContext, aPresContext, canStoreInRuleTree,
-          aBounds.Height());
+          &aRefBox, &TransformReferenceBox::Height);
         double z = nsStyleTransformMatrix::ProcessTranslatePart(
           array->Item(3), aContext, aPresContext, canStoreInRuleTree,
-          0);
+          nullptr);
 
         aFunctions.AppendElement(Translation(x, y, z));
         break;
       }
       case eCSSKeyword_skewx:
       {
         CSSAngle x = MakeCSSAngle(array->Item(1));
         aFunctions.AppendElement(SkewX(x));
@@ -319,17 +320,17 @@ static void AddTransformFunctions(nsCSSV
       }
       case eCSSKeyword_interpolatematrix:
       {
         gfx3DMatrix matrix;
         nsStyleTransformMatrix::ProcessInterpolateMatrix(matrix, array,
                                                          aContext,
                                                          aPresContext,
                                                          canStoreInRuleTree,
-                                                         aBounds);
+                                                         aRefBox);
         aFunctions.AppendElement(TransformMatrix(gfx::ToMatrix4x4(matrix)));
         break;
       }
       case eCSSKeyword_perspective:
       {
         aFunctions.AppendElement(Perspective(array->Item(1).GetFloatValue()));
         break;
       }
@@ -357,17 +358,17 @@ AddAnimationForProperty(nsIFrame* aFrame
                         dom::Animation* aAnimation, Layer* aLayer,
                         AnimationData& aData, bool aPending)
 {
   MOZ_ASSERT(aLayer->AsContainerLayer(), "Should only animate ContainerLayer");
   MOZ_ASSERT(aAnimation->GetEffect(),
              "Should not be adding an animation without an effect");
   nsStyleContext* styleContext = aFrame->StyleContext();
   nsPresContext* presContext = aFrame->PresContext();
-  nsRect bounds = nsDisplayTransform::GetFrameBoundsForTransform(aFrame);
+  TransformReferenceBox refBox(aFrame);
 
   layers::Animation* animation =
     aPending ?
     aLayer->AddAnimationForNextTransaction() :
     aLayer->AddAnimation();
 
   const AnimationTiming& timing = aAnimation->GetEffect()->Timing();
   Nullable<TimeDuration> startTime = aAnimation->GetCurrentOrPendingStartTime();
@@ -388,21 +389,21 @@ AddAnimationForProperty(nsIFrame* aFrame
 
     AnimationSegment* animSegment = animation->segments().AppendElement();
     if (aProperty.mProperty == eCSSProperty_transform) {
       animSegment->startState() = InfallibleTArray<TransformFunction>();
       animSegment->endState() = InfallibleTArray<TransformFunction>();
 
       nsCSSValueSharedList* list =
         segment.mFromValue.GetCSSValueSharedListValue();
-      AddTransformFunctions(list->mHead, styleContext, presContext, bounds,
+      AddTransformFunctions(list->mHead, styleContext, presContext, refBox,
                             animSegment->startState().get_ArrayOfTransformFunction());
 
       list = segment.mToValue.GetCSSValueSharedListValue();
-      AddTransformFunctions(list->mHead, styleContext, presContext, bounds,
+      AddTransformFunctions(list->mHead, styleContext, presContext, refBox,
                             animSegment->endState().get_ArrayOfTransformFunction());
     } else if (aProperty.mProperty == eCSSProperty_opacity) {
       animSegment->startState() = segment.mFromValue.GetFloatValue();
       animSegment->endState() = segment.mToValue.GetFloatValue();
     }
 
     animSegment->startPortion() = segment.mFromKey;
     animSegment->endPortion() = segment.mToKey;
@@ -530,17 +531,23 @@ nsDisplayListBuilder::AddAnimationsAndTr
     // We need to schedule another refresh driver run so that AnimationManager
     // or TransitionManager get a chance to unthrottle the animation.
     aFrame->SchedulePaint();
     return;
   }
 
   AnimationData data;
   if (aProperty == eCSSProperty_transform) {
-    nsRect bounds = nsDisplayTransform::GetFrameBoundsForTransform(aFrame);
+    // XXX Performance here isn't ideal for SVG. We'd prefer to avoid resolving
+    // the dimensions of refBox. That said, we only get here if there are CSS
+    // animations or transitions on this element, and that is likely to be a
+    // lot rarer that transforms on SVG (the frequency of which drives the need
+    // for TransformReferenceBox).
+    TransformReferenceBox refBox(aFrame);
+    nsRect bounds(0, 0, refBox.Width(), refBox.Height());
     // all data passed directly to the compositor should be in dev pixels
     int32_t devPixelsToAppUnits = aFrame->PresContext()->AppUnitsPerDevPixel();
     float scale = devPixelsToAppUnits;
     Point3D offsetToTransformOrigin =
       nsDisplayTransform::GetDeltaToTransformOrigin(aFrame, scale, &bounds);
     Point3D offsetToPerspectiveOrigin =
       nsDisplayTransform::GetDeltaToPerspectiveOrigin(aFrame, scale);
     nscoord perspective = 0.0;
@@ -1476,22 +1483,22 @@ already_AddRefed<LayerManager> nsDisplay
     bool isRoot = presContext->IsRootContentDocument();
 
     nsRect viewport(aBuilder->ToReferenceFrame(frame), frame->GetSize());
 
     nsIFrame* scrollFrame = presShell->GetRootScrollFrame();
     nsIContent* content = nullptr;
     if (scrollFrame) {
       content = scrollFrame->GetContent();
-    } else if (!gfxPrefs::LayoutUseContainersForRootFrames()) {
-      // If there is no root scroll frame, and we're using containerless
-      // scrolling, pick the document element instead.
-      // On Android we want the root xul document to get a null scroll id
-      // so that the root content document gets the first non-null scroll id.
-#ifndef MOZ_WIDGET_ANDROID
+    } else {
+      // If there is no root scroll frame, pick the document element instead.
+      // The only case we don't want to do this is in non-APZ fennec, where
+      // we want the root xul document to get a null scroll id so that the root
+      // content document gets the first non-null scroll id.
+#if !defined(MOZ_WIDGET_ANDROID) || defined(MOZ_ANDROID_APZ)
       content = document->GetDocumentElement();
 #endif
     }
 
     root->SetFrameMetrics(
       nsLayoutUtils::ComputeFrameMetrics(frame,
                          presShell->GetRootScrollFrame(),
                          content,
@@ -4460,84 +4467,28 @@ bool nsDisplayZoom::ComputeVisibility(ns
 
   return retval;
 }
 
 ///////////////////////////////////////////////////
 // nsDisplayTransform Implementation
 //
 
-// Write #define UNIFIED_CONTINUATIONS here to have the transform property try
+// Write #define UNIFIED_CONTINUATIONS here and in
+// TransformReferenceBox::Initialize to have the transform property try
 // to transform content with continuations as one unified block instead of
 // several smaller ones.  This is currently disabled because it doesn't work
 // correctly, since when the frames are initially being reflowed, their
 // continuations all compute their bounding rects independently of each other
 // and consequently get the wrong value.  Write #define DEBUG_HIT here to have
 // the nsDisplayTransform class dump out a bunch of information about hit
 // detection.
 #undef  UNIFIED_CONTINUATIONS
 #undef  DEBUG_HIT
 
-/* Returns the bounds of a frame as defined for transforms.  If
- * UNIFIED_CONTINUATIONS is not defined, this is simply the frame's bounding
- * rectangle, translated to the origin. Otherwise, returns the smallest
- * rectangle containing a frame and all of its continuations.  For example, if
- * there is a <span> element with several continuations split over several
- * lines, this function will return the rectangle containing all of those
- * continuations.  This rectangle is relative to the origin of the frame's local
- * coordinate space.
- */
-#ifndef UNIFIED_CONTINUATIONS
-
-nsRect
-nsDisplayTransform::GetFrameBoundsForTransform(const nsIFrame* aFrame)
-{
-  NS_PRECONDITION(aFrame, "Can't get the bounds of a nonexistent frame!");
-
-  if (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
-    // TODO: SVG needs to define what percentage translations resolve against.
-    return nsRect();
-  }
-
-  return nsRect(nsPoint(0, 0), aFrame->GetSize());
-}
-
-#else
-
-nsRect
-nsDisplayTransform::GetFrameBoundsForTransform(const nsIFrame* aFrame)
-{
-  NS_PRECONDITION(aFrame, "Can't get the bounds of a nonexistent frame!");
-
-  nsRect result;
-
-  if (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
-    // TODO: SVG needs to define what percentage translations resolve against.
-    return result;
-  }
-
-  /* Iterate through the continuation list, unioning together all the
-   * bounding rects.
-   */
-  for (const nsIFrame *currFrame = aFrame->FirstContinuation();
-       currFrame != nullptr;
-       currFrame = currFrame->GetNextContinuation())
-    {
-      /* Get the frame rect in local coordinates, then translate back to the
-       * original coordinates.
-       */
-      result.UnionRect(result, nsRect(currFrame->GetOffsetTo(aFrame),
-                                      currFrame->GetSize()));
-    }
-
-  return result;
-}
-
-#endif
-