Merge m-c to inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 25 Mar 2016 22:08:15 -0400
changeset 290550 f3d7946a5cb969f875790b8685092b841985d320
parent 290549 3a90ca63a3f04c0b84200e9d3b0a806dd93453fa (current diff)
parent 290516 8a4359ad909fe0cffcfea512770483ffdf8cd4e6 (diff)
child 290551 604371ad19b0b60ca45f801b846e67f9f71f4264
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone48.0a1
Merge m-c to inbound. a=merge
toolkit/components/passwordmgr/test/auth2/authenticate.sjs
toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger.js
toolkit/components/passwordmgr/test/chrome/test_formless_autofill.html
toolkit/components/passwordmgr/test/mochitest/auth2/authenticate.sjs
toolkit/components/passwordmgr/test/mochitest/test_basic_form.html
toolkit/components/passwordmgr/test/mochitest/test_basic_form_0pw.html
toolkit/components/passwordmgr/test/mochitest/test_basic_form_1pw.html
toolkit/components/passwordmgr/test/mochitest/test_basic_form_1pw_2.html
toolkit/components/passwordmgr/test/mochitest/test_basic_form_2pw_1.html
toolkit/components/passwordmgr/test/mochitest/test_basic_form_3pw_1.html
toolkit/components/passwordmgr/test/mochitest/test_basic_form_html5.html
toolkit/components/passwordmgr/test/mochitest/test_basic_form_pwevent.html
toolkit/components/passwordmgr/test/mochitest/test_basic_form_pwonly.html
toolkit/components/passwordmgr/test/mochitest/test_bug_776171.html
toolkit/components/passwordmgr/test/mochitest/test_case_differences.html
toolkit/components/passwordmgr/test/mochitest/test_form_action_1.html
toolkit/components/passwordmgr/test/mochitest/test_form_action_2.html
toolkit/components/passwordmgr/test/mochitest/test_form_action_javascript.html
toolkit/components/passwordmgr/test/mochitest/test_formless_autofill.html
toolkit/components/passwordmgr/test/mochitest/test_input_events.html
toolkit/components/passwordmgr/test/mochitest/test_input_events_for_identical_values.html
toolkit/components/passwordmgr/test/mochitest/test_maxlength.html
toolkit/components/passwordmgr/test/mochitest/test_passwords_in_type_password.html
toolkit/components/passwordmgr/test/mochitest/test_xhr_2.html
toolkit/components/passwordmgr/test/subtst_notifications_7.html
toolkit/components/passwordmgr/test/test_autofill_before_load.html
toolkit/components/passwordmgr/test/test_basic_form.html
toolkit/components/passwordmgr/test/test_basic_form_0pw.html
toolkit/components/passwordmgr/test/test_basic_form_1pw.html
toolkit/components/passwordmgr/test/test_basic_form_1pw_2.html
toolkit/components/passwordmgr/test/test_basic_form_2pw_1.html
toolkit/components/passwordmgr/test/test_basic_form_3pw_1.html
toolkit/components/passwordmgr/test/test_basic_form_html5.html
toolkit/components/passwordmgr/test/test_basic_form_pwevent.html
toolkit/components/passwordmgr/test/test_basic_form_pwonly.html
toolkit/components/passwordmgr/test/test_bug_776171.html
toolkit/components/passwordmgr/test/test_case_differences.html
toolkit/components/passwordmgr/test/test_form_action_1.html
toolkit/components/passwordmgr/test/test_form_action_2.html
toolkit/components/passwordmgr/test/test_form_action_javascript.html
toolkit/components/passwordmgr/test/test_input_events.html
toolkit/components/passwordmgr/test/test_input_events_for_identical_values.html
toolkit/components/passwordmgr/test/test_maxlength.html
toolkit/components/passwordmgr/test/test_notifications.html
toolkit/components/passwordmgr/test/test_passwords_in_type_password.html
toolkit/components/passwordmgr/test/test_xhr_2.html
toolkit/components/passwordmgr/test/test_zzz_finish.html
toolkit/components/places/tests/browser/461710_iframe.html
toolkit/components/places/tests/browser/461710_link_page-2.html
toolkit/components/places/tests/browser/461710_link_page-3.html
toolkit/components/places/tests/browser/461710_link_page.html
toolkit/components/places/tests/browser/461710_visited_page.html
toolkit/components/places/tests/browser/history_post.sjs
toolkit/components/places/tests/bug94514-postpage.html
toolkit/components/places/tests/chrome.ini
toolkit/components/places/tests/chrome/history_post.sjs
toolkit/components/places/tests/chrome/test_history_post.xul
toolkit/components/places/tests/mochitest/bug_461710/.eslintrc
toolkit/components/places/tests/mochitest/bug_461710/iframe.html
toolkit/components/places/tests/mochitest/bug_461710/link_page-2.html
toolkit/components/places/tests/mochitest/bug_461710/link_page-3.html
toolkit/components/places/tests/mochitest/bug_461710/link_page.html
toolkit/components/places/tests/mochitest/bug_461710/visited_page.html
toolkit/components/places/tests/test_bug_461710_perwindowpb.html
toolkit/components/places/tests/test_bug_94514.html
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -48,16 +48,17 @@ support-files =
 [browser_ext_tabs_update_url.js]
 [browser_ext_tabs_onUpdated.js]
 [browser_ext_tabs_sendMessage.js]
 [browser_ext_tabs_move.js]
 [browser_ext_tabs_move_window.js]
 [browser_ext_tabs_move_window_multiple.js]
 [browser_ext_tabs_move_window_pinned.js]
 [browser_ext_tabs_onHighlighted.js]
+[browser_ext_tabs_reload.js]
 [browser_ext_windows_create.js]
 tags = fullscreen
 [browser_ext_windows_create_tabId.js]
 [browser_ext_windows.js]
 [browser_ext_windows_size.js]
 skip-if = os == 'mac' # Fails when windows are randomly opened in fullscreen mode
 [browser_ext_windows_update.js]
 tags = fullscreen
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_reload.js
@@ -0,0 +1,54 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"],
+    },
+
+    files: {
+      "tab.js": function() {
+        browser.runtime.sendMessage("tab-loaded");
+      },
+      "tab.html":
+        `<head>
+          <meta charset="utf-8">
+          <script src="tab.js"></script>
+        </head>`,
+    },
+
+    background: function() {
+      let tabLoadedCount = 0;
+
+      browser.tabs.create({url: "tab.html", active: true}).then(tab => {
+        browser.runtime.onMessage.addListener(msg => {
+          if (msg == "tab-loaded") {
+            tabLoadedCount++;
+
+            if (tabLoadedCount == 1) {
+              // Reload the tab once passing no arguments.
+              return browser.tabs.reload();
+            }
+
+            if (tabLoadedCount == 2) {
+              // Reload the tab again with explicit arguments.
+              return browser.tabs.reload(tab.id, {
+                bypassCache: false,
+              });
+            }
+
+            if (tabLoadedCount == 3) {
+              browser.test.notifyPass("tabs.reload");
+            }
+          }
+        });
+      });
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("tabs.reload");
+  yield extension.unload();
+});
--- a/browser/locales/en-US/chrome/overrides/netError.dtd
+++ b/browser/locales/en-US/chrome/overrides/netError.dtd
@@ -19,17 +19,17 @@
 <!ENTITY deniedPortAccess.longDesc "">
 
 <!ENTITY dnsNotFound.title "Server not found">
 <!ENTITY dnsNotFound.longDesc "
 <ul>
   <li>Check the address for typing errors such as
     <strong>ww</strong>.example.com instead of
     <strong>www</strong>.example.com</li>
-  <li>If you are unable to load any pages, check your computer's network
+  <li>If you are unable to load any pages, check your computer’s network
     connection.</li>
   <li>If your computer or network is protected by a firewall or proxy, make sure
     that &brandShortName; is permitted to access the Web.</li>
 </ul>
 ">
 
 <!ENTITY fileNotFound.title "File not found">
 <!ENTITY fileNotFound.longDesc "
@@ -37,34 +37,34 @@
   <li>Check the file name for capitalization or other typing errors.</li>
   <li>Check to see if the file was moved, renamed or deleted.</li>
 </ul>
 ">
 
 
 <!ENTITY generic.title "Oops.">
 <!ENTITY generic.longDesc "
-<p>&brandShortName; can't load this page for some reason.</p>
+<p>&brandShortName; can’t load this page for some reason.</p>
 ">
 
-<!ENTITY malformedURI.title "The address isn't valid">
+<!ENTITY malformedURI.title "The address isn’t valid">
 <!ENTITY malformedURI.longDesc "
 <ul>
   <li>Web addresses are usually written like
     <strong>http://www.example.com/</strong></li>
-  <li>Make sure that you're using forward slashes (i.e.
+  <li>Make sure that you’re using forward slashes (i.e.
     <strong>/</strong>).</li>
 </ul>
 ">
 
 <!ENTITY netInterrupt.title "The connection was interrupted">
 <!ENTITY netInterrupt.longDesc "&sharedLongDesc;">
 
 <!ENTITY notCached.title "Document Expired">
-<!ENTITY notCached.longDesc "<p>The requested document is not available in &brandShortName;'s cache.</p><ul><li>As a security precaution, &brandShortName; does not automatically re-request sensitive documents.</li><li>Click Try Again to re-request the document from the website.</li></ul>">
+<!ENTITY notCached.longDesc "<p>The requested document is not available in &brandShortName;’s cache.</p><ul><li>As a security precaution, &brandShortName; does not automatically re-request sensitive documents.</li><li>Click Try Again to re-request the document from the website.</li></ul>">
 
 <!ENTITY netOffline.title "Offline mode">
 <!ENTITY netOffline.longDesc2 "
 <ul>
   <li>Press &quot;Try Again&quot; to switch to online mode and reload the page.</li>
 </ul>
 ">
 
@@ -83,17 +83,17 @@
 ">
 
 <!ENTITY netReset.title "The connection was reset">
 <!ENTITY netReset.longDesc "&sharedLongDesc;">
 
 <!ENTITY netTimeout.title "The connection has timed out">
 <!ENTITY netTimeout.longDesc "&sharedLongDesc;">
 
-<!ENTITY unknownProtocolFound.title "The address wasn't understood">
+<!ENTITY unknownProtocolFound.title "The address wasn’t understood">
 <!ENTITY unknownProtocolFound.longDesc "
 <ul>
   <li>You might need to install other software to open this address.</li>
 </ul>
 ">
 
 <!ENTITY proxyConnectFailure.title "The proxy server is refusing connections">
 <!ENTITY proxyConnectFailure.longDesc "
@@ -109,17 +109,17 @@
 <ul>
   <li>Check the proxy settings to make sure that they are correct.</li>
   <li>Check to make sure your computer has a working network connection.</li>
   <li>If your computer or network is protected by a firewall or proxy, make sure
     that &brandShortName; is permitted to access the Web.</li>
 </ul>
 ">
 
-<!ENTITY redirectLoop.title "The page isn't redirecting properly">
+<!ENTITY redirectLoop.title "The page isn’t redirecting properly">
 <!ENTITY redirectLoop.longDesc "
 <ul>
   <li>This problem can sometimes be caused by disabling or refusing to accept
     cookies.</li>
 </ul>
 ">
 
 <!ENTITY unknownSocketType.title "Unexpected response from server">
@@ -137,28 +137,28 @@
   <li>The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.</li>
   <li>Please contact the website owners to inform them of this problem.</li>
 </ul>
 ">
 
 <!ENTITY nssBadCert.title "Secure Connection Failed">
 <!ENTITY nssBadCert.longDesc2 "
 <ul>
-  <li>This could be a problem with the server's configuration, or it could be
+  <li>This could be a problem with the server’s configuration, or it could be
 someone trying to impersonate the server.</li>
   <li>If you have connected to this server successfully in the past, the error may
 be temporary, and you can try again later.</li>
 </ul>
 ">
 
 <!ENTITY sharedLongDesc "
 <ul>
   <li>The site could be temporarily unavailable or too busy. Try again in a few
     moments.</li>
-  <li>If you are unable to load any pages, check your computer's network
+  <li>If you are unable to load any pages, check your computer’s network
     connection.</li>
   <li>If your computer or network is protected by a firewall or proxy, make sure
     that &brandShortName; is permitted to access the Web.</li>
 </ul>
 ">
 
 <!ENTITY cspBlocked.title "Blocked by Content Security Policy">
 <!ENTITY cspBlocked.longDesc "<p>&brandShortName; prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -2331,40 +2331,35 @@ richlistitem[type~="action"][actiontype$
 }
 
 #historySwipeAnimationContainer {
   background: url("chrome://browser/skin/subtle-pattern.png") #B3B9C1;
 }
 
 /* ----- SIDEBAR ELEMENTS ----- */
 
-#sidebar,
-sidebarheader {
-  -moz-appearance: -moz-mac-vibrancy-light;
-  background-color: #e2e7ed;
-}
-
-#sidebar:-moz-window-inactive,
-sidebarheader:-moz-window-inactive {
-  background-color: #e8e8e8;
+#sidebar-box {
+  background-color: hsl(212,19%,85%);
+  background-image: linear-gradient(hsl(213,26%,93%), hsl(212,19%,85%));
+  box-shadow: inset -2px 0 0 hsla(0,0%,100%,.2);
+}
+
+#sidebar-box:-moz-window-inactive {
+  background-color: hsl(0,0%,92%);
+  background-image: linear-gradient(hsl(0,0%,97%), hsl(0,0%,92%));
 }
 
 sidebarheader {
-  padding: 2px;
-  text-shadow: none;
-}
-
-#sidebar-box {
-  -moz-appearance: dialog;
-  -moz-appearance: none;
+  padding: 2px 2px 0;
+  text-shadow: 0 1px 0 hsla(0,0%,100%,.5);
 }
 
 .sidebar-splitter {
   -moz-border-start: none;
-  -moz-border-end: 1px solid #bdbdbd;
+  -moz-border-end: 1px solid #b4b4b4;
   min-width: 1px;
   width: 3px;
   background-image: none !important;
   background-color: transparent;
   -moz-margin-start: -3px;
   position: relative;
 }
 
@@ -2372,33 +2367,62 @@ sidebarheader {
   -moz-border-start: 1px solid #ccc;
   -moz-border-end: none;
   -moz-margin-start: 0;
   -moz-margin-end: -3px;
 }
 
 .sidebar-title,
 #sidebar-title {
-  color: #535f6d;
+  color: #596c80;
   font-weight: bold;
 }
 
+.sidebar-title:-moz-window-inactive,
+#sidebar-title:-moz-window-inactive {
+  color: #868b92;
+}
+
 .sidebar-throbber[loading="true"],
 #sidebar-throbber[loading="true"] {
   list-style-image: url("chrome://global/skin/icons/loading.png");
 }
 
 @media (min-resolution: 2dppx) {
   .sidebar-throbber[loading="true"],
   #sidebar-throbber[loading="true"] {
     list-style-image: url("chrome://global/skin/icons/loading@2x.png");
     width: 16px;
   }
 }
 
+@media (-moz-mac-yosemite-theme) {
+  #sidebar-box {
+    -moz-appearance: -moz-mac-vibrancy-light;
+    background-image: none;
+    box-shadow: none;
+  }
+
+  #sidebar-box:-moz-window-inactive {
+    background-image: none;
+  }
+
+  sidebarheader {
+    text-shadow: none;
+    font-weight: 500;
+  }
+
+  .sidebar-title,
+  #sidebar-title {
+    color: #636363;
+    font-weight: 500;
+  }
+}
+
+
 /* ----- CONTENT ----- */
 
 .browserContainer > findbar {
   background: @scopeBarBackground@;
   border-top: @scopeBarSeparatorBorder@;
   color: -moz-DialogText;
   text-shadow: none;
 }
--- a/browser/themes/osx/places/organizer.css
+++ b/browser/themes/osx/places/organizer.css
@@ -4,41 +4,47 @@
 
 %include ../shared.inc
 
 /* Places Organizer Sidebars */
 
 #placesList > treechildren::-moz-tree-row {
   background-color: transparent;
   border-color: transparent;
+  padding-bottom: 1px;
+  height: 24px;
+}
+
+#placesList > treechildren::-moz-tree-cell-text {
+  font-size: 12px;
+  -moz-margin-end: 6px;
 }
 
 #placesList > treechildren::-moz-tree-row(selected) {  
   background: @sidebarItemBackground@;
   border-top: @sidebarItemBorderTop@;
+  border-bottom: @sidebarItemBorderBottom@;
 }
 
 #placesList > treechildren::-moz-tree-row(selected,focus) {  
   background: @sidebarItemFocusedBackground@;
   border-top: @sidebarItemFocusedBorderTop@;
+  border-bottom: @sidebarItemFocusedBorderBottom@;
 }
 
 #placesList:-moz-system-metric(mac-graphite-theme) > treechildren::-moz-tree-row(selected) {
   background: @sidebarItemGraphiteBackground@;
   border-top: @sidebarItemGraphiteBorderTop@;
+  border-bottom: @sidebarItemGraphiteBorderBottom@;
 }
 
 #placesList:-moz-system-metric(mac-graphite-theme) > treechildren::-moz-tree-row(selected,focus) {
   background: @sidebarItemGraphiteFocusedBackground@;
   border-top: @sidebarItemGraphiteFocusedBorderTop@;
-}
-
-#placesList > treechildren:-moz-window-inactive::-moz-tree-row(selected) {
-  background: @sidebarItemInactiveBackground@;
-  border-top: @sidebarItemInactiveBorderTop@;
+  border-bottom: @sidebarItemGraphiteFocusedBorderBottom@;
 }
 
 #placesList > treechildren::-moz-tree-row(History),
 #placesList > treechildren::-moz-tree-row(history)  {
   background-color: blue;
 }
 
 #placesList > treechildren::-moz-tree-cell(separator) {
@@ -46,31 +52,100 @@
 }
 
 #placesList > treechildren::-moz-tree-separator {
   border-top: 1px solid #505d6d;
   margin: 0 10px;
 }
 
 #placesList > treechildren::-moz-tree-cell-text(selected) {  
-  font-weight: bold !important;
-  color: #ffffff !important;
+  color: #fff;
+  font-weight: bold;
+}
+
+#placesList > treechildren::-moz-tree-twisty {
+  -moz-appearance: none;
+  padding: 0 2px;
+  list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
+}
+
+#placesList > treechildren::-moz-tree-twisty(selected) {
+  list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
+}
+
+#placesList > treechildren::-moz-tree-twisty(open) {
+  list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
+}
+
+#placesList > treechildren::-moz-tree-twisty(open, selected) {
+  list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
+}
+
+@media (-moz-mac-yosemite-theme) {
+  #placesList > treechildren::-moz-tree-row(selected) {
+    background: @sidebarItemBackgroundYosemite@;
+    border-top: none;
+    border-bottom: none;
+  }
+
+  #placesList > treechildren::-moz-tree-row(selected,focus) {
+    background: @sidebarItemFocusedBackgroundYosemite@;
+    border-top: none;
+    border-bottom: none;
+  }
+
+  #placesList > treechildren:-moz-system-metric(mac-graphite-theme)::-moz-tree-row(selected) {
+    background: @sidebarItemGraphiteBackgroundYosemite@;
+    border-top: none;
+    border-bottom: none;
+  }
+
+  #placesList > treechildren:-moz-system-metric(mac-graphite-theme)::-moz-tree-row(selected,focus) {
+    background: @sidebarItemGraphiteFocusedBackgroundYosemite@;
+    border-top: none;
+    border-bottom: none;
+  }
+
+  #placesList > treechildren::-moz-tree-cell-text(selected) {
+    color: -moz-dialogtext;
+    font-weight: 500;
+  }
+
+  #placesList > treechildren::-moz-tree-cell-text(selected, focus) {
+    color: #fff;
+  }
+
+  #placesList > treechildren::-moz-tree-twisty(selected) {
+    list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
+  }
+
+  #placesList > treechildren::-moz-tree-twisty(selected, focus) {
+    list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
+  }
+
+  #placesList > treechildren::-moz-tree-twisty(open, selected) {
+    list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
+  }
+
+  #placesList > treechildren::-moz-tree-twisty(open, selected, focus) {
+    list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
+  }
 }
 
 #placesToolbar {
   padding: 0 4px 3px;
 }
 
 #placesView {
   border-top: none !important;
 }
 
 #placesView > splitter {
   -moz-border-start: none !important;
-  -moz-border-end: 1px solid #bdbdbd;
+  -moz-border-end: 1px solid #b4b4b4;
   min-width: 1px;
   width: 3px;
   -moz-margin-start: -3px;
   position: relative;
   background-image: none !important;
 }
 
 #placesToolbar > toolbarbutton {
@@ -169,25 +244,40 @@
 /* Root View */
 #placesView {
   border-top: 1px solid ThreeDDarkShadow;
   -moz-user-focus: ignore;
 }
 
 /* Place List, Place Content */
 #placesList {
-  -moz-appearance: -moz-mac-vibrancy-light;
-  background-color: #e2e7ed;
+  background-color: hsl(212,19%,85%);
+  background-image: linear-gradient(hsl(213,26%,93%), hsl(212,19%,85%));
+  box-shadow: inset -2px 0 0 hsla(0,0%,100%,.2);
   width: 160px;
 }
 
 #placesList:-moz-window-inactive {
-  background-color: #e8e8e8;
+  background-color: hsl(0,0%,92%);
+  background-image: linear-gradient(hsl(0,0%,97%), hsl(0,0%,92%));
 }
 
+@media (-moz-mac-yosemite-theme) {
+  #placesList {
+    -moz-appearance: -moz-mac-vibrancy-light;
+    background-image: none;
+    box-shadow: none;
+  }
+
+  #placesList:-moz-window-inactive {
+    background-image: none;
+  }
+}
+
+
 /* Info box */
 #detailsDeck {
   border-top: 1px solid #919191;
   background-color: #f0f0f0;
   padding: 10px;
 }
 
 #placeContent {
--- a/browser/themes/osx/places/places.css
+++ b/browser/themes/osx/places/places.css
@@ -17,79 +17,174 @@
 
 .sidebar-placesTree,
 .sidebar-placesTreechildren::-moz-tree-row {
   background-color: transparent;
   border-color: transparent;
   padding-bottom: 1px;
   -moz-appearance: none;
   margin: 0;
+  height: 24px;
   border: none;
+  font-size: 12px;
 }
 
 .sidebar-placesTreechildren::-moz-tree-separator {
   border-top: 1px solid #505d6d;
   margin: 0 10px;
 }
 
 .sidebar-placesTree {
   border-top: 1px solid #bebebe;
 }
 
 .sidebar-placesTreechildren::-moz-tree-row(selected) {
   background: @sidebarItemBackground@;
   border-top: @sidebarItemBorderTop@;
+  border-bottom: @sidebarItemBorderBottom@;
 }
 
 .sidebar-placesTreechildren::-moz-tree-row(selected,focus) {
   background: @sidebarItemFocusedBackground@;
   border-top: @sidebarItemFocusedBorderTop@;
+  border-bottom: @sidebarItemFocusedBorderBottom@;
 }
 
 .sidebar-placesTreechildren:-moz-system-metric(mac-graphite-theme)::-moz-tree-row(selected) {
   background: @sidebarItemGraphiteBackground@;
   border-top: @sidebarItemGraphiteBorderTop@;
+  border-bottom: @sidebarItemGraphiteBorderBottom@;
 }
 
 .sidebar-placesTreechildren:-moz-system-metric(mac-graphite-theme)::-moz-tree-row(selected,focus) {
   background: @sidebarItemGraphiteFocusedBackground@;
   border-top: @sidebarItemGraphiteFocusedBorderTop@;
+  border-bottom: @sidebarItemGraphiteFocusedBorderBottom@;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell-text {
+  -moz-margin-end: 6px;
 }
 
 .sidebar-placesTreechildren::-moz-tree-cell-text(selected) {
-  font-weight: bold !important;
-  color: #ffffff !important;
+  color: #fff;
+  font-weight: bold;
 }
 
 #sidebar-search-label {
   display: none;
 }
 
+.sidebar-placesTreechildren::-moz-tree-twisty {
+  -moz-appearance: none;
+  padding: 0 2px;
+  list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
+}
+
+.sidebar-placesTreechildren::-moz-tree-twisty(selected) {
+  list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
+}
+
+.sidebar-placesTreechildren::-moz-tree-twisty(open) {
+  list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
+}
+
+.sidebar-placesTreechildren::-moz-tree-twisty(open, selected) {
+  list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
+}
+
+@media (-moz-mac-yosemite-theme) {
+  .sidebar-placesTreechildren::-moz-tree-row(selected) {
+    background: @sidebarItemBackgroundYosemite@;
+    border-top: none;
+    border-bottom: none;
+  }
+
+  .sidebar-placesTreechildren::-moz-tree-row(selected,focus) {
+    background: @sidebarItemFocusedBackgroundYosemite@;
+    border-top: none;
+    border-bottom: none;
+  }
+
+  .sidebar-placesTreechildren:-moz-system-metric(mac-graphite-theme)::-moz-tree-row(selected) {
+    background: @sidebarItemGraphiteBackgroundYosemite@;
+    border-top: none;
+    border-bottom: none;
+  }
+
+  .sidebar-placesTreechildren:-moz-system-metric(mac-graphite-theme)::-moz-tree-row(selected,focus) {
+    background: @sidebarItemGraphiteFocusedBackgroundYosemite@;
+    border-top: none;
+    border-bottom: none;
+  }
+
+  .sidebar-placesTreechildren::-moz-tree-cell-text(selected) {
+    color: -moz-dialogtext;
+    font-weight: 500;
+  }
+
+  .sidebar-placesTreechildren::-moz-tree-cell-text(selected, focus) {
+    color: #fff;
+  }
+
+  .sidebar-placesTreechildren::-moz-tree-twisty(selected) {
+    list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
+  }
+
+  .sidebar-placesTreechildren::-moz-tree-twisty(selected, focus) {
+    list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
+  }
+
+  .sidebar-placesTreechildren::-moz-tree-twisty(open, selected) {
+    list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
+  }
+
+  .sidebar-placesTreechildren::-moz-tree-twisty(open, selected, focus) {
+    list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
+  }
+}
+
 #viewButton {
   -moz-appearance: none;
-  border: 1px solid #7F7F7F;
-  border-radius: 10px;
-  background: @toolbarbuttonBackground@;
+  padding-bottom: 1px;
+  -moz-padding-start: 5px;
+  -moz-padding-end: 0px;
+  margin: 0;
+  -moz-margin-end: 4px;
   min-width: 0px;
   min-height: 0px;
-  -moz-padding-start: 5px;
-  -moz-padding-end: 0px;
-  padding-top: 1px;
-  padding-bottom: 1px;
+  border: 1px solid #a2a9b1;
+  border-radius: 10px;
+  background-image: linear-gradient(hsla(0,0%,100%,.75),hsla(0,0%,100%,.1));
+  box-shadow: inset 0 0 1px hsla(0,0%,100%,.85),
+                    0 1px hsla(0,0%,100%,.35);
+}
+
+#viewButton:hover:active,
+#viewButton[open=true] {
+  background-image: linear-gradient(hsla(0,0%,100%,.1),hsla(0,0%,100%,.75));
+  box-shadow: @roundButtonPressedShadow@;
+  color: -moz-dialogText;
+}
+
+#viewButton:-moz-window-inactive {
+  border-color: #b6b6b6;
+  background-image: linear-gradient(hsla(0,0%,100%,.3),hsla(0,0%,100%,0));
 }
 
 #viewButton .button-menu-dropmarker {
   display: -moz-box;
   list-style-image: url("chrome://global/skin/icons/menulist-dropmarker.png");
 }
 
 #viewButton:focus {
-  outline: 2px solid #4A8BC7;
-  outline-offset: -2px;
-  -moz-outline-radius: 10px;
+  box-shadow: 0 1px 0 hsla(0, 0%, 0%, .15),
+              0 0 0 1px hsla(210, 100%, 60%, .45) inset,
+              0 0 0 2px hsla(210, 100%, 60%, .45);
+  border-color: hsla(210, 100%, 60%, 1);
 }
 
 #sidebar-search-container {
   margin: 0 4px 6px;
 }
 
 /* Trees */
 
--- a/browser/themes/osx/syncedtabs/sidebar.css
+++ b/browser/themes/osx/syncedtabs/sidebar.css
@@ -1,54 +1,110 @@
 /* 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/. */
 
+%include ../shared.inc
 %include ../../shared/syncedtabs/sidebar.inc.css
 
 /* These styles are intended to mimic XUL trees and the XUL search box. */
 
 html {
 }
 
 .item {
   color: -moz-DialogText;
 }
 
+.item-title-container {
+  box-sizing: border-box;
+  align-items: center;
+  height: 24px;
+  font-size: 12px;
+}
+
 .item.selected > .item-title-container {
   color: HighlightText;
   font-weight: bold;
 }
 
 .item.selected > .item-title-container {
-  background: linear-gradient(to bottom, rgba(156,172,204,1) 0%, rgba(116,135,172,1) 100%);
+  background: @sidebarItemBackground@;
+  border-top: @sidebarItemBorderTop@;
+  border-bottom: @sidebarItemBorderBottom@;
 }
 
 .item.selected:focus > .item-title-container {
-  background: linear-gradient(to bottom, rgba(95,144,209,1) 0%, rgba(39,90,173,1) 100%);
-}
-
-/* Using '-moz-appearance: twistytree[open];' seems to force the twisty to
-   be 20px high and max-height etc doesn't adjust it. So we set the max-height
-   on the container and a negative margin-top on the twisty itself to keep things
-   aligned.
-*/
-.item-title-container {
-  max-height: 16px;
+  background: @sidebarItemFocusedBackground@;
+  border-top: @sidebarItemFocusedBorderTop@;
+  border-bottom: @sidebarItemFocusedBorderBottom@;
 }
 
 .item.client .item-twisty-container {
-  margin-top: -2px;
-  -moz-appearance: treetwistyopen;
+  width: 16px;
+  height: 16px;
+  background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
 }
 
 .item.client.closed .item-twisty-container {
-  -moz-appearance: treetwisty;
+  background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
+}
+
+.item.client.selected:focus .item-twisty-container {
+  background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
+}
+
+.item.client.selected.closed:focus .item-twisty-container {
+  background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
 }
 
+@media (-moz-mac-yosemite-theme) {
+  .item.selected > .item-title-container {
+    font-weight: 500;
+  }
+
+  .item.selected > .item-title-container {
+    background-image: none;
+    background-color: @sidebarItemBackgroundYosemite@;
+    border-top: none;
+    border-bottom: none;
+  }
+
+  .item.selected:focus > .item-title-container {
+    background-image: none;
+    background-color: @sidebarItemFocusedBackgroundYosemite@;
+    border-top: none;
+    border-bottom: none;
+  }
+
+  .item.selected:-moz-system-metric(mac-graphite-theme) > .item-title-container {
+    background-image: none;
+    background-color: @sidebarItemGraphiteBackgroundYosemite@;
+    border-top: none;
+    border-bottom: none;
+  }
+
+  .item.selected:focus:-moz-system-metric(mac-graphite-theme) > .item-title-container {
+    background-image: none;
+    background-color: @sidebarItemGraphiteFocusedBackgroundYosemite@;
+    border-top: none;
+    border-bottom: none;
+  }
+
+  .item.selected > .item-title-container {
+    color: -moz-dialogtext;
+    font-weight: 500;
+  }
+
+  .item.selected:focus > .item-title-container {
+    color: #fff;
+  }
+}
+
+
 .sidebar-search-container {
   border-bottom: 1px solid #bdbdbd;
 }
 
 .search-box {
   -moz-appearance: searchfield;
   padding: 1px;
   font-size: 12px;
--- a/devtools/client/animationinspector/animation-controller.js
+++ b/devtools/client/animationinspector/animation-controller.js
@@ -94,16 +94,18 @@ var getServerTraits = Task.async(functio
     { name: "hasSetPlaybackRates", actor: "animations",
       method: "setPlaybackRates" },
     { name: "hasTargetNode", actor: "domwalker",
       method: "getNodeFromActor" },
     { name: "hasSetCurrentTimes", actor: "animations",
       method: "setCurrentTimes" },
     { name: "hasGetFrames", actor: "animationplayer",
       method: "getFrames" },
+    { name: "hasGetProperties", actor: "animationplayer",
+      method: "getProperties" },
     { name: "hasSetWalkerActor", actor: "animations",
       method: "setWalkerActor" },
   ];
 
   let traits = {};
   for (let {name, actor, method} of config) {
     traits[name] = yield target.actorHasMethod(actor, method);
   }
--- a/devtools/client/animationinspector/animation-panel.js
+++ b/devtools/client/animationinspector/animation-panel.js
@@ -57,17 +57,18 @@ var AnimationsPanel = {
       "onPickerStopped", "refreshAnimationsUI", "onToggleAllClicked",
       "onTabNavigated", "onTimelineDataChanged", "onTimelinePlayClicked",
       "onTimelineRewindClicked", "onRateChanged"]) {
       this[functionName] = this[functionName].bind(this);
     }
     let hUtils = gToolbox.highlighterUtils;
     this.togglePicker = hUtils.togglePicker.bind(hUtils);
 
-    this.animationsTimelineComponent = new AnimationsTimeline(gInspector);
+    this.animationsTimelineComponent = new AnimationsTimeline(gInspector,
+      AnimationsController.traits);
     this.animationsTimelineComponent.init(this.playersEl);
 
     if (AnimationsController.traits.hasSetPlaybackRate) {
       this.rateSelectorComponent = new RateSelector();
       this.rateSelectorComponent.init(this.rateSelectorEl);
     }
 
     this.startListeners();
--- a/devtools/client/animationinspector/components/animation-details.js
+++ b/devtools/client/animationinspector/components/animation-details.js
@@ -11,100 +11,134 @@ const {Task} = Cu.import("resource://gre
 const {createNode, TimeScale} = require("devtools/client/animationinspector/utils");
 const {Keyframes} = require("devtools/client/animationinspector/components/keyframes");
 
 /**
  * UI component responsible for displaying detailed information for a given
  * animation.
  * This includes information about timing, easing, keyframes, animated
  * properties.
+ *
+ * @param {Object} serverTraits The list of server-side capabilities.
  */
-function AnimationDetails() {
+function AnimationDetails(serverTraits) {
   EventEmitter.decorate(this);
 
   this.onFrameSelected = this.onFrameSelected.bind(this);
 
   this.keyframeComponents = [];
+  this.serverTraits = serverTraits;
 }
 
 exports.AnimationDetails = AnimationDetails;
 
 AnimationDetails.prototype = {
   // These are part of frame objects but are not animated properties. This
   // array is used to skip them.
   NON_PROPERTIES: ["easing", "composite", "computedOffset", "offset"],
 
   init: function(containerEl) {
     this.containerEl = containerEl;
   },
 
   destroy: function() {
     this.unrender();
     this.containerEl = null;
+    this.serverTraits = null;
   },
 
   unrender: function() {
     for (let component of this.keyframeComponents) {
       component.off("frame-selected", this.onFrameSelected);
       component.destroy();
     }
     this.keyframeComponents = [];
 
     while (this.containerEl.firstChild) {
       this.containerEl.firstChild.remove();
     }
   },
 
   /**
-   * Convert a list of frames into a list of tracks, one per animated property,
-   * each with a list of frames.
+   * Get a list of the tracks of the animation actor
+   * @return {Object} A list of tracks, one per animated property, each
+   * with a list of keyframes
    */
-  getTracksFromFrames: function(frames) {
+  getTracks: Task.async(function*() {
     let tracks = {};
 
-    for (let frame of frames) {
-      for (let name in frame) {
-        if (this.NON_PROPERTIES.indexOf(name) != -1) {
-          continue;
-        }
+    /*
+     * getFrames is a AnimationPlayorActor method that returns data about the
+     * keyframes of the animation.
+     * In FF48, the data it returns change, and will hold only longhand
+     * properties ( e.g. borderLeftWidth ), which does not match what we
+     * want to display in the animation detail.
+     * A new AnimationPlayerActor function, getProperties, is introduced,
+     * that returns the animated css properties of the animation and their
+     * keyframes values.
+     * If the animation actor has the getProperties function, we use it, and if
+     * not, we fall back to getFrames, which then returns values we used to
+     * handle.
+     */
+    if (this.serverTraits.hasGetProperties) {
+      let properties = yield this.animation.getProperties();
+      for (let propertyObject of properties) {
+        let name = propertyObject.property;
 
         if (!tracks[name]) {
           tracks[name] = [];
         }
 
-        tracks[name].push({
-          value: frame[name],
-          offset: frame.computedOffset
-        });
+        for (let {value, offset} of propertyObject.values) {
+          tracks[name].push({value, offset});
+        }
+      }
+    } else {
+      let frames = yield this.animation.getFrames();
+      for (let frame of frames) {
+        for (let name in frame) {
+          if (this.NON_PROPERTIES.indexOf(name) != -1) {
+            continue;
+          }
+
+          if (!tracks[name]) {
+            tracks[name] = [];
+          }
+
+          tracks[name].push({
+            value: frame[name],
+            offset: frame.computedOffset
+          });
+        }
       }
     }
 
     return tracks;
-  },
+  }),
 
   render: Task.async(function*(animation) {
     this.unrender();
 
     if (!animation) {
       return;
     }
     this.animation = animation;
 
-    let frames = yield animation.getFrames();
-
     // We might have been destroyed in the meantime, or the component might
     // have been re-rendered.
     if (!this.containerEl || this.animation !== animation) {
       return;
     }
+
+    // Build an element for each animated property track.
+    this.tracks = yield this.getTracks(animation, this.serverTraits);
+
     // Useful for tests to know when the keyframes have been retrieved.
     this.emit("keyframes-retrieved");
 
-    // Build an element for each animated property track.
-    this.tracks = this.getTracksFromFrames(frames);
     for (let propertyName in this.tracks) {
       let line = createNode({
         parent: this.containerEl,
         attributes: {"class": "property"}
       });
 
       createNode({
         // text-overflow doesn't work in flex items, so we need a second level
--- a/devtools/client/animationinspector/components/animation-timeline.js
+++ b/devtools/client/animationinspector/components/animation-timeline.js
@@ -31,23 +31,25 @@ const TIMELINE_BACKGROUND_RESIZE_DEBOUNC
  * time play head.
  * Animations are organized by lines, with a left margin containing the preview
  * of the target DOM element the animation applies to.
  * The current time play head can be moved by clicking/dragging in the header.
  * when this happens, the component emits "current-data-changed" events with the
  * new time and state of the timeline.
  *
  * @param {InspectorPanel} inspector.
+ * @param {Object} serverTraits The list of server-side capabilities.
  */
-function AnimationsTimeline(inspector) {
+function AnimationsTimeline(inspector, serverTraits) {
   this.animations = [];
   this.targetNodes = [];
   this.timeBlocks = [];
   this.details = [];
   this.inspector = inspector;
+  this.serverTraits = serverTraits;
 
   this.onAnimationStateChanged = this.onAnimationStateChanged.bind(this);
   this.onScrubberMouseDown = this.onScrubberMouseDown.bind(this);
   this.onScrubberMouseUp = this.onScrubberMouseUp.bind(this);
   this.onScrubberMouseOut = this.onScrubberMouseOut.bind(this);
   this.onScrubberMouseMove = this.onScrubberMouseMove.bind(this);
   this.onAnimationSelected = this.onAnimationSelected.bind(this);
   this.onWindowResize = this.onWindowResize.bind(this);
@@ -127,16 +129,17 @@ AnimationsTimeline.prototype = {
 
     this.rootWrapperEl = null;
     this.timeHeaderEl = null;
     this.animationsEl = null;
     this.scrubberEl = null;
     this.scrubberHandleEl = null;
     this.win = null;
     this.inspector = null;
+    this.serverTraits = null;
   },
 
   /**
    * Destroy sub-components that have been created and stored on this instance.
    * @param {String} name An array of components will be expected in this[name]
    * @param {Array} handlers An option list of event handlers information that
    * should be used to remove these handlers.
    */
@@ -299,17 +302,17 @@ AnimationsTimeline.prototype = {
       let detailsEl = createNode({
         parent: this.animationsEl,
         nodeType: "li",
         attributes: {
           "class": "animated-properties"
         }
       });
 
-      let details = new AnimationDetails();
+      let details = new AnimationDetails(this.serverTraits);
       details.init(detailsEl);
       details.on("frame-selected", this.onFrameSelected);
       this.details.push(details);
 
       // Left sidebar for the animated node.
       let animatedNodeEl = createNode({
         parent: animationEl,
         attributes: {
--- a/devtools/client/animationinspector/test/browser_animation_animated_properties_displayed.js
+++ b/devtools/client/animationinspector/test/browser_animation_animated_properties_displayed.js
@@ -34,17 +34,17 @@ add_task(function*() {
   info("Click to select the animation");
   yield clickOnAnimation(panel, 0);
 
   ok(isNodeVisible(propertiesList),
      "The list of properties panel is shown");
   ok(propertiesList.querySelectorAll(".property").length,
      "The list of properties panel actually contains properties");
   ok(hasExpectedProperties(propertiesList),
-     "The list of proeprties panel contains the right properties");
+     "The list of properties panel contains the right properties");
 
   info("Click to unselect the animation");
   yield clickOnAnimation(panel, 0, true);
 
   ok(!isNodeVisible(propertiesList),
      "The list of properties panel is hidden again");
 });
 
--- a/devtools/client/animationinspector/test/browser_animation_keyframe_click_to_set_time.js
+++ b/devtools/client/animationinspector/test/browser_animation_keyframe_click_to_set_time.js
@@ -17,17 +17,17 @@ add_task(function*() {
   // the animations to be slightly offset with the header when it appears.
   // So for now, let's hide the scrollbar. Bug 1229340 should fix this.
   timeline.animationsEl.style.overflow = "hidden";
 
   info("Expand the animation");
   yield clickOnAnimation(panel, 0);
 
   info("Click on the first keyframe of the first animated property");
-  yield clickKeyframe(panel, 0, "backgroundColor", 0);
+  yield clickKeyframe(panel, 0, "background-color", 0);
 
   info("Make sure the scrubber stopped moving and is at the right position");
   yield assertScrubberMoving(panel, false);
   checkScrubberPos(scrubberEl, 0);
 
   info("Click on a keyframe in the middle");
   yield clickKeyframe(panel, 0, "transform", 2);
 
--- a/devtools/client/animationinspector/test/browser_animation_keyframe_markers.js
+++ b/devtools/client/animationinspector/test/browser_animation_keyframe_markers.js
@@ -49,26 +49,29 @@ add_task(function*() {
       is(markers[i].dataset.value, values[i],
          "Marker " + i + " for " + propertyName + " has the right value");
     }
   }
 });
 
 function* getExpectedKeyframesData(animation) {
   // We're testing the UI state here, so it's fine to get the list of expected
-  // keyframes from the animation actor.
-  let frames = yield animation.getFrames();
+  // properties from the animation actor.
+  let properties = yield animation.getProperties();
   let data = {};
 
-  for (let property of EXPECTED_PROPERTIES) {
-    data[property] = [];
-    for (let frame of frames) {
-      if (typeof frame[property] !== "undefined") {
-        data[property].push({
-          offset: frame.computedOffset,
-          value: frame[property]
+  for (let expectedProperty of EXPECTED_PROPERTIES) {
+    data[expectedProperty] = [];
+    for (let propertyObject of properties) {
+      if (propertyObject.property !== expectedProperty) {
+        continue;
+      }
+      for (let valueObject of propertyObject.values) {
+        data[expectedProperty].push({
+          offset: valueObject.offset,
+          value: valueObject.value
         });
       }
     }
   }
 
   return data;
 }
--- a/devtools/client/canvasdebugger/canvasdebugger.xul
+++ b/devtools/client/canvasdebugger/canvasdebugger.xul
@@ -17,31 +17,29 @@
   <script type="application/javascript" src="callslist.js"/>
   <script type="application/javascript" src="snapshotslist.js"/>
 
   <hbox class="theme-body" flex="1">
     <vbox id="snapshots-pane">
       <toolbar id="snapshots-toolbar"
                class="devtools-toolbar">
         <hbox id="snapshots-controls">
+          <toolbarbutton id="clear-snapshots"
+                         class="devtools-toolbarbutton devtools-clear-icon"
+                         oncommand="SnapshotsListView._onClearButtonClick()"
+                         tooltiptext="&canvasDebuggerUI.clearSnapshots;"/>
           <toolbarbutton id="record-snapshot"
                          class="devtools-toolbarbutton"
                          oncommand="SnapshotsListView._onRecordButtonClick()"
                          tooltiptext="&canvasDebuggerUI.recordSnapshot.tooltip;"
                          hidden="true"/>
-          <hbox class="devtools-toolbarbutton-group">
-            <toolbarbutton id="import-snapshot"
-                           class="devtools-toolbarbutton"
-                           oncommand="SnapshotsListView._onImportButtonClick()"
-                           label="&canvasDebuggerUI.importSnapshot;"/>
-            <toolbarbutton id="clear-snapshots"
-                           class="devtools-toolbarbutton"
-                           oncommand="SnapshotsListView._onClearButtonClick()"
-                           label="&canvasDebuggerUI.clearSnapshots;"/>
-           </hbox>
+          <toolbarbutton id="import-snapshot"
+                         class="devtools-toolbarbutton"
+                         oncommand="SnapshotsListView._onImportButtonClick()"
+                         label="&canvasDebuggerUI.importSnapshot;"/>
         </hbox>
       </toolbar>
       <vbox id="snapshots-list" flex="1"/>
     </vbox>
 
     <vbox id="debugging-pane" class="devtools-main-content" flex="1">
       <hbox id="reload-notice"
             class="notice-container"
--- a/devtools/client/debugger/content/views/sources-view.js
+++ b/devtools/client/debugger/content/views/sources-view.js
@@ -144,17 +144,17 @@ SourcesView.prototype = Heritage.extend(
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the SourcesView");
 
     this.widget.removeEventListener("select", this._onSourceSelect, false);
     this._stopBlackBoxButton.removeEventListener("click", this._onStopBlackBoxing, false);
     this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false);
-    this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShown, false);
+    this._cbPanel.removeEventListener("popupshown", this._onConditionalPopupShown, false);
     this._cbPanel.removeEventListener("popuphiding", this._onConditionalPopupHiding, false);
     this._cbTextbox.removeEventListener("keypress", this._onConditionalTextboxKeyPress, false);
     this._copyUrlMenuItem.removeEventListener("command", this._onCopyUrlCommand, false);
     this._newTabMenuItem.removeEventListener("command", this._onNewTabCommand, false);
     this.DebuggerView.editor.off("popupOpen", this._onEditorContextMenuOpen, false);
   },
 
   empty: function() {
--- a/devtools/client/memory/components/toolbar.js
+++ b/devtools/client/memory/components/toolbar.js
@@ -174,16 +174,23 @@ module.exports = createClass({
         },
 
         dom.div(
           {
             className: "toolbar-group"
           },
 
           dom.button({
+            id: "clear-snapshots",
+            className: "clear-snapshots devtools-button",
+            onClick: onClearSnapshotsClick,
+            title: L10N.getStr("clear-snapshots.tooltip")
+          }),
+
+          dom.button({
             id: "take-snapshot",
             className: "take-snapshot devtools-button",
             onClick: onTakeSnapshotClick,
             title: L10N.getStr("take-snapshot")
           }),
 
           dom.button(
             {
@@ -199,24 +206,17 @@ module.exports = createClass({
             {
               id: "import-snapshot",
               className: "devtools-toolbarbutton import-snapshot devtools-button",
               onClick: onImportClick,
               title: L10N.getStr("import-snapshot"),
               "data-text-only": true,
             },
             L10N.getStr("import-snapshot")
-          ),
-
-          dom.button({
-            id: "clear-snapshots",
-            className: "clear-snapshots devtools-button",
-            onClick: onClearSnapshotsClick,
-            title: L10N.getStr("clear-snapshots.tooltip")
-          })
+          )
         ),
 
         dom.label(
           {
             title: L10N.getStr("checkbox.recordAllocationStacks.tooltip"),
           },
           dom.input({
             id: "record-allocation-stacks-checkbox",
--- a/devtools/client/performance/performance.xul
+++ b/devtools/client/performance/performance.xul
@@ -79,25 +79,25 @@
   <hbox id="body" class="theme-body" flex="1">
 
     <!-- Sidebar: controls and recording list -->
     <vbox id="recordings-pane">
       <toolbar id="recordings-toolbar"
                class="devtools-toolbar">
         <hbox id="recordings-controls"
               class="devtools-toolbarbutton-group">
+          <toolbarbutton id="clear-button"
+                         class="devtools-toolbarbutton devtools-clear-icon"
+                         tooltiptext="&performanceUI.clearButton;"/>
           <toolbarbutton id="main-record-button"
                          class="devtools-toolbarbutton record-button"
                          tooltiptext="&performanceUI.recordButton.tooltip;"/>
           <toolbarbutton id="import-button"
                          class="devtools-toolbarbutton"
                          label="&performanceUI.importButton;"/>
-          <toolbarbutton id="clear-button"
-                         class="devtools-toolbarbutton"
-                         label="&performanceUI.clearButton;"/>
         </hbox>
       </toolbar>
       <vbox id="recordings-list" class="theme-sidebar" flex="1"/>
     </vbox>
 
     <!-- Main panel content -->
     <vbox id="performance-pane" flex="1">
 
--- a/devtools/client/shared/demangle.js
+++ b/devtools/client/shared/demangle.js
@@ -43,20 +43,22 @@ var ra=[Sc,qc];return{_malloc:vc,_i64Sub
   var m = Module();
   var status = m._malloc(4);
   var buf = m._malloc(2048);
 
   return function(func) {
     if (func.length >= 2048) return null;
     m.writeStringToMemory(func.substr(1), buf);
     var ret = m['___cxa_demangle'](buf, 0, 0, status);
+    var result = null;
     if (m.HEAP32[status >> 2] === 0 && ret) {
-      return m.Pointer_stringify(ret);
+      result = m.Pointer_stringify(ret);
+      m._free(ret);
     }
-    return null;
+    return result;
   };
 })();
 
 // The emscripten compiler exports the Module object; we just want
 // the demangle function
 if (typeof module === "object" && typeof module.exports === "object") {
   module.exports = demangle;
 }
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -284,16 +284,18 @@
 }
 
 .ruleview-swatch {
   cursor: pointer;
   border-radius: 50%;
   width: 0.9em;
   height: 0.9em;
   vertical-align: middle;
+  /* align the swatch with its value */
+  margin-top: -1px;
   -moz-margin-end: 5px;
   display: inline-block;
   position: relative;
 }
 
 .ruleview-colorswatch::before {
   content: '';
   background-color: #eee;
--- a/devtools/client/webconsole/webconsole.xul
+++ b/devtools/client/webconsole/webconsole.xul
@@ -87,79 +87,83 @@ function goUpdateConsoleCommands() {
     </menupopup>
   </popupset>
 
   <tooltip id="aHTMLTooltip" page="true"/>
 
   <box class="hud-outer-wrapper devtools-responsive-container theme-body" flex="1">
     <vbox class="hud-console-wrapper devtools-main-content" flex="1">
       <toolbar class="hud-console-filter-toolbar devtools-toolbar" mode="full">
+        <toolbarbutton class="webconsole-clear-console-button devtools-toolbarbutton devtools-clear-icon"
+                       tooltiptext="&btnClear.tooltip;"
+                       accesskey="&btnClear.accesskey;"
+                       tabindex="3"/>
         <hbox class="devtools-toolbarbutton-group">
           <toolbarbutton label="&btnPageNet.label;" type="menu-button"
                          category="net" class="devtools-toolbarbutton webconsole-filter-button"
                          tooltiptext="&btnPageNet.tooltip;"
                          accesskeyMacOSX="&btnPageNet.accesskeyMacOSX;"
                          accesskey="&btnPageNet.accesskey;"
-                         tabindex="3">
+                         tabindex="4">
             <menupopup id="net-contextmenu">
               <menuitem label="&btnConsoleErrors;" type="checkbox" autocheck="false"
                         prefKey="network"/>
               <menuitem label="&btnConsoleWarnings;" type="checkbox" autocheck="false"
                         prefKey="netwarn"/>
               <menuitem label="&btnConsoleXhr;" type="checkbox" autocheck="false"
                         prefKey="netxhr"/>
               <menuitem label="&btnConsoleLog;" type="checkbox" autocheck="false"
                         prefKey="networkinfo"/>
             </menupopup>
           </toolbarbutton>
           <toolbarbutton label="&btnPageCSS.label;" type="menu-button"
                          category="css" class="devtools-toolbarbutton webconsole-filter-button"
                          tooltiptext="&btnPageCSS.tooltip2;"
                          accesskey="&btnPageCSS.accesskey;"
-                         tabindex="4">
+                         tabindex="5">
             <menupopup id="css-contextmenu">
               <menuitem label="&btnConsoleErrors;" type="checkbox" autocheck="false"
                         prefKey="csserror"/>
               <menuitem label="&btnConsoleWarnings;" type="checkbox"
                         autocheck="false" prefKey="cssparser"/>
               <menuitem label="&btnConsoleReflows;" type="checkbox"
                         autocheck="false" prefKey="csslog"/>
             </menupopup>
           </toolbarbutton>
           <toolbarbutton label="&btnPageJS.label;" type="menu-button"
                          category="js" class="devtools-toolbarbutton webconsole-filter-button"
                          tooltiptext="&btnPageJS.tooltip;"
                          accesskey="&btnPageJS.accesskey;"
-                         tabindex="5">
+                         tabindex="6">
             <menupopup id="js-contextmenu">
               <menuitem label="&btnConsoleErrors;" type="checkbox"
                         autocheck="false" prefKey="exception"/>
               <menuitem label="&btnConsoleWarnings;" type="checkbox"
                         autocheck="false" prefKey="jswarn"/>
               <menuitem label="&btnConsoleLog;" type="checkbox"
                         autocheck="false" prefKey="jslog"/>
             </menupopup>
           </toolbarbutton>
           <toolbarbutton label="&btnPageSecurity.label;" type="menu-button"
                          category="security" class="devtools-toolbarbutton webconsole-filter-button"
                          tooltiptext="&btnPageSecurity.tooltip;"
                          accesskey="&btnPageSecurity.accesskey;"
-                         tabindex="6">
+                         tabindex="7">
             <menupopup id="security-contextmenu">
               <menuitem label="&btnConsoleErrors;" type="checkbox"
                         autocheck="false" prefKey="secerror"/>
               <menuitem label="&btnConsoleWarnings;" type="checkbox"
                         autocheck="false" prefKey="secwarn"/>
             </menupopup>
           </toolbarbutton>
           <toolbarbutton label="&btnPageLogging.label;" type="menu-button"
                          category="logging" class="devtools-toolbarbutton webconsole-filter-button"
                          tooltiptext="&btnPageLogging.tooltip;"
                          accesskey="&btnPageLogging.accesskey3;"
-                         tabindex="7">
+                         tabindex="8">
             <menupopup id="logging-contextmenu">
               <menuitem label="&btnConsoleErrors;" type="checkbox"
                         autocheck="false" prefKey="error"/>
               <menuitem label="&btnConsoleWarnings;" type="checkbox"
                         autocheck="false" prefKey="warn"/>
               <menuitem label="&btnConsoleInfo;" type="checkbox" autocheck="false"
                         prefKey="info"/>
               <menuitem label="&btnConsoleLog;" type="checkbox" autocheck="false"
@@ -172,33 +176,29 @@ function goUpdateConsoleCommands() {
               <menuitem label="&btnConsoleWindowlessWorkers;" type="checkbox"
                         autocheck="false" prefKey="windowlessworkers"/>
             </menupopup>
           </toolbarbutton>
           <toolbarbutton label="&btnServerLogging.label;" type="menu-button"
                          category="server" class="devtools-toolbarbutton webconsole-filter-button"
                          tooltiptext="&btnServerLogging.tooltip;"
                          accesskey="&btnServerLogging.accesskey;"
-                         tabindex="8">
+                         tabindex="9">
             <menupopup id="server-logging-contextmenu">
               <menuitem label="&btnServerErrors;" type="checkbox"
                         autocheck="false" prefKey="servererror"/>
               <menuitem label="&btnServerWarnings;" type="checkbox"
                         autocheck="false" prefKey="serverwarn"/>
               <menuitem label="&btnServerInfo;" type="checkbox" autocheck="false"
                         prefKey="serverinfo"/>
               <menuitem label="&btnServerLog;" type="checkbox" autocheck="false"
                         prefKey="serverlog"/>
             </menupopup>
           </toolbarbutton>
         </hbox>
-        <toolbarbutton class="webconsole-clear-console-button devtools-toolbarbutton"
-                       label="&btnClear.label;" tooltiptext="&btnClear.tooltip;"
-                       accesskey="&btnClear.accesskey;"
-                       tabindex="9"/>
 
         <spacer flex="1"/>
 
         <textbox class="compact hud-filter-box devtools-searchinput" type="search"
                  placeholder="&filterOutput.placeholder;" tabindex="2"/>
       </toolbar>
 
       <hbox id="output-wrapper" flex="1" context="output-contextmenu" tooltip="aHTMLTooltip">
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -385,16 +385,30 @@ var AnimationPlayerActor = ActorClass({
    */
   getFrames: method(function() {
     return this.player.effect.getFrames();
   }, {
     request: {},
     response: {
       frames: RetVal("json")
     }
+  }),
+
+  /**
+   * Get data about the animated properties of this animation player.
+   * @return {Object} Returns a list of animated properties.
+   * Each property contains a list of values and their offsets
+   */
+  getProperties: method(function() {
+    return this.player.effect.getProperties();
+  }, {
+    request: {},
+    response: {
+      frames: RetVal("json")
+    }
   })
 });
 
 exports.AnimationPlayerActor = AnimationPlayerActor;
 
 var AnimationPlayerFront = FrontClass(AnimationPlayerActor, {
   initialize: function(conn, form, detail, ctx) {
     Front.prototype.initialize.call(this, conn, form, detail, ctx);
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -19,16 +19,17 @@ support-files =
   stylesheets-nested-iframes.html
   timeline-iframe-child.html
   timeline-iframe-parent.html
   director-script-target.html
   storage-helpers.js
 
 [browser_animation_emitMutations.js]
 [browser_animation_getFrames.js]
+[browser_animation_getProperties.js]
 [browser_animation_getMultipleStates.js]
 [browser_animation_getPlayers.js]
 [browser_animation_getStateAfterFinished.js]
 [browser_animation_getSubTreeAnimations.js]
 [browser_animation_keepFinished.js]
 [browser_animation_playerState.js]
 [browser_animation_playPauseIframe.js]
 [browser_animation_playPauseSeveral.js]
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_getProperties.js
@@ -0,0 +1,37 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check that the AnimationPlayerActor exposes a getProperties method that
+// returns the list of animated properties in the animation.
+
+const URL = MAIN_DOMAIN + "animation.html";
+
+add_task(function*() {
+  let {client, walker, animations} =
+    yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
+
+  info("Get the test node and its animation front");
+  let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
+  let [player] = yield animations.getAnimationPlayersForNode(node);
+
+  ok(player.getProperties, "The front has the getProperties method");
+
+  let properties = yield player.getProperties();
+  is(properties.length, 1, "The correct number of properties was retrieved");
+
+  let propertyObject = properties[0];
+  is(propertyObject.property, "transform", "Property 0 is transform");
+
+  is(propertyObject.values.length, 2,
+    "The correct number of property values was retrieved");
+
+  // Note that we don't really test the content of the frame object here on
+  // purpose. This object comes straight out of the web animations API
+  // unmodified.
+
+  yield closeDebuggerClient(client);
+  gBrowser.removeCurrentTab();
+});
--- a/devtools/shared/css-angle.js
+++ b/devtools/shared/css-angle.js
@@ -337,12 +337,8 @@ function classifyAngle(value) {
     return CssAngle.ANGLEUNIT.rad;
   }
   if (value.endsWith("turn")) {
     return CssAngle.ANGLEUNIT.turn;
   }
 
   return CssAngle.ANGLEUNIT.deg;
 }
-
-loader.lazyGetter(this, "DOMUtils", function() {
-  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
-});
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/action/CheckForUpdatesAction.java
+++ b/mobile/android/base/java/org/mozilla/gecko/feeds/action/CheckForUpdatesAction.java
@@ -161,17 +161,17 @@ public class CheckForUpdatesAction exten
             inboxStyle.addLine(StringUtils.stripScheme(url, StringUtils.UrlFlags.STRIP_HTTPS));
             urls.add(url);
         }
         inboxStyle.setSummaryText(context.getString(R.string.content_notification_summary));
 
         Intent intent = new Intent(context, BrowserApp.class);
         intent.setAction(BrowserApp.ACTION_VIEW_MULTIPLE);
         intent.putStringArrayListExtra("urls", urls);
-	    intent.putExtra(EXTRA_CONTENT_NOTIFICATION, true);
+        intent.putExtra(EXTRA_CONTENT_NOTIFICATION, true);
 
         PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
 
         Notification notification = new NotificationCompat.Builder(context)
                 .setSmallIcon(R.drawable.ic_status_logo)
                 .setContentTitle(context.getString(R.string.content_notification_title_plural, feeds.size()))
                 .setContentText(context.getString(R.string.content_notification_summary))
                 .setStyle(inboxStyle)
--- a/mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsSplitPlaneFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsSplitPlaneFragment.java
@@ -388,17 +388,17 @@ public class RemoteTabsSplitPlaneFragmen
 
         @Override
         public boolean onTouch(View v, MotionEvent event) {
             final int action = event.getAction();
             switch (action) {
                 case MotionEvent.ACTION_DOWN:
                     // Enable swipe to refresh iff the first item is visible and is at the top.
                     mRefreshLayout.setEnabled(listView.getCount() <= 0
-                    	    || (listView.getFirstVisiblePosition() <= 0 && listView.getChildAt(0).getTop() >= 0));
+                            || (listView.getFirstVisiblePosition() <= 0 && listView.getChildAt(0).getTop() >= 0));
                     break;
                 case MotionEvent.ACTION_CANCEL:
                 case MotionEvent.ACTION_UP:
                     mRefreshLayout.setEnabled(true);
                     break;
             }
 
             // Event is not handled here, it will be consumed in enclosing SwipeRefreshLayout.
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -156,17 +156,17 @@
 <!ENTITY pref_category_search_restore_defaults "Restore search engines">
 <!ENTITY pref_search_restore_defaults "Restore defaults">
 <!ENTITY pref_search_restore_defaults_summary "Restore defaults">
 <!-- Localization note (pref_search_hint) : "TIP" as in "hint", "clue" etc. Displayed as an
      advisory message on the customise search providers settings page explaining how to add new
      search providers.
      The &formatI; in the string will be replaced by a small image of the icon described, and can be moved to wherever
      it is applicable. -->
-<!ENTITY pref_search_hint "TIP: Add any website to your list of search providers by long-pressing on its search field and then tapping the &formatI; icon.">
+<!ENTITY pref_search_hint2 "TIP: Add any website to your list of search providers by long-pressing on its search field and then touching the &formatI; icon.">
 <!ENTITY pref_category_advanced "Advanced">
 <!ENTITY pref_category_advanced_summary2 "Restore tabs, plugins, developer tools">
 <!ENTITY pref_category_notifications "Notifications">
 <!ENTITY pref_content_notifications "Website updates">
 <!ENTITY pref_content_notifications_summary "Allow notifications for supported sites">
 <!ENTITY pref_developer_remotedebugging_usb "Remote debugging via USB">
 <!ENTITY pref_developer_remotedebugging_wifi "Remote debugging via Wi-Fi">
 <!ENTITY pref_developer_remotedebugging_wifi_disabled_summary "Wi-Fi debugging requires your device to have a QR code reader app installed.">
@@ -276,17 +276,17 @@
 <!-- Localization note (pref_clear_private_data_now_tablet): This action to clear private data is only shown on tablets.
      The action is shown below a header saying "Clear private data"; See pref_clear_private_data -->
 <!ENTITY pref_clear_private_data_now_tablet "Clear now">
 <!ENTITY pref_clear_on_exit_title3 "Clear private data on exit">
 <!ENTITY pref_clear_on_exit_summary2 "&brandShortName; will automatically clear your data whenever you select \u0022Quit\u0022 from the main menu">
 <!ENTITY pref_clear_on_exit_dialog_title "Select which data to clear">
 <!ENTITY pref_plugins "Plugins">
 <!ENTITY pref_plugins_enabled "Enabled">
-<!ENTITY pref_plugins_tap_to_play "Tap to play">
+<!ENTITY pref_plugins_tap_to_play2 "Touch to play">
 <!ENTITY pref_plugins_disabled "Disabled">
 <!ENTITY pref_text_size "Text size">
 <!ENTITY pref_restore_tabs "Restore tabs">
 <!ENTITY pref_restore_always "Always restore">
 <!ENTITY pref_restore_quit "Don\'t restore after quitting &brandShortName;">
 <!ENTITY pref_font_size_tiny "Tiny">
 <!ENTITY pref_font_size_small "Small">
 <!ENTITY pref_font_size_medium "Medium">
@@ -456,17 +456,17 @@ size. -->
 <!ENTITY doorhanger_login_select_toast_copy "Password copied to clipboard">
 <!ENTITY doorhanger_login_select_toast_copy_error "Couldn\'t copy password">
 <!ENTITY doorhanger_login_select_action_text "Select another login">
 <!ENTITY doorhanger_login_select_title "Copy password from">
 
 <!-- Localization note (pref_prevent_magnifying_glass): Label for setting that controls
      whether or not the magnifying glass is disabled. -->
 <!ENTITY pref_magnifying_glass_enabled "Magnify small areas">
-<!ENTITY pref_magnifying_glass_enabled_summary "Enlarge links and form fields when tapping near them">
+<!ENTITY pref_magnifying_glass_enabled_summary2 "Enlarge links and form fields when touching near them">
 
 <!-- Localization note (pref_scroll_title_bar2): Label for setting that controls
      whether or not the dynamic toolbar is enabled. -->
 <!ENTITY pref_scroll_title_bar2 "Full-screen browsing">
 <!ENTITY pref_scroll_title_bar_summary2 "Hide the &brandShortName; toolbar when scrolling down a page">
 
 <!ENTITY pref_tab_queue_title3 "Tab queue">
 <!ENTITY pref_tab_queue_summary4 "Save links until the next time you open &brandShortName;">
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -146,17 +146,17 @@
   <string name="pref_category_vendor">&pref_category_vendor2;</string>
   <string name="pref_category_vendor_summary">&pref_category_vendor_summary2;</string>
   <string name="pref_category_datareporting">&pref_category_datareporting;</string>
   <string name="pref_category_installed_search_engines">&pref_category_installed_search_engines;</string>
   <string name="pref_category_add_search_providers">&pref_category_add_search_providers;</string>
   <string name="pref_category_search_restore_defaults">&pref_category_search_restore_defaults;</string>
   <string name="pref_search_restore_defaults">&pref_search_restore_defaults;</string>
   <string name="pref_search_restore_defaults_summary">&pref_search_restore_defaults_summary;</string>
-  <string name="pref_search_hint">&pref_search_hint;</string>
+  <string name="pref_search_hint">&pref_search_hint2;</string>
 
   <string name="pref_category_language">&pref_category_language;</string>
   <string name="pref_category_language_summary">&pref_category_language_summary;</string>
   <string name="pref_browser_locale">&pref_browser_locale;</string>
   <string name="locale_system_default">&locale_system_default;</string>
 
   <string name="pref_category_advanced">&pref_category_advanced;</string>
   <string name="pref_category_advanced_summary">&pref_category_advanced_summary2;</string>
@@ -221,17 +221,17 @@
   <string name="pref_char_encoding_off">&pref_char_encoding_off;</string>
   <string name="pref_clear_private_data_now">&pref_clear_private_data2;</string>
   <string name="pref_clear_private_data_now_tablet">&pref_clear_private_data_now_tablet;</string>
   <string name="pref_clear_on_exit_title">&pref_clear_on_exit_title3;</string>
   <string name="pref_clear_on_exit_summary2">&pref_clear_on_exit_summary2;</string>
   <string name="pref_clear_on_exit_dialog_title">&pref_clear_on_exit_dialog_title;</string>
   <string name="pref_plugins">&pref_plugins;</string>
   <string name="pref_plugins_enabled">&pref_plugins_enabled;</string>
-  <string name="pref_plugins_tap_to_play">&pref_plugins_tap_to_play;</string>
+  <string name="pref_plugins_tap_to_play">&pref_plugins_tap_to_play2;</string>
   <string name="pref_plugins_disabled">&pref_plugins_disabled;</string>
   <string name="pref_text_size">&pref_text_size;</string>
   <string name="pref_font_size_tiny">&pref_font_size_tiny;</string>
   <string name="pref_font_size_small">&pref_font_size_small;</string>
   <string name="pref_font_size_medium">&pref_font_size_medium;</string>
   <string name="pref_font_size_large">&pref_font_size_large;</string>
   <string name="pref_font_size_xlarge">&pref_font_size_xlarge;</string>
   <string name="pref_font_size_set">&pref_font_size_set;</string>
@@ -389,17 +389,17 @@
   <string name="doorhanger_login_edit_toast_error">&doorhanger_login_edit_toast_error;</string>
   <string name="doorhanger_login_select_message">&doorhanger_login_select_message;</string>
   <string name="doorhanger_login_select_toast_copy">&doorhanger_login_select_toast_copy;</string>
   <string name="doorhanger_login_select_toast_copy_error">&doorhanger_login_select_toast_copy_error;</string>
   <string name="doorhanger_login_select_action_text">&doorhanger_login_select_action_text;</string>
   <string name="doorhanger_login_select_title">&doorhanger_login_select_title;</string>
 
   <string name="pref_magnifying_glass_enabled">&pref_magnifying_glass_enabled;</string>
-  <string name="pref_magnifying_glass_enabled_summary">&pref_magnifying_glass_enabled_summary;</string>
+  <string name="pref_magnifying_glass_enabled_summary">&pref_magnifying_glass_enabled_summary2;</string>
 
   <string name="pref_scroll_title_bar2">&pref_scroll_title_bar2;</string>
   <string name="pref_scroll_title_bar_summary">&pref_scroll_title_bar_summary2;</string>
 
   <string name="page_removed">&page_removed;</string>
 
   <string name="bookmark_edit_title">&bookmark_edit_title;</string>
   <string name="bookmark_edit_name">&bookmark_edit_name;</string>
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
@@ -1,17 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.background.fxa;
 
 import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountClientException
-        .FxAccountClientMalformedResponseException;
+import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientMalformedResponseException;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.HKDF;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BaseResource;
--- a/mobile/locales/en-US/overrides/netError.dtd
+++ b/mobile/locales/en-US/overrides/netError.dtd
@@ -23,57 +23,57 @@
 <ul>
   <li>Check the address for typing errors such as
     <strong>ww</strong>.example.com instead of
     <strong>www</strong>.example.com</li>
     <div id='searchbox'>
       <input id='searchtext' type='search'></input>
       <button id='searchbutton'>Search</button>
     </div>
-  <li>If you are unable to load any pages, check your device's data or Wi-Fi connection.
+  <li>If you are unable to load any pages, check your device’s data or Wi-Fi connection.
     <button id='wifi'>Enable Wi-Fi</button>
   </li>
 </ul>
 ">
 
 <!ENTITY fileNotFound.title "File not found">
 <!ENTITY fileNotFound.longDesc "
 <ul>
   <li>Check the file name for capitalization or other typing errors.</li>
   <li>Check to see if the file was moved, renamed or deleted.</li>
 </ul>
 ">
 
 
 <!ENTITY generic.title "Oops.">
 <!ENTITY generic.longDesc "
-<p>&brandShortName; can't load this page for some reason.</p>
+<p>&brandShortName; can’t load this page for some reason.</p>
 ">
 
-<!ENTITY malformedURI.title "The address isn't valid">
+<!ENTITY malformedURI.title "The address isn’t valid">
 <!-- LOCALIZATION NOTE (malformedURI.longDesc2) This string contains markup including widgets for searching
      or enabling wifi connections. The text inside the tags should be localized. Do not touch the ids. -->
 <!ENTITY malformedURI.longDesc2 "
 <ul>
   <li>Web addresses are usually written like
     <strong>http://www.example.com/</strong></li>
     <div id='searchbox'>
       <input id='searchtext' type='search'></input>
       <button id='searchbutton'>Search</button>
     </div>
-  <li>Make sure that you're using forward slashes (i.e.
+  <li>Make sure that you’re using forward slashes (i.e.
     <strong>/</strong>).</li>
 </ul>
 ">
 
 <!ENTITY netInterrupt.title "The connection was interrupted">
 <!ENTITY netInterrupt.longDesc2 "&sharedLongDesc3;">
 
 <!ENTITY notCached.title "Document Expired">
-<!ENTITY notCached.longDesc "<p>The requested document is not available in &brandShortName;'s cache.</p><ul><li>As a security precaution, &brandShortName; does not automatically re-request sensitive documents.</li><li>Click Try Again to re-request the document from the website.</li></ul>">
+<!ENTITY notCached.longDesc "<p>The requested document is not available in &brandShortName;’s cache.</p><ul><li>As a security precaution, &brandShortName; does not automatically re-request sensitive documents.</li><li>Click Try Again to re-request the document from the website.</li></ul>">
 
 <!ENTITY netOffline.title "Offline mode">
 <!-- LOCALIZATION NOTE (netOffline.longDesc3) This string contains markup including widgets enabling wifi connections.
      The text inside the tags should be localized. Do not touch the ids. -->
 <!ENTITY netOffline.longDesc3 "
 <ul>
   <li>Try again. &brandShortName; will attempt to open a connection and reload the page.
     <button id='wifi'>Enable Wi-Fi</button>
@@ -96,17 +96,17 @@
 ">
 
 <!ENTITY netReset.title "The connection was reset">
 <!ENTITY netReset.longDesc2 "&sharedLongDesc3;">
 
 <!ENTITY netTimeout.title "The connection has timed out">
 <!ENTITY netTimeout.longDesc2 "&sharedLongDesc3;">
 
-<!ENTITY unknownProtocolFound.title "The address wasn't understood">
+<!ENTITY unknownProtocolFound.title "The address wasn’t understood">
 <!ENTITY unknownProtocolFound.longDesc "
 <ul>
   <li>You might need to install other software to open this address.</li>
 </ul>
 ">
 
 <!ENTITY proxyConnectFailure.title "The proxy server is refusing connections">
 <!ENTITY proxyConnectFailure.longDesc "
@@ -124,17 +124,17 @@
 <ul>
   <li>Check the proxy settings to make sure that they are correct.</li>
   <li>Check to make sure your device has a working data or Wi-Fi connection.
     <button id='wifi'>Enable Wi-Fi</button>
   </li>
 </ul>
 ">
 
-<!ENTITY redirectLoop.title "The page isn't redirecting properly">
+<!ENTITY redirectLoop.title "The page isn’t redirecting properly">
 <!ENTITY redirectLoop.longDesc "
 <ul>
   <li>This problem can sometimes be caused by disabling or refusing to accept
     cookies.</li>
 </ul>
 ">
 
 <!ENTITY unknownSocketType.title "Unexpected response from server">
@@ -152,29 +152,29 @@
   <li>The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.</li>
   <li>Please contact the website owners to inform them of this problem.</li>
 </ul>
 ">
 
 <!ENTITY nssBadCert.title "Secure Connection Failed">
 <!ENTITY nssBadCert.longDesc2 "
 <ul>
-  <li>This could be a problem with the server's configuration, or it could be
+  <li>This could be a problem with the server’s configuration, or it could be
 someone trying to impersonate the server.</li>
   <li>If you have connected to this server successfully in the past, the error may
 be temporary, and you can try again later.</li>
 </ul>
 ">
 
 <!-- LOCALIZATION NOTE (sharedLongDesc3) This string contains markup including widgets for enabling wifi connections.
      The text inside the tags should be localized. Do not touch the ids. -->
 <!ENTITY sharedLongDesc3 "
 <ul>
   <li>The site could be temporarily unavailable or too busy. Try again in a few moments.</li>
-  <li>If you are unable to load any pages, check your mobile device's data or Wi-Fi connection.
+  <li>If you are unable to load any pages, check your mobile device’s data or Wi-Fi connection.
     <button id='wifi'>Enable Wi-Fi</button>
   </li>
 </ul>
 ">
 
 <!ENTITY cspBlocked.title "Blocked by Content Security Policy">
 <!ENTITY cspBlocked.longDesc "<p>&brandShortName; prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
 
--- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
+++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
@@ -966,55 +966,56 @@ this.BrowserTestUtils = {
     });
   },
 
   /**
    * Will poll a condition function until it returns true.
    *
    * @param condition
    *        A condition function that must return true or false. If the
-   *        condition ever throws, this is also treated as a false.
+   *        condition ever throws, this is also treated as a false. The
+   *        function can be a generator.
    * @param interval
    *        The time interval to poll the condition function. Defaults
    *        to 100ms.
    * @param attempts
    *        The number of times to poll before giving up and rejecting
    *        if the condition has not yet returned true. Defaults to 50
    *        (~5 seconds for 100ms intervals)
    * @return Promise
    *        Resolves when condition is true.
    *        Rejects if timeout is exceeded or condition ever throws.
    */
   waitForCondition(condition, msg, interval=100, maxTries=50) {
     return new Promise((resolve, reject) => {
       let tries = 0;
-      let intervalID = setInterval(() => {
+      let intervalID = setInterval(Task.async(function* () {
         if (tries >= maxTries) {
           clearInterval(intervalID);
           msg += ` - timed out after ${maxTries} tries.`;
           reject(msg);
           return;
         }
 
         let conditionPassed = false;
         try {
-          conditionPassed = condition();
+          conditionPassed = yield condition();
         } catch(e) {
           msg += ` - threw exception: ${e}`;
           clearInterval(intervalID);
           reject(msg);
           return;
         }
 
         if (conditionPassed) {
           clearInterval(intervalID);
           resolve();
         }
         tries++;
-      }, interval);
+      }), interval);
     });
   },
 
   /**
    * Waits for a <xul:notification> with a particular value to appear
    * for the <xul:notificationbox> of the passed in browser.
    *
    * @param tabbrowser (<xul:tabbrowser>)
--- a/testing/mochitest/tests/SimpleTest/SpawnTask.js
+++ b/testing/mochitest/tests/SimpleTest/SpawnTask.js
@@ -267,17 +267,20 @@ var add_task = (function () {
       // Use setTimeout to ensure the master task runs after the client
       // script finishes.
       setTimeout(function () {
         spawn_task(function* () {
           // We stop the entire test file at the first exception because this
           // may mean that the state of subsequent tests may be corrupt.
           try {
             for (var task of task_list) {
+              var name = task.name || "";
+              info("SpawnTask.js | Entering test " + name);
               yield task();
+              info("SpawnTask.js | Leaving test " + name);
             }
           } catch (ex) {
             try {
               ok(false, "" + ex);
             } catch (ex2) {
               ok(false, "(The exception cannot be converted to string.)");
             }
           }
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -665,26 +665,44 @@ ExtensionData.prototype = {
           resolve(JSON.parse(text));
         } catch (e) {
           reject(e);
         }
       });
     });
   },
 
-  // Reads the extension's |manifest.json| file, and stores its
-  // parsed contents in |this.manifest|.
+  // Reads the extension's |manifest.json| file and optional |mozilla.json|,
+  // and stores the parsed and merged contents in |this.manifest|.
   readManifest() {
     return Promise.all([
       this.readJSON("manifest.json"),
+      this.readJSON("mozilla.json").catch(err => null),
       Management.lazyInit(),
-    ]).then(([manifest]) => {
+    ]).then(([manifest, mozManifest]) => {
       this.manifest = manifest;
       this.rawManifest = manifest;
 
+      if (mozManifest) {
+        if (typeof mozManifest != "object") {
+          this.logger.warn(`Loading extension '${this.id}': mozilla.json has unexpected type ${typeof mozManifest}`);
+        } else {
+          Object.keys(mozManifest).forEach(key => {
+            if (key != "applications") {
+              throw new Error(`Illegal property "${key}" in mozilla.json`);
+            }
+            if (key in manifest) {
+              this.logger.warn(`Ignoring property "${key}" from mozilla.json`);
+            } else {
+              manifest[key] = mozManifest[key];
+            }
+          });
+        }
+      }
+
       if (manifest && manifest.default_locale) {
         return this.initLocale();
       }
     }).then(() => {
       let context = {
         url: this.baseURI && this.baseURI.spec,
 
         principal: this.principal,
--- a/toolkit/components/extensions/ext-downloads.js
+++ b/toolkit/components/extensions/ext-downloads.js
@@ -153,16 +153,17 @@ const DownloadMap = {
           onDownloadAdded(download) {
             const item = self.newFromDownload(download, null);
             self.emit("create", item);
           },
 
           onDownloadRemoved(download) {
             const item = self.byDownload.get(download);
             if (item != null) {
+              self.emit("erase", item);
               self.byDownload.delete(download);
               self.byId.delete(item.id);
             }
           },
 
           onDownloadChanged(download) {
             const item = self.byDownload.get(download);
             if (item == null) {
@@ -216,18 +217,16 @@ const DownloadMap = {
     return item;
   },
 
   erase(item) {
     // This will need to get more complicated for bug 1255507 but for now we
     // only work with downloads in the DownloadList from getAll()
     return this.getDownloadList().then(list => {
       list.remove(item.download);
-      this.byId.delete(item.id);
-      this.byDownload.delete(item.download);
     });
   },
 };
 
 // Create a callable function that filters a DownloadItem based on a
 // query object of the type passed to search() or erase().
 function downloadQuery(query) {
   let queryTerms = [];
@@ -634,13 +633,26 @@ extensions.registerSchemaAPI("downloads"
         });
         return () => {
           registerPromise.then(() => {
             DownloadMap.off("create", handler);
           });
         };
       }).api(),
 
-      onErased: ignoreEvent(context, "downloads.onErased"),
+      onErased: new SingletonEventManager(context, "downloads.onErased", fire => {
+        const handler = (what, item) => {
+          runSafeSync(context, fire, item.id);
+        };
+        let registerPromise = DownloadMap.getDownloadList().then(() => {
+          DownloadMap.on("erase", handler);
+        });
+        return () => {
+          registerPromise.then(() => {
+            DownloadMap.off("erase", handler);
+          });
+        };
+      }).api(),
+
       onDeterminingFilename: ignoreEvent(context, "downloads.onDeterminingFilename"),
     },
   };
 });
--- a/toolkit/components/extensions/schemas/downloads.json
+++ b/toolkit/components/extensions/schemas/downloads.json
@@ -694,17 +694,16 @@
             "name": "downloadItem"
           }
         ],
         "type": "function"
       },
       {
         "description": "Fires with the <code>downloadId</code> when a download is erased from history.",
         "name": "onErased",
-        "unsupported": true,
         "parameters": [
           {
             "name": "downloadId",
             "description": "The <code>id</code> of the <a href='#type-DownloadItem'>DownloadItem</a> that was erased.",
             "type": "integer"
           }
         ],
         "type": "function"
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html
@@ -26,79 +26,125 @@ const BASE = "http://mochi.test:8888/chr
 const TXT_FILE = "file_download.txt";
 const TXT_URL = BASE + "/" + TXT_FILE;
 const INTERRUPTIBLE_URL = BASE + "/interruptible.sjs";
 // Keep these in sync with code in interruptible.sjs
 const INT_PARTIAL_LEN = 15;
 const INT_TOTAL_LEN = 31;
 
 function backgroundScript() {
-  let events = [];
+  let events = new Set();
   let eventWaiter = null;
 
   browser.downloads.onCreated.addListener(data => {
-    events.push({type: "onCreated", data});
+    events.add({type: "onCreated", data});
     if (eventWaiter) {
       eventWaiter();
     }
   });
 
   browser.downloads.onChanged.addListener(data => {
-    events.push({type: "onChanged", data});
+    events.add({type: "onChanged", data});
     if (eventWaiter) {
       eventWaiter();
     }
   });
 
-  function waitForEvents(expected) {
+  browser.downloads.onErased.addListener(data => {
+    events.add({type: "onErased", data});
+    if (eventWaiter) {
+      eventWaiter();
+    }
+  });
+
+  // Returns a promise that will resolve when the given list of expected
+  // events have all been seen.  By default, succeeds only if the exact list
+  // of expected events is seen in the given order.  options.exact can be
+  // set to false to allow other events and options.inorder can be set to
+  // false to allow the events to arrive in any order.
+  function waitForEvents(expected, options = {}) {
     function compare(received, expected) {
       if (typeof expected == "object" && expected != null) {
         if (typeof received != "object") {
           return false;
         }
         return Object.keys(expected).every(fld => compare(received[fld], expected[fld]));
       }
       return (received == expected);
     }
+
+    const exact = ("exact" in options) ? options.exact : true;
+    const inorder = ("inorder" in options) ? options.inorder : true;
     return new Promise((resolve, reject) => {
       function check() {
-        if (events.length < expected.length) {
+        function fail(msg) {
+          browser.test.fail(msg);
+          reject(new Error(msg));
+        }
+        if (events.size < expected.length) {
           return;
         }
-        if (expected.length > events.length) {
-          reject(new Error(`Got ${events.length} events but only expected ${expected.length}`));
+        if (exact && expected.length < events.size) {
+          fail(`Got ${events.size} events but only expected ${expected.length}`);
+          return;
         }
 
-        for (let i = 0; i < expected.length; i++) {
-          if (!compare(events[i], expected[i])) {
-            reject(new Error(`Mismatch in event ${i}, expecting ${JSON.stringify(expected[i])} but got ${JSON.stringify(events[i])}`));
+        let remaining = new Set(events);
+        if (inorder) {
+          for (let event of events) {
+            if (compare(event, expected[0])) {
+              expected.shift();
+              remaining.delete(event);
+            }
           }
+        } else {
+          expected = expected.filter(val => {
+            for (let remainingEvent of remaining) {
+              if (compare(remainingEvent, val)) {
+                remaining.delete(remainingEvent);
+                return false;
+              }
+            }
+            return true;
+          });
         }
 
-        events = [];
-        eventWaiter = null;
-        resolve();
+        // Events that did occur have been removed from expected so if
+        // expected is empty, we're done.  If we didn't see all the
+        // expected events and we're not looking for an exact match,
+        // then we just may not have seen the event yet, so return without
+        // failing and check() will be called again when a new event arrives.
+        if (expected.length == 0) {
+          events = remaining;
+          eventWaiter = null;
+          resolve();
+        } else if (exact) {
+          fail(`Mismatched event: expecting ${JSON.stringify(expected[0])} but got ${JSON.stringify(Array.from(remaining)[0])}`);
+        }
       }
       eventWaiter = check;
       check();
     });
   }
 
   browser.test.onMessage.addListener(function(msg, ...args) {
     let match = msg.match(/(\w+).request$/);
     if (!match) {
       return;
     }
     let what = match[1];
     if (what == "waitForEvents") {
-      waitForEvents(arguments[1]).then(() => {
+      waitForEvents(...args).then(() => {
         browser.test.sendMessage("waitForEvents.done", {status: "success"});
       }).catch(error => {
         browser.test.sendMessage("waitForEvents.done", {status: "error", errmsg: error.message});
       });
+    } else if (what == "clearEvents") {
+      events = new Set();
+      browser.test.sendMessage("clearEvents.done", {status: "success"});
     } else {
       // extension functions throw on bad arguments, we can remove the extra
       // promise when bug 1250223 is fixed.
       Promise.resolve().then(() => {
         return browser.downloads[what](...args);
       }).then(result => {
         browser.test.sendMessage(`${what}.done`, {status: "success", result});
       }).catch(error => {
@@ -117,18 +163,18 @@ function clearDownloads(callback) {
   return Downloads.getList(Downloads.ALL).then(list => {
     return list.getAll().then(downloads => {
       return Promise.all(downloads.map(download => list.remove(download)))
                     .then(() => downloads);
     });
   });
 }
 
-function runInExtension(what, args) {
-  extension.sendMessage(`${what}.request`, args);
+function runInExtension(what, ...args) {
+  extension.sendMessage(`${what}.request`, ...args);
   return extension.awaitMessage(`${what}.done`);
 }
 
 // This is pretty simplistic, it looks for a progress update for a
 // download of the given url in which the total bytes are exactly equal
 // to the given value.  Unless you know exactly how data will arrive from
 // the server (eg see interruptible.sjs), it probably isn't very useful.
 function waitForProgress(url, bytes) {
@@ -235,18 +281,28 @@ add_task(function* test_cancel() {
       type: "onChanged",
       data: {
         id,
         error: {
           previous: null,
           current: "USER_CANCELED",
         },
       },
-    }]);
-  is(msg.status, "success", "got onChanged event corresponding to pause");
+    }, {
+      type: "onChanged",
+      data: {
+        id,
+        paused: {
+          previous: true,
+          current: false,
+        },
+      },
+    },
+  ]);
+  is(msg.status, "success", "got onChanged events corresponding to cancel()");
 
   msg = yield runInExtension("search", {error: "USER_CANCELED"});
   is(msg.status, "success", "search() succeeded");
   is(msg.result.length, 1, "search() found 1 download");
   is(msg.result[0].id, id, "download.id is correct");
   is(msg.result[0].state, "interrupted", "download.state is correct");
   is(msg.result[0].paused, false, "download.paused is correct");
   is(msg.result[0].canResume, false, "download.canResume is correct");
@@ -292,16 +348,25 @@ add_task(function* test_pauseresume() {
           previous: false,
           current: true,
         },
         canResume: {
           previous: false,
           current: true,
         },
       },
+    }, {
+      type: "onChanged",
+      data: {
+        id,
+        error: {
+          previous: null,
+          current: "USER_CANCELED",
+        },
+      },
     }]);
   is(msg.status, "success", "got onChanged event corresponding to pause");
 
   msg = yield runInExtension("search", {paused: true});
   is(msg.status, "success", "search() succeeded");
   is(msg.result.length, 1, "search() found 1 download");
   is(msg.result[0].id, id, "download.id is correct");
   is(msg.result[0].state, "interrupted", "download.state is correct");
@@ -408,16 +473,25 @@ add_task(function* test_pausecancel() {
           previous: false,
           current: true,
         },
         canResume: {
           previous: false,
           current: true,
         },
       },
+    }, {
+      type: "onChanged",
+      data: {
+        id,
+        error: {
+          previous: null,
+          current: "USER_CANCELED",
+        },
+      },
     }]);
   is(msg.status, "success", "got onChanged event corresponding to pause");
 
   msg = yield runInExtension("search", {paused: true});
   is(msg.status, "success", "search() succeeded");
   is(msg.result.length, 1, "search() found 1 download");
   is(msg.result[0].id, id, "download.id is correct");
   is(msg.result[0].state, "interrupted", "download.state is correct");
@@ -585,44 +659,63 @@ add_task(function* test_removal_of_incom
 });
 
 // Test erase().  We don't do elaborate testing of the query handling
 // since it uses the exact same engine as search() which is tested
 // more thoroughly in test_chrome_ext_downloads_search.html
 add_task(function* test_erase() {
   yield clearDownloads();
 
-  let ids = {};
-  let msg = yield runInExtension("download", {url: TXT_URL});
-  is(msg.status, "success", "download succeeded");
-  ids.dl1 = msg.result;
+  yield runInExtension("clearEvents");
+
+  function* download() {
+    let msg = yield runInExtension("download", {url: TXT_URL});
+    is(msg.status, "success", "download succeeded");
+    let id = msg.result;
 
-  msg = yield runInExtension("download", {url: TXT_URL});
-  is(msg.status, "success", "download succeeded");
-  ids.dl2 = msg.result;
+    msg = yield runInExtension("waitForEvents", [{
+      type: "onChanged", data: {id, state: {current: "complete"}},
+    }], {exact: false});
+    is(msg.status, "success", "download finished");
 
-  msg = yield runInExtension("download", {url: TXT_URL});
-  is(msg.status, "success", "download succeeded");
-  ids.dl3 = msg.result;
+    return id;
+  }
 
-  msg = yield runInExtension("search", {});
+  let ids = {};
+  ids.dl1 = yield download();
+  ids.dl2 = yield download();
+  ids.dl3 = yield download();
+
+  let msg = yield runInExtension("search", {});
   is(msg.status, "success", "search succeded");
   is(msg.result.length, 3, "search found 3 downloads");
 
+  msg = yield runInExtension("clearEvents");
+
   msg = yield runInExtension("erase", {id: ids.dl1});
-  if (msg.errmsg) { info(msg.errmsg); }
   is(msg.status, "success", "erase by id succeeded");
 
+  msg = yield runInExtension("waitForEvents", [
+    {type: "onErased", data: ids.dl1},
+  ]);
+  is(msg.status, "success", "received onErased event");
+
   msg = yield runInExtension("search", {});
   is(msg.status, "success", "search succeded");
   is(msg.result.length, 2, "search found 2 downloads");
 
   msg = yield runInExtension("erase", {});
   is(msg.status, "success", "erase everything succeeded");
 
+  msg = yield runInExtension("waitForEvents", [
+    {type: "onErased", data: ids.dl2},
+    {type: "onErased", data: ids.dl3},
+  ], {inorder: false});
+  is(msg.status, "success", "received 2 onErased events");
+
   msg = yield runInExtension("search", {});
   is(msg.status, "success", "search succeded");
   is(msg.result.length, 0, "search found 0 downloads");
 });
 
 add_task(function* cleanup() {
   yield extension.unload();
 });
--- a/toolkit/components/passwordmgr/test/LoginTestUtils.jsm
+++ b/toolkit/components/passwordmgr/test/LoginTestUtils.jsm
@@ -238,8 +238,20 @@ this.LoginTestUtils.testData = {
 
       new LoginInfo("chrome://example_extension", null, "Example Login One",
                     "the username", "the password one", "", ""),
       new LoginInfo("chrome://example_extension", null, "Example Login Two",
                     "the username", "the password two", "", ""),
     ];
   },
 };
+
+this.LoginTestUtils.recipes = {
+  getRecipeParent() {
+    let { LoginManagerParent } = Cu.import("resource://gre/modules/LoginManagerParent.jsm", {});
+    if (!LoginManagerParent.recipeParentPromise) {
+      return null;
+    }
+    return LoginManagerParent.recipeParentPromise.then((recipeParent) => {
+      return recipeParent;
+    });
+  },
+};
--- a/toolkit/components/passwordmgr/test/browser/browser.ini
+++ b/toolkit/components/passwordmgr/test/browser/browser.ini
@@ -1,17 +1,31 @@
 [DEFAULT]
 support-files =
+  ../formsubmit.sjs
+  ../subtst_notifications_1.html
+  ../subtst_notifications_2.html
+  ../subtst_notifications_2pw_0un.html
+  ../subtst_notifications_2pw_1un_1text.html
+  ../subtst_notifications_3.html
+  ../subtst_notifications_4.html
+  ../subtst_notifications_5.html
+  ../subtst_notifications_6.html
+  ../subtst_notifications_8.html
+  ../subtst_notifications_9.html
+  ../subtst_notifications_10.html
   authenticate.sjs
   form_basic.html
+  head.js
   insecure_test.html
   insecure_test_subframe.html
   multiple_forms.html
   streamConverter_content.sjs
 
+[browser_capture_doorhanger.js]
 [browser_DOMFormHasPassword.js]
 [browser_DOMInputPasswordAdded.js]
 [browser_filldoorhanger.js]
 [browser_hasInsecureLoginForms.js]
 [browser_hasInsecureLoginForms_streamConverter.js]
 [browser_notifications.js]
 skip-if = true # Intermittent failures: Bug 1182296, bug 1148771
 [browser_passwordmgr_editing.js]
rename from toolkit/components/passwordmgr/test/test_notifications.html
rename to toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger.js
--- a/toolkit/components/passwordmgr/test/test_notifications.html
+++ b/toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger.js
@@ -1,619 +1,410 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>Test for Login Manager</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="pwmgr_common.js"></script>
-  <script type="text/javascript" src="notification_common.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-Login Manager test: notifications
-<p id="display"></p>
+/*
+ * Test capture popup notifications
+ */
+
+const BRAND_BUNDLE = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+const BRAND_SHORT_NAME = BRAND_BUNDLE.GetStringFromName("brandShortName");
+
+let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
+                                             Ci.nsILoginInfo, "init");
+let login1 = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
+                             "notifyu1", "notifyp1", "user", "pass");
+let login2 = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
+                             "", "notifyp1", "", "pass");
+let login1B = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
+                              "notifyu1B", "notifyp1B", "user", "pass");
+let login2B = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
+                              "", "notifyp1B", "", "pass");
+
+requestLongerTimeout(2);
+
+add_task(function* setup() {
+  // Load recipes for this test.
+  let recipeParent = yield LoginManagerParent.recipeParentPromise;
+  yield recipeParent.load({
+    siteRecipes: [{
+      hosts: ["example.org"],
+      usernameSelector: "#user",
+      passwordSelector: "#pass",
+    }],
+  });
+});
 
-<div id="content" style="display: none">
-  <iframe id="iframe"></iframe>
-</div>
+add_task(function* test_remember_opens() {
+  yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "got notification popup");
+    notif.remove();
+  });
+});
 
-<pre id="test">
-<script class="testbody" type="application/javascript;version=1.8">
+add_task(function* test_clickNever() {
+  yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "got notification popup");
+    is(true, Services.logins.getLoginSavingEnabled("http://mochi.test:8888"),
+       "Checking for login saving enabled");
+    clickDoorhangerButton(notif, NEVER_BUTTON);
+  });
 
-/** Test for Login Manager: notifications. **/
-
-const { Services } = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm");
+  info("Make sure Never took effect");
+  yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(!notif, "checking for no notification popup");
+    is(false, Services.logins.getLoginSavingEnabled("http://mochi.test:8888"),
+       "Checking for login saving disabled");
+    Services.logins.setLoginSavingEnabled("http://mochi.test:8888", true);
+  });
+});
 
-const BRAND_BUNDLE = "chrome://branding/locale/brand.properties";
+add_task(function* test_clickRemember() {
+  yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "got notification popup");
 
-// Set testpath to the directory where we live. Used to load tests from
-// alternate Mochitest servers (different hostnames, same content).
-var testpath = document.location.pathname + "/../";
+    is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
+    clickDoorhangerButton(notif, REMEMBER_BUTTON);
+  });
 
-var subtests = [
-                   "subtst_notifications_1.html", // 1
-                   "subtst_notifications_1.html", // 2
-                   "subtst_notifications_1.html", // 3
-                   "subtst_notifications_1.html", // 4
-                   "subtst_notifications_1.html", // 5
-                   "subtst_notifications_1.html", // 6
-                   "subtst_notifications_1.html", // 7
-                   "subtst_notifications_1.html", // 8
-                   "subtst_notifications_2.html", // 9
-                   "subtst_notifications_3.html", // 10
-                   "subtst_notifications_4.html", // 11
-                   "subtst_notifications_5.html", // 12
-                   "subtst_notifications_1.html", // 13
-                   "subtst_notifications_6.html", // 14
-                   "subtst_notifications_1.html", // 15
-                   "subtst_notifications_6.html", // 16
-                   "subtst_notifications_8.html", // 17
-                   "subtst_notifications_8.html", // 18
-                   "subtst_notifications_9.html", // 19
-                   "subtst_notifications_10.html",  // 20
-                   "http://test1.example.org:80" + testpath + "subtst_notifications_1.html", // 21
-                   "http://test1.example.org:80" + testpath + "subtst_notifications_7.html", // 22
-                   "http://test1.example.org:80" + testpath + "subtst_notifications_6.html", // 23
-                   "subtst_notifications_2pw_0un.html",  // 24
-                   "subtst_notifications_2pw_0un.html",  // 25
-                   "subtst_notifications_2pw_0un.html",  // 26
-                   "subtst_notifications_2pw_0un.html",  // 27
-                   "subtst_notifications_2pw_0un.html",  // 28
-                   "http://example.org" + testpath + "subtst_notifications_2pw_1un_1text.html", // 29
-                   "http://example.org" + testpath + "subtst_notifications_2pw_1un_1text.html", // 30
-                   "subtst_notifications_1.html", // 31
-               ];
+  info("Make sure Remember took effect and we don't prompt for an existing login");
+  yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(!notif, "checking for no notification popup");
+  });
+
+  checkOnlyLoginWasUsedTwice({ justChanged: false });
+
+  // remove that login
+  Services.logins.removeLogin(login1);
+});
+
+/* signons.rememberSignons pref tests... */
+
+add_task(function* test_rememberSignonsFalse() {
+  info("Make sure we don't prompt with rememberSignons=false");
+  Services.prefs.setBoolPref("signon.rememberSignons", false);
+
+  yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(!notif, "checking for no notification popup");
+  });
+});
+
+add_task(function* test_rememberSignonsTrue() {
+  info("Make sure we prompt with rememberSignons=true");
+  Services.prefs.setBoolPref("signon.rememberSignons", true);
 
+  yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "got notification popup");
+    notif.remove();
+  });
+});
 
-var ignoreLoad = false;
-function handleLoad(aEvent) {
-    // ignore every other load event ... We get one for loading the subtest (which
-    // we want to ignore), and another when the subtest's form submits itself
-    // (which we want to handle, to start the next test).
-    ignoreLoad = !ignoreLoad;
-    if (ignoreLoad) {
-        ok(true, "Ignoring load of subtest #" + testNum);
-        return;
-    }
-    ok(true, "Processing submission of subtest #" + testNum);
+/* autocomplete=off tests... */
+
+add_task(function* test_autocompleteOffUsername() {
+  info("Check for notification popup when autocomplete=off present on username");
+
+  yield testSubmittingLoginForm("subtst_notifications_2.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "checking for notification popup");
+    notif.remove();
+  });
+});
 
-    checkTest();
+add_task(function* test_autocompleteOffPassword() {
+  info("Check for notification popup when autocomplete=off present on password");
 
-    testNum++;
+  yield testSubmittingLoginForm("subtst_notifications_3.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "checking for notification popup");
+    notif.remove();
+  });
+});
 
-    if (testNum <= subtests.length) {
-        ok(true, "Starting test #" + testNum);
-        iframe.src = subtests[testNum-1];
-    } else {
-        ok(true, "notification tests finished.");
-        SimpleTest.finish();
-    }
-}
+add_task(function* test_autocompleteOffForm() {
+  info("Check for notification popup when autocomplete=off present on form");
+
+  yield testSubmittingLoginForm("subtst_notifications_4.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "checking for notification popup");
+    notif.remove();
+  });
+});
 
 
-// Remember, Never for This Site, Not Now
-function checkTest() {
-    var popup, notificationText, expectedText;
-
-    // The document generated from formsubmit.sjs contains the user/pass it
-    // received inside <span id="blah">value</span>
-    var gotUser = SpecialPowers.wrap(iframe).contentDocument.getElementById("user").textContent;
-    var gotPass = SpecialPowers.wrap(iframe).contentDocument.getElementById("pass").textContent;
-
-    let brandBundle = Services.strings.createBundle(BRAND_BUNDLE);
-    let brandShortName = brandBundle.GetStringFromName("brandShortName");
-
-    switch(testNum) {
-
-      /* Basic Yes/No/Never tests... */
-
-      case 1:
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(popup, "got notification popup");
-        popup.remove();
-        break;
+add_task(function* test_noPasswordField() {
+  info("Check for no notification popup when no password field present");
 
-      case 2:
-        // Same subtest, this time click Never
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(popup, "got notification popup");
-        is(true, pwmgr.getLoginSavingEnabled("http://mochi.test:8888"),
-           "Checking for login saving enabled");
-        clickPopupButton(popup, kNeverButton);
-        break;
-
-      case 3:
-        // Same subtest, make sure Never took effect
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(!popup, "checking for no notification popup");
-        is(false, pwmgr.getLoginSavingEnabled("http://mochi.test:8888"),
-           "Checking for login saving disabled");
-        // reenable login saving.
-        pwmgr.setLoginSavingEnabled("http://mochi.test:8888", true);
-        break;
-
-      case 4:
-        // Same subtest, this time click Remember
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(popup, "got notification popup");
-
-        // Sanity check, no logins should exist yet.
-        var logins = pwmgr.getAllLogins();
-        is(logins.length, 0, "Should not have any logins yet");
-
-        clickPopupButton(popup, kRememberButton);
-        break;
+  yield testSubmittingLoginForm("subtst_notifications_5.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "null", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(!notif, "checking for no notification popup");
+  });
+});
 
-      case 5:
-        // Same subtest, make sure we didn't prompt for an existing login.
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(!popup, "checking for no notification popup");
-
-        // Check to make sure we updated the timestamps and use count on the
-        // existing login that was submitted for this form.
-        var logins = pwmgr.getAllLogins();
-        is(logins.length, 1, "Should only have 1 login");
-        ok(SpecialPowers.call_Instanceof(logins[0], Ci.nsILoginMetaInfo), "metainfo QI");
-        is(logins[0].timesUsed, 2, "check .timesUsed for existing login submission");
-        ok(logins[0].timeLastUsed > logins[0].timeCreated, "timeLastUsed bumped");
-        ok(logins[0].timeCreated == logins[0].timePasswordChanged, "timeChanged not updated");
-
-        // remove that login
-        pwmgr.removeLogin(login1);
-        break;
-
-      /* signons.rememberSignons pref tests... */
+add_task(function* test_pwOnlyLoginMatchesForm() {
+  info("Check for update popup when existing pw-only login matches form.");
+  Services.logins.addLogin(login2);
 
-      case 6:
-        // Same subtest, make sure we're getting the popup again.
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(popup, "got notification popup");
-        popup.remove();
-        // Change prefs to no longer remember signons
-        prefs.setBoolPref("rememberSignons", false);
-        break;
+  yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-change");
+    ok(notif, "checking for notification popup");
+    notif.remove();
+  });
 
-      case 7:
-        // Same subtest, make sure we're not prompting.
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(!popup, "checking for no notification popup");
-        // Change prefs to remember signons again
-        prefs.setBoolPref("rememberSignons", true);
-        break;
-
-      case 8:
-        // Same subtest, make sure we're getting the popup again.
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(popup, "got notification popup");
-        popup.remove();
-        break;
-
-      /* autocomplete=off tests... */
-
-      case 9:
-        // Check for notification popup when autocomplete=off present
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(popup, "checking for notification popup");
-        popup.remove();
-        break;
+  Services.logins.removeLogin(login2);
+});
 
-      case 10:
-        // Check for notification popup when autocomplete=off present
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(popup, "checking for notification popup");
-        popup.remove();
-        break;
-
-      case 11:
-        // Check for notification popup when autocomplete=off present
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(popup, "checking for notification popup");
-        popup.remove();
-        break;
-
-      /* no password field test... */
-
-      case 12:
-        // Check for no notification popup when no password field present
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "null",     "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(!popup, "checking for no notification popup");
-
-        // Add login for the next test.
-        pwmgr.addLogin(login2);
-        break;
+add_task(function* test_pwOnlyFormMatchesLogin() {
+  info("Check for no notification popup when pw-only form matches existing login.");
+  Services.logins.addLogin(login1);
 
-      case 13:
-        // Check for update popup when existing pw-only login matches form.
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-change");
-        ok(popup, "checking for notification popup");
-        popup.remove();
-        pwmgr.removeLogin(login2);
-
-        // Add login for the next test
-        pwmgr.addLogin(login1);
-        break;
+  yield testSubmittingLoginForm("subtst_notifications_6.html", function*(fieldValues) {
+    is(fieldValues.username, "null", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(!notif, "checking for no notification popup");
+  });
 
-      case 14:
-        // Check for no notification popup when pw-only form matches existing login.
-        is(gotUser, "null",     "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(!popup, "checking for no notification popup");
-        pwmgr.removeLogin(login1);
-
-        // Add login for the next test
-        pwmgr.addLogin(login2B);
-        break;
-
-      case 15:
-        // Check for notification popup when existing pw-only login doesn't match form.
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(popup, "got notification popup");
-        pwmgr.removeLogin(login2B);
-        popup.remove();
+  Services.logins.removeLogin(login1);
+});
 
-        // Add login for the next test
-        pwmgr.addLogin(login1B);
-        break;
-
-      case 16:
-        // Check for notification popup when pw-only form doesn't match existing login.
-        is(gotUser, "null",     "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(popup, "got notification popup");
-        pwmgr.removeLogin(login1B);
-        popup.remove();
-
-        // Add login for the next tests
-        pwmgr.addLogin(login1);
-        break;
-
-      case 17:
-        // Check for change-password popup, u+p login on u+p form. (not changed)
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "pass2",    "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-change");
-        ok(popup, "got notification popup");
-        clickPopupButton(popup, kDontChangeButton);
-        break;
+add_task(function* test_pwOnlyFormDoesntMatchExisting() {
+  info("Check for notification popup when pw-only form doesn't match existing login.");
+  Services.logins.addLogin(login1B);
 
-      case 18:
-        // Check for change-password popup, u+p login on u+p form.
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "pass2",    "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-change");
-        ok(popup, "got notification popup");
-        clickPopupButton(popup, kChangeButton);
-
-        // Check to make sure we updated the timestamps and use count for
-        // the login being changed with this form.
-        var logins = pwmgr.getAllLogins();
-        is(logins.length, 1, "Should only have 1 login");
-        ok(SpecialPowers.call_Instanceof(logins[0], Ci.nsILoginMetaInfo), "metainfo QI");
-        is(logins[0].timesUsed, 2, "check .timesUsed incremented on change");
-        ok(logins[0].timeCreated < logins[0].timeLastUsed, "timeLastUsed bumped");
-        ok(logins[0].timeLastUsed == logins[0].timePasswordChanged, "timeUsed == timeChanged");
+  yield testSubmittingLoginForm("subtst_notifications_6.html", function*(fieldValues) {
+    is(fieldValues.username, "null", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "got notification popup");
+    notif.remove();
+  });
 
-        // cleanup
-        login1.password = "pass2";
-        pwmgr.removeLogin(login1);
-        login1.password = "notifyp1";
-
-        // Add login for the next test
-        pwmgr.addLogin(login2);
-        break;
-
-      // ...can't change a u+p login on a p-only form...
+  Services.logins.removeLogin(login1B);
+});
 
-      case 19:
-        // Check for change-password popup, p-only login on u+p form.
-        // (needed a different subtest for this because the login created in
-        // test_0init was interfering)
-        is(gotUser, "",         "Checking submitted username");
-        is(gotPass, "pass2",    "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-change");
-        ok(popup, "got notification popup");
-        clickPopupButton(popup, kChangeButton);
-        break;
+add_task(function* test_changeUPLoginOnUPForm_dont() {
+  info("Check for change-password popup, u+p login on u+p form. (not changed)");
+  Services.logins.addLogin(login1);
 
-      case 20:
-        // Check for change-password popup, p-only login on p-only form.
-        is(gotUser, "null",     "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-change");
-        ok(popup, "got notification popup");
-        clickPopupButton(popup, kChangeButton);
-
-        pwmgr.removeLogin(login2);
-        break;
-
-      case 21:
-        // Check text on a user+pass notification popup
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(popup, "got notification popup");
-        // Check the text, which comes from the localized saveLoginText string.
-        notificationText = popup.message;
-        expectedText = "Would you like " + brandShortName + " to remember this login?";
-        is(expectedText, notificationText, "Checking text: " + notificationText);
-        popup.remove();
-        break;
+  yield testSubmittingLoginForm("subtst_notifications_8.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "pass2", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-change");
+    ok(notif, "got notification popup");
+    clickDoorhangerButton(notif, DONT_CHANGE_BUTTON);
+  });
 
-      case 22:
-        // Check text on a user+pass notification popup, username is really long
-        is(gotUser, "nowisthetimeforallgoodmentocometotheaidoftheircountry", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(popup, "got notification popup");
-        // Check the text, which comes from the localized saveLoginText string.
-        notificationText = popup.message;
-        expectedText = "Would you like " + brandShortName + " to remember this login\?";
-        is(expectedText, notificationText, "Checking text: " + notificationText);
-        popup.remove();
-        break;
-
-      case 23:
-        // Check text on a pass-only notification popup
-        is(gotUser, "null",     "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(popup, "got notification popup");
-        // Check the text, which comes from the localized saveLoginTextNoUser string.
-        notificationText = popup.message;
-        expectedText = "Would you like " + brandShortName + " to remember this password\?";
-        is(expectedText, notificationText, "Checking text: " + notificationText);
-        popup.remove();
-        break;
-
-      case 24:
-        // Check for notification popup when a form with 2 password fields (no username) is
-        // submitted and there are no saved logins.
-        is(gotUser, "null", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(popup, "got notification popup");
-        popup.remove();
-
-        // Add login for the next test
-        pwmgr.addLogin(login1B);
-        break;
+  Services.logins.removeLogin(login1);
+});
 
-      case 25:
-        // Check for notification popup when a form with 2 password fields (no username) is
-        // submitted and there is a saved login with a username and different password.
-        is(gotUser, "null", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-change");
-        ok(popup, "got notification popup");
-        popup.remove();
-        // remove that login
-        pwmgr.removeLogin(login1B);
-
-        // Add login for the next test
-        pwmgr.addLogin(login2B);
-        break;
-
-      case 26:
-        // Check for notification popup when a form with 2 password fields (no username) is
-        // submitted and there is a saved login with no username and a different password.
-        is(gotUser, "null", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-change");
-        ok(popup, "got notification popup");
-        popup.remove();
-        // remove that login
-        pwmgr.removeLogin(login2B);
-
-        // Add login for the next test
-        pwmgr.addLogin(login1);
-
-        break;
+add_task(function* test_changeUPLoginOnUPForm_change() {
+  info("Check for change-password popup, u+p login on u+p form.");
+  Services.logins.addLogin(login1);
 
-      case 27:
-        // Check for no notification popup when a form with 2 password fields (no username) is
-        // submitted and there is a saved login with a username and the same password.
-        is(gotUser, "null", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-change");
-        ok(!popup, "checking for no notification popup");
+  yield testSubmittingLoginForm("subtst_notifications_8.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "pass2", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-change");
+    ok(notif, "got notification popup");
+    clickDoorhangerButton(notif, CHANGE_BUTTON);
+    ok(!getCaptureDoorhanger("password-change"), "popup should be gone");
+  });
 
-        // Check to make sure we updated the timestamps and use count on the
-        // existing login that was submitted for this form.
-        var logins = pwmgr.getAllLogins();
-        is(logins.length, 1, "Should only have 1 login");
-        ok(SpecialPowers.call_Instanceof(logins[0], Ci.nsILoginMetaInfo), "metainfo QI");
-        is(logins[0].timesUsed, 2, "check .timesUsed for existing login submission");
-        ok(logins[0].timeLastUsed > logins[0].timeCreated, "timeLastUsed bumped");
-        ok(logins[0].timeCreated == logins[0].timePasswordChanged, "timeChanged not updated");
-
-        // remove that login
-        pwmgr.removeLogin(login1);
-
-        // Add login for the next test
-        pwmgr.addLogin(login2);
-        break;
-
-      case 28:
-        // Check for no notification popup when a form with 2 password fields (no username) is
-        // submitted and there is a saved login with no username and the same password.
-        is(gotUser, "null", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-change");
-        ok(!popup, "checking for no notification popup");
+  checkOnlyLoginWasUsedTwice({ justChanged: true });
 
-        // Check to make sure we updated the timestamps and use count on the
-        // existing login that was submitted for this form.
-        var logins = pwmgr.getAllLogins();
-        is(logins.length, 1, "Should only have 1 login");
-        ok(SpecialPowers.call_Instanceof(logins[0], Ci.nsILoginMetaInfo), "metainfo QI");
-        is(logins[0].timesUsed, 2, "check .timesUsed for existing login submission");
-        ok(logins[0].timeLastUsed > logins[0].timeCreated, "timeLastUsed bumped");
-        ok(logins[0].timeCreated == logins[0].timePasswordChanged, "timeChanged not updated");
-
-        // remove that login
-        pwmgr.removeLogin(login2);
-        break;
-
-      case 29: {
-        // Check that we capture the proper fields when a field recipe is in use.
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(popup, "got notification popup");
-
-        // Sanity check, no logins should exist yet.
-        let logins = pwmgr.getAllLogins();
-        is(logins.length, 0, "Should not have any logins yet");
-
-        clickPopupButton(popup, kRememberButton);
-        break;
-      }
+  // cleanup
+  login1.password = "pass2";
+  Services.logins.removeLogin(login1);
+  login1.password = "notifyp1";
+});
 
-      case 30: {
-        // Same subtest, make sure we didn't prompt for an existing login.
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(!popup, "checking for no notification popup");
-
-        // Check to make sure we updated the timestamps and use count on the
-        // existing login that was submitted for this form.
-        let logins = pwmgr.getAllLogins();
-        is(logins.length, 1, "Should only have 1 login");
-        ok(SpecialPowers.call_Instanceof(logins[0], Ci.nsILoginMetaInfo), "metainfo QI");
-        is(logins[0].username, "notifyu1", "check .username for existing login submission");
-        is(logins[0].password, "notifyp1", "check .password for existing login submission");
-        is(logins[0].timesUsed, 2, "check .timesUsed for existing login submission");
-        ok(logins[0].timeLastUsed > logins[0].timeCreated, "timeLastUsed bumped");
-        ok(logins[0].timeCreated == logins[0].timePasswordChanged, "timeChanged not updated");
-
-        // remove the added login
-        pwmgr.removeAllLogins();
-
-        // Add login for the next test
-        pwmgr.addLogin(login3);
-
-        break;
-      }
-
-      case 31: {
-        // make sure we didn't prompt for an existing login with different
-        // scheme for formSubmitURL.
-        is(gotUser, "notifyu1", "Checking submitted username");
-        is(gotPass, "notifyp1", "Checking submitted password");
-        popup = getPopup(popupNotifications, "password-save");
-        ok(!popup, "checking for no notification popup");
+add_task(function* test_changePLoginOnUPForm() {
+  info("Check for change-password popup, p-only login on u+p form.");
+  Services.logins.addLogin(login2);
 
-        // Check to make sure we updated the timestamps and use count on the
-        // existing login that was submitted for this form.
-        var logins = pwmgr.getAllLogins();
-        is(logins.length, 1, "Should only have 1 login");
-        ok(SpecialPowers.call_Instanceof(logins[0], Ci.nsILoginMetaInfo), "metainfo QI");
-        is(logins[0].timesUsed, 2, "check .timesUsed for existing login submission");
-        ok(logins[0].timeLastUsed > logins[0].timeCreated, "timeLastUsed bumped");
-        ok(logins[0].timeCreated == logins[0].timePasswordChanged, "timeChanged not updated");
-
-        // remove that login
-        pwmgr.removeAllLogins();
-        break;
-      }
-      default:
-        ok(false, "Unexpected call to checkTest for test #" + testNum);
-
-    }
-
-    // TODO:
-    // * existing login test, form has different password --> change password, no save prompt
-}
-
-const Ci = SpecialPowers.Ci;
-const Cc = SpecialPowers.Cc;
-ok(Ci != null, "Access Ci");
-ok(Cc != null, "Access Cc");
-
-var pwmgr = Cc["@mozilla.org/login-manager;1"].
-            getService(Ci.nsILoginManager);
-ok(pwmgr != null, "Access pwmgr");
-
-pwmgr.removeAllLogins();
+  yield testSubmittingLoginForm("subtst_notifications_9.html", function*(fieldValues) {
+    is(fieldValues.username, "", "Checking submitted username");
+    is(fieldValues.password, "pass2", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-change");
+    ok(notif, "got notification popup");
+    clickDoorhangerButton(notif, CHANGE_BUTTON);
+    ok(!getCaptureDoorhanger("password-change"), "popup should be gone");
+  });
+});
 
-var prefs = Cc["@mozilla.org/preferences-service;1"].
-            getService(Ci.nsIPrefService);
-ok(prefs != null, "Access prefs");
-prefs = prefs.getBranch("signon.");
-ok(prefs != null, "Access pref branch");
-
-var nsLoginInfo = new SpecialPowers.wrap(SpecialPowers.Components).Constructor("@mozilla.org/login-manager/loginInfo;1",
-                                             Ci.nsILoginInfo, "init");
-var login1 = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
-                             "notifyu1", "notifyp1", "user", "pass");
-var login2 = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
-                             "", "notifyp1", "", "pass");
-var login1B = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
-                              "notifyu1B", "notifyp1B", "user", "pass");
-var login2B = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
-                              "", "notifyp1B", "", "pass");
-var login3 = new nsLoginInfo("http://mochi.test:8888", "https://mochi.test:8888", null,
-                             "notifyu1", "notifyp1", "user", "pass");
+add_task(function* test_changePLoginOnPForm() {
+  info("Check for change-password popup, p-only login on p-only form.");
 
-var parentScriptURL = SimpleTest.getTestFileURL("pwmgr_common.js");
-var mm = SpecialPowers.loadChromeScript(parentScriptURL);
-
-var iframe = document.getElementById("iframe");
-iframe.onload = handleLoad;
-
-// popupNotifications (not *popup*) is a constant, per-tab container. So, we
-// only need to fetch it once.
-var popupNotifications = getPopupNotifications(window.top);
-ok(popupNotifications, "Got popupNotifications");
-
-var testNum = 1;
-
-// Load recipes for this test.
-mm.sendAsyncMessage("loadRecipes", {
-  siteRecipes: [{
-    hosts: ["example.org"],
-    usernameSelector: "#user",
-    passwordSelector: "#pass",
-  }],
+  yield testSubmittingLoginForm("subtst_notifications_10.html", function*(fieldValues) {
+    is(fieldValues.username, "null", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-change");
+    ok(notif, "got notification popup");
+    clickDoorhangerButton(notif, CHANGE_BUTTON);
+    ok(!getCaptureDoorhanger("password-change"), "popup should be gone");
+  });
+  Services.logins.removeLogin(login2);
 });
 
-mm.addMessageListener("loadedRecipes", function loadedRecipes() {
-  ok(true, "Starting test #" + testNum);
-  iframe.src = subtests[testNum-1];
-})
+add_task(function* test_checkUPSaveText() {
+  info("Check text on a user+pass notification popup");
+
+  yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "got notification popup");
+    // Check the text, which comes from the localized saveLoginText string.
+    let notificationText = notif.message;
+    let expectedText = "Would you like " + BRAND_SHORT_NAME + " to remember this login?";
+    is(expectedText, notificationText, "Checking text: " + notificationText);
+    notif.remove();
+  });
+});
+
+add_task(function* test_checkPSaveText() {
+  info("Check text on a pass-only notification popup");
+
+  yield testSubmittingLoginForm("subtst_notifications_6.html", function*(fieldValues) {
+    is(fieldValues.username, "null", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "got notification popup");
+    // Check the text, which comes from the localized saveLoginTextNoUser string.
+    let notificationText = notif.message;
+    let expectedText = "Would you like " + BRAND_SHORT_NAME + " to remember this password?";
+    is(expectedText, notificationText, "Checking text: " + notificationText);
+    notif.remove();
+  });
+});
+
+add_task(function* test_capture2pw0un() {
+  info("Check for notification popup when a form with 2 password fields (no username) " +
+       "is submitted and there are no saved logins.");
+
+  yield testSubmittingLoginForm("subtst_notifications_2pw_0un.html", function*(fieldValues) {
+    is(fieldValues.username, "null", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "got notification popup");
+    notif.remove();
+  });
+});
+
+add_task(function* test_change2pw0unExistingDifferentUP() {
+  info("Check for notification popup when a form with 2 password fields (no username) " +
+       "is submitted and there is a saved login with a username and different password.");
+
+  Services.logins.addLogin(login1B);
+
+  yield testSubmittingLoginForm("subtst_notifications_2pw_0un.html", function*(fieldValues) {
+    is(fieldValues.username, "null", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-change");
+    ok(notif, "got notification popup");
+    notif.remove();
+  });
+
+  Services.logins.removeLogin(login1B);
+});
+
+add_task(function* test_change2pw0unExistingDifferentP() {
+  info("Check for notification popup when a form with 2 password fields (no username) " +
+       "is submitted and there is a saved login with no username and different password.");
+
+  Services.logins.addLogin(login2B);
 
-SimpleTest.waitForExplicitFinish();
-</script>
-</pre>
-</body>
-</html>
+  yield testSubmittingLoginForm("subtst_notifications_2pw_0un.html", function*(fieldValues) {
+    is(fieldValues.username, "null", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-change");
+    ok(notif, "got notification popup");
+    notif.remove();
+  });
+
+  Services.logins.removeLogin(login2B);
+});
+
+add_task(function* test_change2pw0unExistingWithSameP() {
+  info("Check for no notification popup when a form with 2 password fields (no username) " +
+       "is submitted and there is a saved login with a username and the same password.");
+
+  Services.logins.addLogin(login2);
+
+  yield testSubmittingLoginForm("subtst_notifications_2pw_0un.html", function*(fieldValues) {
+    is(fieldValues.username, "null", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-change");
+    ok(!notif, "checking for no notification popup");
+  });
+
+  checkOnlyLoginWasUsedTwice({ justChanged: false });
+
+  Services.logins.removeLogin(login2);
+});
+
+add_task(function* test_recipeCaptureFields_NewLogin() {
+  info("Check that we capture the proper fields when a field recipe is in use.");
+
+  yield testSubmittingLoginForm("subtst_notifications_2pw_1un_1text.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "got notification popup");
+
+    // Sanity check, no logins should exist yet.
+    let logins = Services.logins.getAllLogins();
+    is(logins.length, 0, "Should not have any logins yet");
+
+    clickDoorhangerButton(notif, REMEMBER_BUTTON);
+  }, "http://example.org"); // The recipe is for example.org
+});
+
+add_task(function* test_recipeCaptureFields_ExistingLogin() {
+  info("Check that we capture the proper fields when a field recipe is in use " +
+       "and there is a matching login");
+
+  yield testSubmittingLoginForm("subtst_notifications_2pw_1un_1text.html", function*(fieldValues) {
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-save");
+    ok(!notif, "checking for no notification popup");
+  }, "http://example.org");
+
+  checkOnlyLoginWasUsedTwice({ justChanged: false });
+  let logins = Services.logins.getAllLogins();
+  is(logins[0].username, "notifyu1", "check .username for existing login submission");
+  is(logins[0].password, "notifyp1", "check .password for existing login submission");
+
+  Services.logins.removeAllLogins();
+});
+
+// TODO:
+// * existing login test, form has different password --> change password, no save prompt
--- a/toolkit/components/passwordmgr/test/browser/browser_context_menu.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_context_menu.js
@@ -1,16 +1,14 @@
 /*
  * Test the password manager context menu.
  */
 
 "use strict";
 
-Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
-
 // The hostname for the test URIs.
 const TEST_HOSTNAME = "https://example.com";
 const MULTIPLE_FORMS_PAGE_PATH = "/browser/toolkit/components/passwordmgr/test/browser/multiple_forms.html";
 
 /**
  * Initialize logins needed for the tests and disable autofill
  * for login forms for easier testing of manual fill.
  */
--- a/toolkit/components/passwordmgr/test/browser/browser_filldoorhanger.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_filldoorhanger.js
@@ -1,13 +1,8 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
-
 /**
  * All these tests require the experimental login fill UI to be enabled. We also
  * disable autofill for login forms for easier testing of manual fill.
  */
 add_task(function* test_initialize() {
   Services.prefs.setBoolPref("signon.ui.experimental", true);
   Services.prefs.setBoolPref("signon.autofillForms", false);
   registerCleanupFunction(function () {
--- a/toolkit/components/passwordmgr/test/browser/browser_notifications.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_notifications.js
@@ -1,13 +1,9 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
 Cu.import("resource://testing-common/ContentTaskUtils.jsm", this);
-Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
 /**
  * Test that the doorhanger notification for password saving is populated with
  * the correct values in various password capture cases.
  */
 add_task(function* test_save_change() {
   let testCases = [{
     username: "username",
     password: "password",
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/head.js
@@ -0,0 +1,118 @@
+const DIRECTORY_PATH = "/browser/toolkit/components/passwordmgr/test/browser/";
+
+Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
+
+registerCleanupFunction(function* cleanup_removeAllLoginsAndResetRecipes() {
+  Services.logins.removeAllLogins();
+  let recipeParent = LoginTestUtils.recipes.getRecipeParent();
+  if (!recipeParent) {
+    // No need to reset the recipes if the recipe module wasn't even loaded.
+    return;
+  }
+  yield recipeParent.then(recipeParent => recipeParent.reset());
+});
+
+/**
+ * Loads a test page in `DIRECTORY_URL` which automatically submits to formsubmit.sjs and returns a
+ * promise resolving with the field values when the optional `aTaskFn` is done.
+ *
+ * @param {String} aPageFile - test page file name which auto-submits to formsubmit.sjs
+ * @param {Function} aTaskFn - task which can be run before the tab closes.
+ * @param {String} [aOrigin="http://mochi.test:8888"] - origin of the server to
+ *                                                      use to load `aPageFile`.
+ */
+function testSubmittingLoginForm(aPageFile, aTaskFn, aOrigin = "http://mochi.test:8888") {
+  return BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: aOrigin + DIRECTORY_PATH + aPageFile,
+  }, function*(browser) {
+    ok(true, "loaded " + aPageFile);
+    let fieldValues = yield ContentTask.spawn(browser, undefined, function*() {
+      yield ContentTaskUtils.waitForCondition(() => {
+        return content.location.pathname.endsWith("/formsubmit.sjs") &&
+          content.document.readyState == "complete";
+      }, "Wait for form submission load (formsubmit.sjs)");
+      let username = content.document.getElementById("user").textContent;
+      let password = content.document.getElementById("pass").textContent;
+      return {
+        username,
+        password,
+      };
+    });
+    ok(true, "form submission loaded");
+    if (aTaskFn) {
+      yield* aTaskFn(fieldValues);
+    }
+    return fieldValues;
+  });
+}
+
+function checkOnlyLoginWasUsedTwice({ justChanged }) {
+  // Check to make sure we updated the timestamps and use count on the
+  // existing login that was submitted for the test.
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  ok(logins[0] instanceof Ci.nsILoginMetaInfo, "metainfo QI");
+  is(logins[0].timesUsed, 2, "check .timesUsed for existing login submission");
+  ok(logins[0].timeCreated < logins[0].timeLastUsed, "timeLastUsed bumped");
+  if (justChanged) {
+    is(logins[0].timeLastUsed, logins[0].timePasswordChanged, "timeLastUsed == timePasswordChanged");
+  } else {
+    is(logins[0].timeCreated, logins[0].timePasswordChanged, "timeChanged not updated");
+  }
+}
+
+// Begin popup notification (doorhanger) functions //
+
+const REMEMBER_BUTTON = 0;
+const NEVER_BUTTON = 1;
+
+const CHANGE_BUTTON = 0;
+const DONT_CHANGE_BUTTON = 1;
+
+/**
+ * Checks if we have a password capture popup notification
+ * of the right type and with the right label.
+ *
+ * @param {String} aKind The desired `passwordNotificationType`
+ * @return the found password popup notification.
+ */
+function getCaptureDoorhanger(aKind) {
+  ok(true, "Looking for " + aKind + " popup notification");
+  let notification = PopupNotifications.getNotification("password");
+  if (notification) {
+    is(notification.options.passwordNotificationType, aKind, "Notification type matches.");
+    if (aKind == "password-change") {
+      is(notification.mainAction.label, "Update", "Main action label matches update doorhanger.");
+    } else if (aKind == "password-save") {
+      is(notification.mainAction.label, "Remember", "Main action label matches save doorhanger.");
+    }
+  }
+  return notification;
+}
+
+/**
+ * Clicks the specified popup notification button.
+ *
+ * @param {Element} aPopup Popup Notification element
+ * @param {Number} aButtonIndex Number indicating which button to click.
+ *                              See the constants in this file.
+ */
+function clickDoorhangerButton(aPopup, aButtonIndex) {
+  ok(true, "Looking for action at index " + aButtonIndex);
+
+  let notifications = aPopup.owner.panel.childNodes;
+  ok(notifications.length > 0, "at least one notification displayed");
+  ok(true, notifications.length + " notification(s)");
+  let notification = notifications[0];
+
+  if (aButtonIndex == 0) {
+    ok(true, "Triggering main action");
+    notification.button.doCommand();
+  } else if (aButtonIndex <= aPopup.secondaryActions.length) {
+    ok(true, "Triggering secondary action " + aButtonIndex);
+    notification.childNodes[aButtonIndex].doCommand();
+  }
+}
+
+// End popup notification (doorhanger) functions //
--- a/toolkit/components/passwordmgr/test/chrome/chrome.ini
+++ b/toolkit/components/passwordmgr/test/chrome/chrome.ini
@@ -1,11 +1,10 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g' || os == 'android'
 support-files =
   ../formsubmit.sjs
   ../notification_common.js
   ../pwmgr_common.js
 
-[test_formless_autofill.html]
 [test_formless_submit.html]
 [test_privbrowsing_perwindowpb.html]
 skip-if = true # Bug 1173337
--- a/toolkit/components/passwordmgr/test/chrome/test_formless_submit.html
+++ b/toolkit/components/passwordmgr/test/chrome/test_formless_submit.html
@@ -17,17 +17,17 @@ const LMCBackstagePass = Cu.import("reso
 const { LoginManagerContent, FormLikeFactory } = LMCBackstagePass;
 
 let parentScriptURL = SimpleTest.getTestFileURL("pwmgr_common.js");
 let mm = SpecialPowers.loadChromeScript(parentScriptURL);
 
 document.addEventListener("DOMContentLoaded", () => {
   document.getElementById("loginFrame").addEventListener("load", (evt) => {
     // Tell the parent to setup test logins.
-    mm.sendAsyncMessage("setupParent");
+    mm.sendAsyncMessage("setupParent", { selfFilling: true });
   });
 });
 
 // When the setup is done, load a recipe for this test.
 mm.addMessageListener("doneSetup", function doneSetup() {
   mm.sendAsyncMessage("loadRecipes", {
     siteRecipes: [{
       hosts: ["test1.mochi.test:8888"],
--- a/toolkit/components/passwordmgr/test/mochitest.ini
+++ b/toolkit/components/passwordmgr/test/mochitest.ini
@@ -4,75 +4,35 @@ support-files =
   authenticate.sjs
   blank.html
   formsubmit.sjs
   notification_common.js
   privbrowsing_perwindowpb_iframe.html
   prompt_common.js
   pwmgr_common.js
   subtst_master_pass.html
-  subtst_notifications_1.html
-  subtst_notifications_10.html
   subtst_notifications_11.html
   subtst_notifications_11_popup.html
-  subtst_notifications_2.html
-  subtst_notifications_2pw_0un.html
-  subtst_notifications_2pw_1un_1text.html
-  subtst_notifications_3.html
-  subtst_notifications_4.html
-  subtst_notifications_5.html
-  subtst_notifications_6.html
-  subtst_notifications_7.html
-  subtst_notifications_8.html
-  subtst_notifications_9.html
   subtst_privbrowsing_1.html
   subtst_privbrowsing_2.html
   subtst_privbrowsing_3.html
   subtst_privbrowsing_4.html
   subtst_prompt_async.html
-  auth2/authenticate.sjs
 
-[test_autofill_before_load.html]
-# This test doesn't pass because we can't ensure a cross-platform event that
-# occurs between DOMContentLoaded and Pageload
-skip-if = true
-[test_basic_form.html]
-[test_basic_form_0pw.html]
-[test_basic_form_1pw.html]
-[test_basic_form_1pw_2.html]
-[test_basic_form_2pw_1.html]
 [test_basic_form_2pw_2.html]
-[test_basic_form_3pw_1.html]
 [test_basic_form_autocomplete.html]
 skip-if = toolkit == 'android'
-[test_case_differences.html]
-skip-if = toolkit == 'android'
-[test_basic_form_html5.html]
-[test_basic_form_pwevent.html]
-[test_basic_form_pwonly.html]
 [test_bug_627616.html]
 skip-if = toolkit == 'android' #TIMED_OUT
-[test_bug_776171.html]
-[test_form_action_1.html]
-[test_form_action_2.html]
-[test_form_action_javascript.html]
-[test_input_events.html]
-[test_input_events_for_identical_values.html]
 [test_master_password.html]
 skip-if = toolkit == 'android' #TIMED_OUT
 [test_master_password_cleanup.html]
 skip-if = toolkit == 'android'
-[test_maxlength.html]
-[test_notifications.html]
-skip-if = toolkit == 'android'
 [test_notifications_popup.html]
-skip-if = os == "linux" || toolkit == 'android' # bug 934057
-[test_passwords_in_type_password.html]
+skip-if = true || os == "linux" || toolkit == 'android' # bug 934057
 [test_prompt.html]
 skip-if = os == "linux" || toolkit == 'android' #TIMED_OUT
 [test_prompt_async.html]
 skip-if = toolkit == 'android' #TIMED_OUT
 [test_xhr.html]
 skip-if = toolkit == 'android' #TIMED_OUT
-[test_xhr_2.html]
 [test_xml_load.html]
 skip-if = toolkit == 'android' #TIMED_OUT
-[test_zzz_finish.html]
rename from toolkit/components/passwordmgr/test/auth2/authenticate.sjs
rename to toolkit/components/passwordmgr/test/mochitest/auth2/authenticate.sjs
--- a/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
+++ b/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
@@ -1,7 +1,33 @@
 [DEFAULT]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g'
 support-files =
+  ../../../satchel/test/parent_utils.js
+  ../../../satchel/test/satchel_common.js
+  ../authenticate.sjs
   ../pwmgr_common.js
+  auth2/authenticate.sjs
 
 [test_autofill_password-only.html]
+[test_basic_form.html]
+[test_basic_form_0pw.html]
+[test_basic_form_1pw.html]
+[test_basic_form_1pw_2.html]
+[test_basic_form_2pw_1.html]
+[test_basic_form_3pw_1.html]
+[test_basic_form_html5.html]
+[test_basic_form_pwevent.html]
+[test_basic_form_pwonly.html]
+[test_bug_776171.html]
+[test_case_differences.html]
+skip-if = toolkit == 'android' # autocomplete
+[test_form_action_1.html]
+[test_form_action_2.html]
+[test_form_action_javascript.html]
+[test_formless_autofill.html]
+skip-if = toolkit == 'android' # Bug 1259768
+[test_input_events.html]
+[test_input_events_for_identical_values.html]
+[test_maxlength.html]
+[test_passwords_in_type_password.html]
 [test_recipe_login_fields.html]
+[test_xhr_2.html]
--- a/toolkit/components/passwordmgr/test/mochitest/test_autofill_password-only.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_autofill_password-only.html
@@ -5,22 +5,22 @@
   <title>Test password-only forms should prefer a password-only login when present</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: Bug 444968
 <script>
-let pwmgrCommonScript = loadParentTestFile("pwmgr_common.js");
-pwmgrCommonScript.sendSyncMessage("setupParent");
+let pwmgrCommonScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js"));
+pwmgrCommonScript.sendSyncMessage("setupParent", { selfFilling: true });
 
 SimpleTest.waitForExplicitFinish();
 
-let chromeScript = runFunctionInParent(function chromeSetup() {
+let chromeScript = runInParent(function chromeSetup() {
   const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
   let pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
 
   let login1A  = Cc["@mozilla.org/login-manager/loginInfo;1"].
                  createInstance(Ci.nsILoginInfo);
   let login1B  = Cc["@mozilla.org/login-manager/loginInfo;1"].
                  createInstance(Ci.nsILoginInfo);
   let login2A  = Cc["@mozilla.org/login-manager/loginInfo;1"].
rename from toolkit/components/passwordmgr/test/test_basic_form.html
rename to toolkit/components/passwordmgr/test/mochitest/test_basic_form.html
--- a/toolkit/components/passwordmgr/test/test_basic_form.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form.html
@@ -6,29 +6,26 @@
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: simple form fill
 
 <script>
-commonInit();
-SimpleTest.waitForExplicitFinish();
+runChecksAfterCommonInit(startTest);
 
 /** Test for Login Manager: form fill, multiple forms. **/
 
 function startTest() {
   is($_(1, "uname").value, "testuser", "Checking for filled username");
   is($_(1, "pword").value, "testpass", "Checking for filled password");
 
   SimpleTest.finish();
 }
-
-window.addEventListener("runTests", startTest);
 </script>
 
 <p id="display"></p>
 
 <div id="content" style="display: none">
 
   <form id="form1" action="formtest.js">
     <p>This is form 1.</p>
rename from toolkit/components/passwordmgr/test/test_basic_form_0pw.html
rename to toolkit/components/passwordmgr/test/mochitest/test_basic_form_0pw.html
--- a/toolkit/components/passwordmgr/test/test_basic_form_0pw.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_0pw.html
@@ -51,26 +51,22 @@ Login Manager test: forms with no passwo
 
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: form fill, no password fields. **/
 
-commonInit();
-
 function startTest() {
   is($_(3, "uname").value, "", "Checking for unfilled checkbox (form 3)");
   is($_(4, "yyyyy").value, "", "Checking for unfilled text field (form 4)");
   is($_(5, "uname").value, "", "Checking for unfilled text field (form 5)");
 
   SimpleTest.finish();
 }
 
-window.addEventListener("runTests", startTest);
-
-SimpleTest.waitForExplicitFinish();
+runChecksAfterCommonInit(startTest);
 </script>
 </pre>
 </body>
 </html>
 
rename from toolkit/components/passwordmgr/test/test_basic_form_1pw.html
rename to toolkit/components/passwordmgr/test/mochitest/test_basic_form_1pw.html
--- a/toolkit/components/passwordmgr/test/test_basic_form_1pw.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_1pw.html
@@ -4,16 +4,19 @@
   <meta charset="utf-8">
   <title>Test autofill for forms with 1 password field</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: forms with 1 password field
+<script>
+runChecksAfterCommonInit(() => startTest());
+</script>
 <p id="display"></p>
 
 <div id="content" style="display: none">
 
 <!-- no username fields -->
 
 <form id='form1' action='formtest.js'> 1
     <!-- Blank, so fill in the password -->
@@ -134,18 +137,16 @@ Login Manager test: forms with 1 passwor
 
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: simple form fill **/
 
-commonInit();
-
 function startTest() {
     var f = 1;
 
     // 1-3
     checkForm(f++, "testpass");
     checkForm(f++, "testpass");
     checkForm(f++, "xxxxxxxx");
 
@@ -164,17 +165,12 @@ function startTest() {
     checkForm(f++, "xxxxxxxx", "testuser", "testpass");
     checkForm(f++, "testuser", "testpass", "xxxxxxxx");
 
     //15
     checkForm(f++, "testuser", "testpass");
 
     SimpleTest.finish();
 }
-
-
-window.addEventListener("runTests", startTest);
-
-SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
rename from toolkit/components/passwordmgr/test/test_basic_form_1pw_2.html
rename to toolkit/components/passwordmgr/test/mochitest/test_basic_form_1pw_2.html
--- a/toolkit/components/passwordmgr/test/test_basic_form_1pw_2.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_1pw_2.html
@@ -4,16 +4,19 @@
   <meta charset="utf-8">
   <title>Test forms with 1 password field, part 2</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: forms with 1 password field, part 2
+<script>
+runChecksAfterCommonInit(() => startTest());
+</script>
 <p id="display"></p>
 
 <div id="content" style="display: none">
 
 <form id='form1' action='formtest.js'> 1
     <input type='password' name='pname' value=''>
     <button type='submit'>Submit</button>
 </form>
@@ -78,36 +81,29 @@ Login Manager test: forms with 1 passwor
 
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: simple form fill, part 2 **/
 
-commonInit();
-
 function startTest() {
     var f;
 
     // Test various combinations of disabled/readonly inputs
     checkForm(1, "testpass"); // control
     checkUnmodifiedForm(2);
     checkUnmodifiedForm(3);
     checkForm(4, "testuser", "testpass"); // control
     for (f = 5;  f <= 8;  f++) { checkUnmodifiedForm(f); }
     // Test case-insensitive comparison of username field
     checkForm(9, "testuser", "testpass");
     checkForm(10, "TESTUSER", "testpass");
     checkForm(11, "TESTUSER", "testpass");
 
     SimpleTest.finish();
 }
-
-
-window.addEventListener("runTests", startTest);
-
-SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
 
rename from toolkit/components/passwordmgr/test/test_basic_form_2pw_1.html
rename to toolkit/components/passwordmgr/test/mochitest/test_basic_form_2pw_1.html
--- a/toolkit/components/passwordmgr/test/test_basic_form_2pw_1.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_2pw_1.html
@@ -4,17 +4,19 @@
   <meta charset="utf-8">
   <title>Test autofill for forms with 2 password fields</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: forms with 2 password fields
-
+<script>
+runChecksAfterCommonInit(() => startTest());
+</script>
 <p id="display"></p>
 
 <div id="content" style="display: none">
 
 
 <!-- no username fields -->
 
 <form id='form1' action='formtest.js'> 1
@@ -149,18 +151,16 @@ Login Manager test: forms with 2 passwor
 
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: simple form fill **/
 
-commonInit();
-
 function startTest() {
     var f = 1;
 
     // 1-6 no username
     checkForm(f++, "testpass", "");
     checkForm(f++, "testpass", "");
     checkForm(f++, "testpass", "", "");
     checkForm(f++, "testpass", "");
@@ -176,17 +176,12 @@ function startTest() {
     checkForm(f++, "testuser", "testpass", "xxxxxxxx");
     checkForm(f++, "testuser", "testpass", "");
     checkForm(f++, "",         "xxxxxxxx", "testpass");
     checkForm(f++, "testpass", "",         "");
     checkForm(f++, "xxxxxxxx", "",         "");
 
     SimpleTest.finish();
 }
-
-
-window.addEventListener("runTests", startTest);
-
-SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
rename from toolkit/components/passwordmgr/test/test_basic_form_3pw_1.html
rename to toolkit/components/passwordmgr/test/mochitest/test_basic_form_3pw_1.html
--- a/toolkit/components/passwordmgr/test/test_basic_form_3pw_1.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_3pw_1.html
@@ -4,16 +4,19 @@
   <meta charset="utf-8">
   <title>Test autofill for forms with 3 password fields</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: forms with 3 password fields (form filling)
+<script>
+runChecksAfterCommonInit(() => startTest());
+</script>
 <p id="display"></p>
 
 <div id="content" style="display: none">
   <p>The next three forms are <b>user/pass/passB/passC</b>, as all-empty, preuser(only), and preuser/pass</p>
   <form id="form1" action="formtest.js">
     <input  type="text"       name="uname">
     <input  type="password"   name="pword">
     <input  type="password"   name="qword">
@@ -107,18 +110,16 @@ Login Manager test: forms with 3 passwor
   </form>
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: form fill, 3 password fields **/
 
-commonInit();
-
 // Test to make sure 3-password forms are filled properly.
 
 function startTest() {
   // Check form 1
   is($_(1, "uname").value, "testuser", "Checking username 1");
   is($_(1, "pword").value, "testpass", "Checking password 1");
   is($_(1, "qword").value, "",         "Checking password 1 (q)");
   is($_(1, "rword").value, "",         "Checking password 1 (r)");
@@ -164,17 +165,13 @@ function startTest() {
   todo_is($_(9, "qword").value, "",         "Checking password 9 (q)");
   is($_(9, "rword").value, "",         "Checking password 9 (r)");
   is($_(9, "pword").value, "testpass", "Checking password 9");
 
   // TODO: as with the 2-password cases, add tests to check for creating new
   // logins and changing passwords.
   SimpleTest.finish();
 }
-
-window.addEventListener("runTests", startTest);
-
-SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
 
rename from toolkit/components/passwordmgr/test/test_basic_form_html5.html
rename to toolkit/components/passwordmgr/test/mochitest/test_basic_form_html5.html
--- a/toolkit/components/passwordmgr/test/test_basic_form_html5.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_html5.html
@@ -5,46 +5,46 @@
   <title>Test for html5 input types (email, tel, url, etc.)</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: html5 input types (email, tel, url, etc.)
 <script>
-commonInit();
-SimpleTest.waitForExplicitFinish();
+runChecksAfterCommonInit(() => startTest());
 
-const Ci = SpecialPowers.Ci;
-const Cc = SpecialPowers.Cc;
-pwmgr = Cc["@mozilla.org/login-manager;1"].
-        getService(Ci.nsILoginManager);
+runInParent(function setup() {
+  const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+  let pwmgr = Cc["@mozilla.org/login-manager;1"].
+              getService(Ci.nsILoginManager);
 
-login1  = Cc["@mozilla.org/login-manager/loginInfo;1"].
+  login1 = Cc["@mozilla.org/login-manager/loginInfo;1"].
            createInstance(Ci.nsILoginInfo);
-login2  = Cc["@mozilla.org/login-manager/loginInfo;1"].
+  login2 = Cc["@mozilla.org/login-manager/loginInfo;1"].
            createInstance(Ci.nsILoginInfo);
-login3  = Cc["@mozilla.org/login-manager/loginInfo;1"].
+  login3 = Cc["@mozilla.org/login-manager/loginInfo;1"].
            createInstance(Ci.nsILoginInfo);
-login4  = Cc["@mozilla.org/login-manager/loginInfo;1"].
+  login4 = Cc["@mozilla.org/login-manager/loginInfo;1"].
            createInstance(Ci.nsILoginInfo);
 
-login1.init("http://mochi.test:8888", "http://bug600551-1", null,
-            "testuser@example.com", "testpass1", "", "");
-login2.init("http://mochi.test:8888", "http://bug600551-2", null,
-            "555-555-5555", "testpass2", "", "");
-login3.init("http://mochi.test:8888", "http://bug600551-3", null,
-            "http://mozilla.org", "testpass3", "", "");
-login4.init("http://mochi.test:8888", "http://bug600551-4", null,
-            "123456789", "testpass4", "", "");
+  login1.init("http://mochi.test:8888", "http://bug600551-1", null,
+              "testuser@example.com", "testpass1", "", "");
+  login2.init("http://mochi.test:8888", "http://bug600551-2", null,
+              "555-555-5555", "testpass2", "", "");
+  login3.init("http://mochi.test:8888", "http://bug600551-3", null,
+              "http://mozilla.org", "testpass3", "", "");
+  login4.init("http://mochi.test:8888", "http://bug600551-4", null,
+              "123456789", "testpass4", "", "");
 
-pwmgr.addLogin(login1);
-pwmgr.addLogin(login2);
-pwmgr.addLogin(login3);
-pwmgr.addLogin(login4);
+  pwmgr.addLogin(login1);
+  pwmgr.addLogin(login2);
+  pwmgr.addLogin(login3);
+  pwmgr.addLogin(login4);
+});
 </script>
 
 <p id="display"></p>
 <div id="content" style="display: none">
 
   <form id="form1" action="http://bug600551-1">
     <input  type="email"    name="uname">
     <input  type="password" name="pword">
@@ -151,21 +151,14 @@ function startTest() {
   is($_(10, "uname").value, "", "type=time should not be considered a username");
 
   is($_(11, "uname").value, "", "type=datetime-local should not be considered a username");
 
   is($_(12, "uname").value, "50", "type=range should not be considered a username");
 
   is($_(13, "uname").value, "#000000", "type=color should not be considered a username");
 
-  pwmgr.removeLogin(login1);
-  pwmgr.removeLogin(login2);
-  pwmgr.removeLogin(login3);
-  pwmgr.removeLogin(login4);
-
   SimpleTest.finish();
 }
-
-window.addEventListener("runTests", startTest);
 </script>
 </pre>
 </body>
 </html>
rename from toolkit/components/passwordmgr/test/test_basic_form_pwevent.html
rename to toolkit/components/passwordmgr/test/mochitest/test_basic_form_pwevent.html
--- a/toolkit/components/passwordmgr/test/test_basic_form_pwevent.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_pwevent.html
@@ -7,50 +7,45 @@ https://bugzilla.mozilla.org/show_bug.cg
   <meta charset="utf-8"/>
   <title>Test for Bug 355063</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <script type="application/javascript">
   /** Test for Bug 355063 **/
 
-  function startTest() {
+  runChecksAfterCommonInit(function startTest() {
     info("startTest");
     // Password Manager's own listener should always have been added first, so
     // the test's listener should be called after the pwmgr's listener fills in
     // a login.
     //
     SpecialPowers.addChromeEventListener("DOMFormHasPassword", function eventFired() {
       SpecialPowers.removeChromeEventListener("DOMFormHasPassword", eventFired);
       var passField = $("p1");
       passField.addEventListener("input", checkForm);
     });
     addForm();
-  }
+  });
 
   function addForm() {
     info("addForm");
     var c = document.getElementById("content");
     c.innerHTML = "<form id=form1>form1: <input id=u1><input type=password id=p1></form><br>";
   }
 
   function checkForm() {
     info("checkForm");
     var userField = document.getElementById("u1");
     var passField = document.getElementById("p1");
     is(userField.value, "testuser", "checking filled username");
     is(passField.value, "testpass", "checking filled password");
 
     SimpleTest.finish();
   }
-
-  commonInit();
-
-  window.addEventListener("runTests", startTest);
-  SimpleTest.waitForExplicitFinish();
 </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=355063">Mozilla Bug 355063</a>
 <p id="display"></p>
 <div id="content">
 forms go here!
 </div>
rename from toolkit/components/passwordmgr/test/test_basic_form_pwonly.html
rename to toolkit/components/passwordmgr/test/mochitest/test_basic_form_pwonly.html
--- a/toolkit/components/passwordmgr/test/test_basic_form_pwonly.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_pwonly.html
@@ -5,47 +5,43 @@
   <title>Test forms and logins without a username</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: forms and logins without a username.
 <script>
-commonInit();
-SimpleTest.waitForExplicitFinish();
-
-var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"]
-                         .getService(SpecialPowers.Ci.nsILoginManager);
+runChecksAfterCommonInit(() => startTest());
+runInParent(() => {
+  const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+  var pwmgr = Cc["@mozilla.org/login-manager;1"]
+              .getService(Ci.nsILoginManager);
 
-var nsLoginInfo = new SpecialPowers.wrap(SpecialPowers.Components).Constructor("@mozilla.org/login-manager/loginInfo;1", SpecialPowers.Ci.nsILoginInfo);
-ok(nsLoginInfo != null, "nsLoginInfo constructor");
+  var nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo);
 
-// pwlogin1 uses a unique formSubmitURL, to check forms where no other logins
-// will apply. pwlogin2 uses the normal formSubmitURL, so that we can test
-// forms with a mix of username and non-username logins that might apply.
-//
-// Note: pwlogin2 is deleted at the end of the test.
+  // pwlogin1 uses a unique formSubmitURL, to check forms where no other logins
+  // will apply. pwlogin2 uses the normal formSubmitURL, so that we can test
+  // forms with a mix of username and non-username logins that might apply.
+  //
+  // Note: pwlogin2 is deleted at the end of the test.
 
-pwlogin1 = new nsLoginInfo();
-pwlogin2 = new nsLoginInfo();
+  pwlogin1 = new nsLoginInfo();
+  pwlogin2 = new nsLoginInfo();
 
-pwlogin1.init("http://mochi.test:8888", "http://mochi.test:1111", null,
-    "", "1234", "uname", "pword");
+  pwlogin1.init("http://mochi.test:8888", "http://mochi.test:1111", null,
+                "", "1234", "uname", "pword");
 
-pwlogin2.init("http://mochi.test:8888", "http://mochi.test:8888", null,
-    "", "1234", "uname", "pword");
+  pwlogin2.init("http://mochi.test:8888", "http://mochi.test:8888", null,
+                "", "1234", "uname", "pword");
+
 
-try {
-    pwmgr.addLogin(pwlogin1);
-    pwmgr.addLogin(pwlogin2);
-} catch (e) {
-    ok(false, "addLogin threw: " + e);
-}
-
+  pwmgr.addLogin(pwlogin1);
+  pwmgr.addLogin(pwlogin2);
+});
 </script>
 <p id="display"></p>
 
 <div id="content" style="display: none">
 
 
 <!-- simple form: no username field, 1 password field -->
 <form id='form1' action='http://mochi.test:1111/formtest.js'> 1
@@ -203,18 +199,15 @@ function startTest() {
 
     checkForm(9, "", "1234");
     checkForm(10, "", "1234");
     checkForm(11, "", "1234");
 
     checkUnmodifiedForm(12);
     checkUnmodifiedForm(13);
 
-    pwmgr.removeLogin(pwlogin2);
     SimpleTest.finish();
 }
-
-window.addEventListener("runTests", startTest);
 </script>
 </pre>
 </body>
 </html>
 
rename from toolkit/components/passwordmgr/test/test_bug_776171.html
rename to toolkit/components/passwordmgr/test/mochitest/test_bug_776171.html
--- a/toolkit/components/passwordmgr/test/test_bug_776171.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_bug_776171.html
@@ -2,74 +2,55 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=776171
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 776171 related to HTTP auth</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body onload="startTest()">
 <script class="testbody" type="text/javascript">
 
 /**
  * This test checks we correctly ignore authentication entry
  * for a subpath and use creds from the URL when provided when XHR
  * is used with filled user name and password.
  *
- * 1. connect auth2/authenticate.sjs that excepts user1:pass1 password
+ * 1. connect auth2/authenticate.sjs that expects user1:pass1 password
  * 2. connect a dummy URL at the same path
  * 3. connect authenticate.sjs that again expects user1:pass1 password
  *    in this case, however, we have an entry without an identity
  *    for this path (that is a parent for auth2 path in the first step)
  */
 
 SimpleTest.waitForExplicitFinish();
 
-function clearAuthCache()
-{
-  var authMgr = SpecialPowers.Cc['@mozilla.org/network/http-auth-manager;1']
-                             .getService(SpecialPowers.Ci.nsIHttpAuthManager);
-  authMgr.clearAll();
-}
-
-function doxhr(URL, user, pass, next)
-{
+function doxhr(URL, user, pass, next) {
   var xhr = new XMLHttpRequest();
   if (user && pass)
     xhr.open("POST", URL, true, user, pass);
   else
     xhr.open("POST", URL, true);
-  xhr.onload = function()
-  {
+  xhr.onload = function() {
     is(xhr.status, 200, "Got status 200");
     next();
   }
-  xhr.onerror = function()
-  {
+  xhr.onerror = function() {
     ok(false, "request passed");
     finishTest();
   }
   xhr.send();
 }
 
-function startTest()
-{
-  clearAuthCache();
+function startTest() {
   doxhr("auth2/authenticate.sjs?user=user1&pass=pass1&realm=realm1", "user1", "pass1", function() {
     doxhr("auth2", null, null, function() {
-      doxhr("authenticate.sjs?user=user1&pass=pass1&realm=realm1", "user1", "pass1", finishTest);
+      doxhr("authenticate.sjs?user=user1&pass=pass1&realm=realm1", "user1", "pass1", SimpleTest.finish);
     });
   });
 }
-
-function finishTest()
-{
-  clearAuthCache();
-  SimpleTest.finish();
-}
-
 </script>
 </body>
 </html>
-
rename from toolkit/components/passwordmgr/test/test_case_differences.html
rename to toolkit/components/passwordmgr/test/mochitest/test_case_differences.html
--- a/toolkit/components/passwordmgr/test/test_case_differences.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_case_differences.html
@@ -1,57 +1,51 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Test autocomplete due to multiple matching logins</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="satchel_common.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: autocomplete due to multiple matching logins
 
 <script>
-commonInit();
-SimpleTest.waitForExplicitFinish();
+runChecksAfterCommonInit(false);
+
+SpecialPowers.loadChromeScript(function addLogins() {
+  const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+  Cu.import("resource://gre/modules/Services.jsm");
 
-// Get the pwmgr service
-var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"]
-                         .getService(SpecialPowers.Ci.nsILoginManager);
-ok(pwmgr != null, "nsLoginManager service");
+  // Create some logins just for this form, since we'll be deleting them.
+  var nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
+                                           Ci.nsILoginInfo, "init");
 
-// Create some logins just for this form, since we'll be deleting them.
-var nsLoginInfo =
-SpecialPowers.wrap(SpecialPowers.Components).Constructor("@mozilla.org/login-manager/loginInfo;1",
-                          SpecialPowers.Ci.nsILoginInfo, "init");
-ok(nsLoginInfo != null, "nsLoginInfo constructor");
-
+  var login0 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
+                               "name", "pass", "uname", "pword");
 
-var login0 = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete:8888", null,
-    "name", "pass", "uname", "pword");
+  var login1 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
+                               "Name", "Pass", "uname", "pword");
 
-var login1 = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete:8888", null,
-    "Name", "Pass", "uname", "pword");
+  var login2 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
+                               "USER", "PASS", "uname", "pword");
 
-var login2 = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete:8888", null,
-    "USER", "PASS", "uname", "pword");
-
-try {
-    pwmgr.addLogin(login0);
-    pwmgr.addLogin(login1);
-    pwmgr.addLogin(login2);
-} catch (e) {
-    ok(false, "addLogin threw: " + e);
-}
-
+  try {
+    Services.logins.addLogin(login0);
+    Services.logins.addLogin(login1);
+    Services.logins.addLogin(login2);
+  } catch (e) {
+    assert.ok(false, "addLogin threw: " + e);
+  }
+});
 </script>
 <p id="display"></p>
 
 <!-- we presumably can't hide the content for this test. -->
 <div id="content">
 
   <!-- form1 tests multiple matching logins -->
   <form id="form1" action="http://autocomplete:8888/formtest.js" onsubmit="return false;">
@@ -60,157 +54,93 @@ try {
     <button type="submit">Submit</button>
   </form>
 
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-/** Test for Login Manager: multiple login autocomplete. **/
-
-var tester;
+/** Test for Login Manager: autocomplete due to multiple matching logins **/
 
 var uname = $_(1, "uname");
 var pword = $_(1, "pword");
 
 // Restore the form to the default state.
 function restoreForm() {
     uname.value = "";
     pword.value = "";
     uname.focus();
 }
 
-
 // Check for expected username/password in form.
 function checkACForm(expectedUsername, expectedPassword) {
   var formID = uname.parentNode.id;
   is(uname.value, expectedUsername, "Checking " + formID + " username");
   is(pword.value, expectedPassword, "Checking " + formID + " password");
 }
 
-
-function sendFakeAutocompleteEvent(element) {
-    var acEvent = document.createEvent("HTMLEvents");
-    acEvent.initEvent("DOMAutoComplete", true, false);
-    element.dispatchEvent(acEvent);
-}
-
-function addPopupListener(eventName, func, capture) {
-  autocompletePopup.addEventListener(eventName, func, capture);
-}
-
-function removePopupListener(eventName, func, capture) {
-  autocompletePopup.removeEventListener(eventName, func, capture);
-}
-
-/*
- * Main section of test...
- *
- * This is a bit hacky, because the events are either being sent or
- * processes asynchronously, so we need to interrupt our flow with lots of
- * setTimeout() calls. The case statements are executed in order, one per
- * timeout.
- */
-function* runTest() {
-  function runNextTest() {
-    addPopupListener("popupshown", function() {
-      removePopupListener("popupshown", arguments.callee, false);
-
-      window.setTimeout(tester.next.bind(tester), 0);
-    }, false);
-  }
-
-  function waitForCompletion() {
-    var observer = SpecialPowers.wrapCallback(function(subject, topic, data) {
-      SpecialPowers.removeObserver(observer, "passwordmgr-processed-form");
-      tester.next();
-    });
-    SpecialPowers.addObserver(observer, "passwordmgr-processed-form", false);
-  }
-
+add_task(function* test_empty_first_entry() {
   /* test 1 */
   // Make sure initial form is empty.
   checkACForm("", "");
   // Trigger autocomplete popup
   restoreForm();
+  let popupState = yield getPopupState();
+  is(popupState.open, false, "Check popup is initially closed");
+  is(popupState.selectedIndex, -1, "Check no entries are selected");
+  let shownPromise = promiseACShown();
   doKey("down");
-  yield runNextTest();
+  let results = yield shownPromise;
+  checkArrayValues(results, ["name", "Name", "USER"], "initial");
 
-  /* test 2 */
   // Check first entry
+  let index0Promise = notifySelectedIndex(0);
   doKey("down");
+  yield index0Promise;
   checkACForm("", ""); // value shouldn't update
   doKey("return"); // not "enter"!
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("name", "pass");
-
-  // Trigger autocomplete popup
-  restoreForm();
-  doKey("down");
-  yield runNextTest();
-
-  /* test 3 */
-  // Check second entry
-  doKey("down");
-  doKey("down");
-  doKey("return"); // not "enter"!
-  yield waitForCompletion();
-  checkACForm("Name", "Pass");
+});
 
-  // Trigger autocomplete popup
+add_task(function* test_empty_second_entry() {
   restoreForm();
-  doKey("down");
-  yield runNextTest();
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
+  doKey("down"); // first
+  doKey("down"); // second
+  doKey("return"); // not "enter"!
+  yield promiseFormsProcessed();
+  checkACForm("Name", "Pass");
+});
 
-  /* test 4 */
-  // Check third entry
-  doKey("down");
-  doKey("down");
-  doKey("down");
+add_task(function* test_empty_third_entry() {
+  restoreForm();
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
+  doKey("down"); // first
+  doKey("down"); // second
+  doKey("down"); // third
   doKey("return");
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("USER", "PASS");
+});
 
-  // Trigger autocomplete popup
+add_task(function* test_preserve_matching_username_case() {
   restoreForm();
   uname.value = "user";
-  doKey("down");
-  yield runNextTest();
-
-  /* test 5 */
-  // Check that we don't clobber user-entered text when tabbing away
-  doKey("tab");
-  yield waitForCompletion();
-  checkACForm("user", "PASS");
-
-  // Trigger autocomplete popup
-  restoreForm();
-  SimpleTest.finish();
-}
-
-
-var autocompletePopup;
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-function startTest() {
-    var Ci = SpecialPowers.Ci;
-    chromeWin = SpecialPowers.wrap(window)
-                    .QueryInterface(Ci.nsIInterfaceRequestor)
-                    .getInterface(Ci.nsIWebNavigation)
-                    .QueryInterface(Ci.nsIDocShellTreeItem)
-                    .rootTreeItem
-                    .QueryInterface(Ci.nsIInterfaceRequestor)
-                    .getInterface(Ci.nsIDOMWindow)
-                    .QueryInterface(Ci.nsIDOMChromeWindow);
-    // shouldn't reach into browser internals like this and
-    // shouldn't assume ID is consistent across products
-    autocompletePopup = chromeWin.document.getElementById("PopupAutoComplete");
-    ok(autocompletePopup, "Got autocomplete popup");
-    tester = runTest();
-    tester.next();
-}
-
-window.addEventListener("runTests", startTest);
+  // Check that we don't clobber user-entered text when tabbing away
+  // (even with no autocomplete entry selected)
+  doKey("tab");
+  yield promiseFormsProcessed();
+  checkACForm("user", "PASS");
+});
 </script>
 </pre>
 </body>
 </html>
-
rename from toolkit/components/passwordmgr/test/test_form_action_1.html
rename to toolkit/components/passwordmgr/test/mochitest/test_form_action_1.html
--- a/toolkit/components/passwordmgr/test/test_form_action_1.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_form_action_1.html
@@ -4,16 +4,19 @@
   <meta charset="utf-8">
   <title>Test for considering form action</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: Bug 360493
+<script>
+runChecksAfterCommonInit(() => startTest());
+</script>
 <p id="display"></p>
 <div id="content" style="display: none">
 
   <!-- normal form with normal relative action. -->
   <form id="form1" action="formtest.js">
     <input  type="text"       name="uname">
     <input  type="password"   name="pword">
 
@@ -109,33 +112,26 @@ Login Manager test: Bug 360493
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: 360493 (Cross-Site Forms + Password
     Manager = Security Failure) **/
 
 // This test is designed to make sure variations on the form's |action|
 // and |method| continue to work with the fix for 360493.
 
-commonInit();
-
 function startTest() {
   for (var i = 1; i <= 9; i++) {
     // Check form i
     is($_(i, "uname").value, "testuser", "Checking for filled username " + i);
     is($_(i, "pword").value, "testpass", "Checking for filled password " + i);
   }
 
   // The login's formSubmitURL isn't "javascript:", so don't fill it in.
   isnot($_(10, "uname"), "testuser", "Checking username w/ JS action URL");
   isnot($_(10, "pword"), "testpass", "Checking password w/ JS action URL");
 
   SimpleTest.finish();
 }
-
-window.addEventListener("runTests", startTest);
-
-SimpleTest.waitForExplicitFinish();
-
 </script>
 </pre>
 </body>
 </html>
 
rename from toolkit/components/passwordmgr/test/test_form_action_2.html
rename to toolkit/components/passwordmgr/test/mochitest/test_form_action_2.html
--- a/toolkit/components/passwordmgr/test/test_form_action_2.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_form_action_2.html
@@ -1,19 +1,22 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-  <meta charset"utf-8">
+  <meta charset="utf-8">
   <title>Test for considering form action</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: Bug 360493
+<script>
+runChecksAfterCommonInit(() => startTest());
+</script>
 <p id="display"></p>
 <div id="content" style="display: none">
 
   <!-- The tests in this page exercise things that shouldn't work. -->
 
   <!-- Change port # of action URL from 8888 to 7777 -->
   <form id="form1" action="http://localhost:7777/tests/toolkit/components/passwordmgr/test/formtest.js">
     <input  type="text"       name="uname">
@@ -126,18 +129,16 @@ Login Manager test: Bug 360493
 <!-- TODO: foo.site.com vs. bar.site.com? -->
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: 360493 (Cross-Site Forms + Password Manager = Security Failure) **/
 
-commonInit();
-
 function startTest() {
   for (var i = 1; i <= 8; i++) {
     // Check form i
     is($_(i, "uname").value, "", "Checking for unfilled username " + i);
     is($_(i, "pword").value, "", "Checking for unfilled password " + i);
   }
 
   is($_(9, "uname").value, "testuser", "Checking for unmodified username 9");
@@ -161,18 +162,13 @@ function startTest() {
   // If the test finds extra forms the submit() causes the test to timeout, then
   // there may be a security issue.
   is(document.forms.length,  11,  "Checking for unexpected forms");
   $("neutered_submit10").click();
   $("neutered_submit11").click();
 
   SimpleTest.finish();
 }
-
-window.addEventListener("runTests", startTest);
-
-SimpleTest.waitForExplicitFinish();
-
 </script>
 </pre>
 </body>
 </html>
 
rename from toolkit/components/passwordmgr/test/test_form_action_javascript.html
rename to toolkit/components/passwordmgr/test/mochitest/test_form_action_javascript.html
--- a/toolkit/components/passwordmgr/test/test_form_action_javascript.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_form_action_javascript.html
@@ -5,40 +5,35 @@
   <title>Test forms with a JS submit action</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: form with JS submit action
 <script>
-SimpleTest.waitForExplicitFinish();
+runChecksAfterCommonInit(() => startTest());
 
-// Note: Call this first so it doesn't override our login.
-commonInit();
+runInParent(function setup() {
+  const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+  Cu.import("resource://gre/modules/Services.jsm");
 
-var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"]
-                         .getService(SpecialPowers.Ci.nsILoginManager);
-var jslogin = SpecialPowers.Cc["@mozilla.org/login-manager/loginInfo;1"]
-                           .createInstance(SpecialPowers.Ci.nsILoginInfo);
-jslogin.init("http://mochi.test:8888", "javascript:", null,
+  let jslogin = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
+  jslogin.init("http://mochi.test:8888", "javascript:", null,
               "jsuser", "jspass123", "uname", "pword");
-pwmgr.addLogin(jslogin);
+  Services.logins.addLogin(jslogin);
+});
 
 /** Test for Login Manager: JS action URL **/
 
 function startTest() {
     checkForm(1, "jsuser", "jspass123");
 
-    pwmgr.removeLogin(jslogin);
     SimpleTest.finish();
 }
-
-// XXX
-window.addEventListener("runTests", startTest);
 </script>
 
 <p id="display"></p>
 
 <div id="content" style="display: none">
 
 
 <form id='form1' action='javascript:alert("never shows")'> 1
rename from toolkit/components/passwordmgr/test/chrome/test_formless_autofill.html
rename to toolkit/components/passwordmgr/test/mochitest/test_formless_autofill.html
--- a/toolkit/components/passwordmgr/test/chrome/test_formless_autofill.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_formless_autofill.html
@@ -1,48 +1,48 @@
 <!DOCTYPE html>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Test autofilling of fields outside of a form</title>
-  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
   <script src="pwmgr_common.js"></script>
-  <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <script type="application/javascript;version=1.8">
-SimpleTest.waitForExplicitFinish();
-
-const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
-Cu.import("resource://gre/modules/Task.jsm");
-const LMCBackstagePass = Cu.import("resource://gre/modules/LoginManagerContent.jsm");
-const { LoginManagerContent, FormLikeFactory } = LMCBackstagePass;
-
-let parentScriptURL = SimpleTest.getTestFileURL("pwmgr_common.js");
-let mm = SpecialPowers.loadChromeScript(parentScriptURL);
+let mm = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js"));
 
 document.addEventListener("DOMContentLoaded", () => {
   document.getElementById("loginFrame").addEventListener("load", (evt) => {
     // Tell the parent to setup test logins.
-    mm.sendAsyncMessage("setupParent");
+    mm.sendAsyncMessage("setupParent", { selfFilling: true });
   });
 });
 
 // When the setup is done, load a recipe for this test.
 mm.addMessageListener("doneSetup", function doneSetup() {
   mm.sendAsyncMessage("loadRecipes", {
     siteRecipes: [{
       hosts: ["mochi.test:8888"],
       usernameSelector: "input[name='recipeuname']",
       passwordSelector: "input[name='recipepword']",
     }],
   });
 });
 
-mm.addMessageListener("loadedRecipes", () => runTest());
+let loadedRecipesPromise = new Promise(resolve => {
+  mm.addMessageListener("loadedRecipes", resolve);
+});
+
+add_task(function* setup() {
+  yield loadedRecipesPromise;
+});
+
 
 const DEFAULT_ORIGIN = "http://mochi.test:8888";
 const TESTCASES = [
   {
     // Inputs
     document: `<input type=password>`,
 
     // Expected outputs
@@ -104,17 +104,17 @@ const TESTCASES = [
         <input>
         <input type=password>
       </form>`,
     expectedFormCount: 2,
     expectedInputValues: ["testuser", "", "", "", "testpass", "", ""],
   },
 ];
 
-let runTest = Task.async(function*() {
+add_task(function* test() {
   let loginFrame = document.getElementById("loginFrame");
   let frameDoc = loginFrame.contentWindow.document;
 
   for (let tc of TESTCASES) {
     info("Starting testcase: " + JSON.stringify(tc));
 
     let numFormLikesExpected = tc.expectedFormCount || 1;
 
@@ -127,18 +127,16 @@ let runTest = Task.async(function*() {
     let testInputs = frameDoc.documentElement.querySelectorAll("input");
     is(testInputs.length, tc.expectedInputValues.length, "Check number of inputs");
     for (let i = 0; i < tc.expectedInputValues.length; i++) {
       let expectedValue = tc.expectedInputValues[i];
       is(testInputs[i].value, expectedValue,
          "Check expected input value " + i + ": " + expectedValue);
     }
   }
-
-  SimpleTest.finish();
 });
 
 </script>
 
 <p id="display"></p>
 
 <div id="content">
   <iframe id="loginFrame" src="http://mochi.test:8888/tests/toolkit/components/passwordmgr/test/blank.html"></iframe>
rename from toolkit/components/passwordmgr/test/test_input_events.html
rename to toolkit/components/passwordmgr/test/mochitest/test_input_events.html
--- a/toolkit/components/passwordmgr/test/test_input_events.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_input_events.html
@@ -6,18 +6,18 @@
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body onload="onNewEvent(event)">
 Login Manager test: input events should fire.
 
 <script>
-commonInit();
-SimpleTest.waitForExplicitFinish();
+runChecksAfterCommonInit();
+
 SimpleTest.requestFlakyTimeout("untriaged");
 
 /** Test for Login Manager: form fill, should get input events. **/
 
 var usernameInputFired = false;
 var passwordInputFired = false;
 var usernameChangeFired = false;
 var passwordChangeFired = false;
rename from toolkit/components/passwordmgr/test/test_input_events_for_identical_values.html
rename to toolkit/components/passwordmgr/test/mochitest/test_input_events_for_identical_values.html
--- a/toolkit/components/passwordmgr/test/test_input_events_for_identical_values.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_input_events_for_identical_values.html
@@ -7,41 +7,34 @@
   <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body onload="onNewEvent(event)">
 Login Manager test: input events should fire.
 
 <script>
-commonInit();
-SimpleTest.waitForExplicitFinish();
+runChecksAfterCommonInit();
+
 SimpleTest.requestFlakyTimeout("untriaged");
 
 /** Test for Login Manager: form fill when form is already filled, should not get input events. **/
 
 var onloadFired = false;
 
 function onNewEvent(e) {
   console.error("Got " + e.type + " event.");
   if (e.type == "load") {
     onloadFired = true;
     $_(1, "uname").focus();
     sendKey("Tab");
   } else {
     ok(false, "Got an input event for " + e.target.name + " field, which shouldn't happen.");
   }
 }
-
-SimpleTest.registerCleanupFunction(function cleanup() {
-  $_(1, "uname").removeAttribute("oninput");
-  $_(1, "pword").removeAttribute("onfocus");
-  $_(1, "pword").removeAttribute("oninput");
-  document.body.removeAttribute("onload");
-});
 </script>
 
 <p id="display"></p>
 
 <div id="content">
 
   <form id="form1" action="formtest.js">
     <p>This is form 1.</p>
rename from toolkit/components/passwordmgr/test/test_maxlength.html
rename to toolkit/components/passwordmgr/test/mochitest/test_maxlength.html
--- a/toolkit/components/passwordmgr/test/test_maxlength.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_maxlength.html
@@ -4,16 +4,19 @@
   <meta charset="utf-8">
   <title>Test for maxlength attributes</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: Bug 391514
+<script>
+runChecksAfterCommonInit(() => startTest());
+</script>
 <p id="display"></p>
 <div id="content" style="display: none">
   <!-- normal form. -->
   <form id="form1" action="formtest.js">
     <input  type="text"     name="uname">
     <input  type="password" name="pword">
 
     <button type="submit">Submit</button>
@@ -101,18 +104,16 @@ Login Manager test: Bug 391514
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /* Test for Login Manager: 391514 (Login Manager gets confused with
  * password/PIN on usaa.com)
  */
 
-commonInit();
-
 function startTest() {
   var i;
 
   is($_(1, "uname").value, "testuser", "Checking for filled username 1");
   is($_(1, "pword").value, "testpass", "Checking for filled password 1");
 
   for (i = 2; i < 8; i++) {
     is($_(i, "uname").value, "", "Checking for unfilled username " + i);
@@ -125,17 +126,12 @@ function startTest() {
   }
 
   // Note that tests 11-13 are limited to exactly the expected value.
   // Assert this lest someone change the login we're testing with.
   is($_(11, "uname").value.length, 8, "asserting test assumption is valid.");
 
   SimpleTest.finish();
 }
-
-window.addEventListener("runTests", startTest);
-
-SimpleTest.waitForExplicitFinish();
-
 </script>
 </pre>
 </body>
 </html>
rename from toolkit/components/passwordmgr/test/test_passwords_in_type_password.html
rename to toolkit/components/passwordmgr/test/mochitest/test_passwords_in_type_password.html
--- a/toolkit/components/passwordmgr/test/test_passwords_in_type_password.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_passwords_in_type_password.html
@@ -4,16 +4,19 @@
   <meta charset="utf-8">
   <title>Test that passwords only get filled in type=password</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: Bug 242956
+<script>
+runChecksAfterCommonInit(() => startTest());
+</script>
 <p id="display"></p>
 <div id="content" style="display: none">
   <!-- pword is not a type=password input -->
   <form id="form1" action="formtest.js">
     <input  type="text" name="uname">
     <input  type="text" name="pword">
 
     <button type="submit">Submit</button>
@@ -78,17 +81,16 @@ Login Manager test: Bug 242956
 
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: 242956 (Stored password is inserted into a
     readable text input on a second page) **/
-commonInit();
 
 // Make sure that pwmgr only puts passwords into type=password <input>s.
 // Might as well test the converse, too (username in password field).
 
 function startTest() {
   var form, input;
 
   is($_(1, "uname").value, "", "Checking for unfilled username 1");
@@ -109,21 +111,14 @@ function startTest() {
   is($_(5, "pword").value, "testpass", "Checking for filled password 5");
 
   is($_(6, "uname").value, "", "Checking for unfilled username 6");
   is($_(6, "pword").value, "", "Checking for unfilled password 6");
 
   is($_(7, "uname").value, "testuser", "Checking for unmodified username 7");
   is($_(7, "pword").value, "",         "Checking for unfilled password 7");
 
-
-
   SimpleTest.finish();
 }
-
-window.addEventListener("runTests", startTest);
-
-SimpleTest.waitForExplicitFinish();
-
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/components/passwordmgr/test/mochitest/test_recipe_login_fields.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_recipe_login_fields.html
@@ -9,17 +9,17 @@
 <body>
 <script type="application/javascript;version=1.8">
 SimpleTest.waitForExplicitFinish();
 
 const PWMGR_COMMON_URL = SimpleTest.getTestFileURL("pwmgr_common.js");
 let pwmgrCommonScript = SpecialPowers.loadChromeScript(PWMGR_COMMON_URL);
 
 // Tell the parent to setup test logins.
-pwmgrCommonScript.sendAsyncMessage("setupParent");
+pwmgrCommonScript.sendAsyncMessage("setupParent", { selfFilling: true });
 
 // When the setup is done, load a recipe for this test.
 pwmgrCommonScript.addMessageListener("doneSetup", function doneSetup() {
   pwmgrCommonScript.sendAsyncMessage("loadRecipes", {
     siteRecipes: [{
       hosts: ["mochi.test:8888"],
       usernameSelector: "input[name='uname1']",
       passwordSelector: "input[name='pword2']",
rename from toolkit/components/passwordmgr/test/test_xhr_2.html
rename to toolkit/components/passwordmgr/test/mochitest/test_xhr_2.html
--- a/toolkit/components/passwordmgr/test/test_xhr_2.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_xhr_2.html
@@ -2,70 +2,54 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=654348
 -->
 <head>
   <meta charset="utf-8">
   <title>Test XHR auth with user and pass arguments</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body onload="startTest()">
 <script class="testbody" type="text/javascript">
 
 /**
  * This test checks we correctly ignore authentication entry
  * for a subpath and use creds from the URL when provided when XHR
  * is used with filled user name and password.
  *
  * 1. connect authenticate.sjs that excepts user1:pass1 password
  * 2. connect authenticate.sjs that this time expects differentuser2:pass2 password
  *    we must use the creds that are provided to the xhr witch are different and expected
  */
 
-SimpleTest.waitForExplicitFinish();
-
-function clearAuthCache()
-{
-  var authMgr = SpecialPowers.Cc['@mozilla.org/network/http-auth-manager;1']
-                             .getService(SpecialPowers.Ci.nsIHttpAuthManager);
-  authMgr.clearAll();
-}
-
-function doxhr(URL, user, pass, code, next)
-{
+function doxhr(URL, user, pass, code, next) {
   var xhr = new XMLHttpRequest();
   if (user && pass)
     xhr.open("POST", URL, true, user, pass);
   else
     xhr.open("POST", URL, true);
-  xhr.onload = function()
-  {
+  xhr.onload = function() {
     is(xhr.status, code, "expected response code " + code);
     next();
   }
-  xhr.onerror = function()
-  {
+  xhr.onerror = function() {
     ok(false, "request passed");
     finishTest();
   }
   xhr.send();
 }
 
-function startTest()
-{
-  clearAuthCache();
+function startTest() {
   doxhr("authenticate.sjs?user=dummy&pass=pass1&realm=realm1&formauth=1", "dummy", "dummy", 403, function() {
     doxhr("authenticate.sjs?user=dummy&pass=pass1&realm=realm1&formauth=1", "dummy", "pass1", 200, finishTest);
   });
 }
 
-function finishTest()
-{
-  clearAuthCache();
+function finishTest() {
   SimpleTest.finish();
 }
 
 </script>
 </body>
 </html>
-
--- a/toolkit/components/passwordmgr/test/notification_common.js
+++ b/toolkit/components/passwordmgr/test/notification_common.js
@@ -32,16 +32,17 @@ function getPopupNotifications(aWindow) 
     return popupNotifications;
 }
 
 
 /**
  * Checks if we have a password popup notification
  * of the right type and with the right label.
  *
+ * @deprecated Write a browser-chrome test instead and use the fork of this method there.
  * @returns the found password popup notification.
  */
 function getPopup(aPopupNote, aKind) {
     ok(true, "Looking for " + aKind + " popup notification");
     var notification = aPopupNote.getNotification("password");
     if (notification) {
       is(notification.options.passwordNotificationType, aKind, "Notification type matches.");
       if (aKind == "password-change") {
@@ -49,18 +50,18 @@ function getPopup(aPopupNote, aKind) {
       } else if (aKind == "password-save") {
         is(notification.mainAction.label, "Remember", "Main action label matches save doorhanger.");
       }
     }
     return notification;
 }
 
 
-/*
- * clickPopupButton
+/**
+ * @deprecated - Use a browser chrome test instead.
  *
  * Clicks the specified popup notification button.
  */
 function clickPopupButton(aPopup, aButtonIndex) {
     ok(true, "Looking for action at index " + aButtonIndex);
 
     var notifications = SpecialPowers.wrap(aPopup.owner).panel.childNodes;
     ok(notifications.length > 0, "at least one notification displayed");
--- a/toolkit/components/passwordmgr/test/pwmgr_common.js
+++ b/toolkit/components/passwordmgr/test/pwmgr_common.js
@@ -125,26 +125,22 @@ function doKey(aKey, modifier) {
  * the test can start checking filled-in values. Tests that check observer
  * notifications might be confused by this.
  */
 function commonInit(selfFilling) {
   var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"].
               getService(SpecialPowers.Ci.nsILoginManager);
   ok(pwmgr != null, "Access LoginManager");
 
-
   // Check that initial state has no logins
   var logins = pwmgr.getAllLogins();
-  if (logins.length) {
-    //todo(false, "Warning: wasn't expecting logins to be present.");
-    pwmgr.removeAllLogins();
-  }
+  is(logins.length, 0, "Not expecting logins to be present");
   var disabledHosts = pwmgr.getAllDisabledHosts();
   if (disabledHosts.length) {
-    //todo(false, "Warning: wasn't expecting disabled hosts to be present.");
+    ok(false, "Warning: wasn't expecting disabled hosts to be present.");
     for (var host of disabledHosts)
       pwmgr.setLoginSavingEnabled(host, true);
   }
 
   // Add a login that's used in multiple tests
   var login = SpecialPowers.Cc["@mozilla.org/login-manager/loginInfo;1"].
               createInstance(SpecialPowers.Ci.nsILoginInfo);
   login.init("http://mochi.test:8888", "http://mochi.test:8888", null,
@@ -155,17 +151,21 @@ function commonInit(selfFilling) {
   logins = pwmgr.getAllLogins();
   is(logins.length, 1, "Checking for successful init login");
   disabledHosts = pwmgr.getAllDisabledHosts();
   is(disabledHosts.length, 0, "Checking for no disabled hosts");
 
   if (selfFilling)
     return;
 
-  registerRunTests();
+  if (this.sendAsyncMessage) {
+    sendAsyncMessage("registerRunTests");
+  } else {
+    registerRunTests();
+  }
 }
 
 function registerRunTests() {
   // We provide a general mechanism for our tests to know when they can
   // safely run: we add a final form that we know will be filled in, wait
   // for the login manager to tell us that it's filled in and then continue
   // with the rest of the tests.
   window.addEventListener("DOMContentLoaded", (event) => {
@@ -277,51 +277,58 @@ function promiseFormsProcessed(expectedC
         SpecialPowers.removeObserver(onProcessedForm, "passwordmgr-processed-form");
         resolve(subject, data);
       }
     }
     SpecialPowers.addObserver(onProcessedForm, "passwordmgr-processed-form", false);
   });
 }
 
-function loadParentTestFile(aRelativeFilePath) {
-  let fileURL = SimpleTest.getTestFileURL(aRelativeFilePath);
-  let testScript = SpecialPowers.loadChromeScript(fileURL);
-  SimpleTest.registerCleanupFunction(function destroyChromeScript() {
-    testScript.destroy();
-  });
-  return testScript;
-}
-
 /**
  * Run a function synchronously in the parent process and destroy it in the test cleanup function.
- * @param {Function} aFunction - function that will be stringified and run.
+ * @param {Function|String} aFunctionOrURL - either a function that will be stringified and run
+ *                                           or the URL to a JS file.
  * @return {Object} - the return value of loadChromeScript providing message-related methods.
  *                    @see loadChromeScript in specialpowersAPI.js
  */
-function runFunctionInParent(aFunction) {
-  let chromeScript = SpecialPowers.loadChromeScript(aFunction);
+function runInParent(aFunctionOrURL) {
+  let chromeScript = SpecialPowers.loadChromeScript(aFunctionOrURL);
   SimpleTest.registerCleanupFunction(() => {
     chromeScript.destroy();
   });
   return chromeScript;
 }
 
+/**
+ * Run commonInit synchronously in the parent then run the test function after the runTests event.
+ *
+ * @param {Function} aFunction The test function to run
+ */
+function runChecksAfterCommonInit(aFunction = null) {
+  SimpleTest.waitForExplicitFinish();
+  let pwmgrCommonScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js"));
+  if (aFunction) {
+    window.addEventListener("runTests", aFunction);
+    pwmgrCommonScript.addMessageListener("registerRunTests", () => registerRunTests());
+  }
+  pwmgrCommonScript.sendSyncMessage("setupParent");
+}
+
 // Code to run when loaded as a chrome script in tests via loadChromeScript
 if (this.addMessageListener) {
   const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
   var SpecialPowers = { Cc, Ci, Cr, Cu, };
   var ok, is;
   // Ignore ok/is in commonInit since they aren't defined in a chrome script.
   ok = is = () => {}; // eslint-disable-line no-native-reassign
 
   Cu.import("resource://gre/modules/Task.jsm");
 
-  addMessageListener("setupParent", () => {
-    commonInit(true);
+  addMessageListener("setupParent", ({selfFilling = false} = {selfFilling: false}) => {
+    commonInit(selfFilling);
     sendAsyncMessage("doneSetup");
   });
 
   addMessageListener("loadRecipes", Task.async(function* loadRecipes(recipes) {
     var { LoginManagerParent } = Cu.import("resource://gre/modules/LoginManagerParent.jsm", {});
     var recipeParent = yield LoginManagerParent.recipeParentPromise;
     yield recipeParent.load(recipes);
     sendAsyncMessage("loadedRecipes", recipes);
@@ -329,16 +336,29 @@ if (this.addMessageListener) {
 
   var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
   globalMM.addMessageListener("RemoteLogins:onFormSubmit", function onFormSubmit(message) {
     sendAsyncMessage("formSubmissionProcessed", message.data, message.objects);
   });
 } else {
   // Code to only run in the mochitest pages (not in the chrome script).
   SimpleTest.registerCleanupFunction(() => {
-    let recipeParent = getRecipeParent();
-    if (!recipeParent) {
-      // No need to reset the recipes if the module wasn't even loaded.
-      return;
-    }
-    recipeParent.then(recipeParent => recipeParent.reset());
+    runInParent(function cleanupParent() {
+      const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+      Cu.import("resource://gre/modules/Services.jsm");
+      Cu.import("resource://gre/modules/LoginManagerParent.jsm");
+
+      // Remove all logins and disabled hosts
+      Services.logins.removeAllLogins();
+
+      let disabledHosts = Services.logins.getAllDisabledHosts();
+      disabledHosts.forEach(host => Services.logins.setLoginSavingEnabled(host, true));
+
+      let authMgr = Cc["@mozilla.org/network/http-auth-manager;1"].
+                    getService(Ci.nsIHttpAuthManager);
+      authMgr.clearAll();
+
+      if (LoginManagerParent._recipeManager) {
+        LoginManagerParent._recipeManager.reset();
+      }
+    });
   });
 }
deleted file mode 100644
--- a/toolkit/components/passwordmgr/test/subtst_notifications_7.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>Subtest for Login Manager notifications</title>
-</head>
-<body>
-<h2>Subtest 7</h2>
-<form id="form" action="formsubmit.sjs">
-  <input id="user" name="user">
-  <input id="pass" name="pass" type="password">
-  <button type='submit'>Submit</button>
-</form>
-
-<script>
-function submitForm() {
-  userField.value = "nowisthetimeforallgoodmentocometotheaidoftheircountry";
-  passField.value = "notifyp1";
-  form.submit();
-}
-
-window.onload = submitForm;
-var form      = document.getElementById("form");
-var userField = document.getElementById("user");
-var passField = document.getElementById("pass");
-
-</script>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/components/passwordmgr/test/test_autofill_before_load.html
+++ /dev/null
@@ -1,96 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>Test for filling before the load event</title>
-  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="pwmgr_common.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body onload="endTest();">
-Login Manager test: Bug 221634
-<p id="display"></p>
-<div id="content" style="display: none">
-
-  <form id="form1" action="formtest.js">
-    <input  type="text"       name="uname">
-    <input  type="password"   name="pword">
-
-    <button type="submit">Submit</button>
-    <button type="reset"> Reset</button>
-  </form>
-
-  <img onload="performTest();" src="mlogosm.gif">
-</div>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-/** Test for Login Manager: 221634 (password manager needs to fill in forms before the page finishes loading) **/
-commonInit();
-
-var dclHappened      = false;
-var testHappened     = false;
-var pageloadHappened = false;
-
-// We're still loading the page, so make sure nothing has filled in yet.
-is($_(1, "uname").value, "", "Checking unfilled username 1");
-is($_(1, "pword").value, "", "Checking unfilled password 1");
-
-document.addEventListener("DOMContentLoaded", contentLoaded, false);
-
-SimpleTest.waitForExplicitFinish();
-
-
-// Step 1 - Fires at DOMContentLoaded
-function contentLoaded() {
-  ok(!testHappened,     "Sanity check to ensure test hasn't happened yet.")
-  ok(!pageloadHappened, "Sanity check to ensure pageload hasn't happened yet.")
-
-  // We're in DOMContentLoaded, so the pwmgr may or may not have filled in yet.
-
-  // Set a 0-second timeout, which should execute before the pageload event.
-  // setTimeout(reallyDoTest, 0);
-  // ha-ha... That doesn't work. The pageload event comes first, although
-  // it can be hacked into working by adding <img src="404_non_existent_file.gif">
-
-  dclHappened = true;
-}
-
-// Step 2 - Fires when the image loads, which should be immediately after DOMContentLoaded (but before pageload)
-function performTest() {
-  ok(dclHappened,       "Sanity check to make sure DOMContentLoaded already happened");
-  ok(!pageloadHappened, "Sanity check to ensure pageload hasn't happened yet.")
-
-  // Check form1
-  is($_(1, "uname").value, "testuser", "Checking filled username");
-  is($_(1, "pword").value, "testpass", "Checking filled password");
-
-  testHappened = true;
-}
-
-// Step 3 - Fired by |body| onload.
-function endTest() {
-  ok(dclHappened, "Sanity check to make sure DOMContentLoaded already happened");
-  ok(testHappened, "Sanity check to make sure our test ran before pageload");
-
-  // Check form1
-  is($_(1, "uname").value, "testuser", "Rechecking filled username");
-  is($_(1, "pword").value, "testpass", "Rechecking filled password");
-
-  pageloadHappened = true;
-
-  // Make sure the expected number of tests (for this page) have run.
-  // If the event execution gets out of order, only a subset get counted.
-  // (Although there should still be other failures... Belt-n-suspenders!)
-  is(SimpleTest._tests.length, 12, "expected number of executed tests");
-
-  SimpleTest.finish();
-}
-
-</script>
-
-</pre>
-</body>
-</html>
-
--- a/toolkit/components/passwordmgr/test/test_xml_load.html
+++ b/toolkit/components/passwordmgr/test/test_xml_load.html
@@ -35,24 +35,16 @@ function initLogins() {
                "xmluser1", "xmlpass1", "", "");
   login2.init("http://mochi.test:8888", null, "xml2",
                "xmluser2", "xmlpass2", "", "");
 
   pwmgr.addLogin(login1);
   pwmgr.addLogin(login2);
 }
 
-function finishTest() {
-  ok(true, "finishTest removing testing logins...");
-  pwmgr.removeLogin(login1);
-  pwmgr.removeLogin(login2);
-
-  SimpleTest.finish();
-}
-
 function handleDialog(doc, testNum) {
   ok(true, "handleDialog running for test " + testNum);
 
   var clickOK = true;
   var userfield = doc.getElementById("loginTextbox");
   var passfield = doc.getElementById("password1Textbox");
   var username = userfield.getAttribute("value");
   var password = passfield.getAttribute("value");
@@ -160,17 +152,17 @@ function doTest() {
         // and making sure the prompt re-focuses the original tab when shown:
         newWin = window.open();
         newWin.focus();
         startCallbackTimer();
         makeRequest("authenticate.sjs?user=xmluser2&pass=xmlpass2&realm=xml2");
         break;
 
     default:
-        finishTest();
+        SimpleTest.finish();
   }
 }
 
 function makeRequest(uri) {
   var xmlDoc = document.implementation.createDocument("", "test", null);
 
   function documentLoaded(e) {
       xmlLoad(xmlDoc);
deleted file mode 100644
--- a/toolkit/components/passwordmgr/test/test_zzz_finish.html
+++ /dev/null
@@ -1,53 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>Test finalization for Login Manager</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="pwmgr_common.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-Login Manager test: finalization.
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-/** Test for Login Manager: finalization **/
-
-
-// Get the pwmgr service
-var Cc_pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"];
-ok(Cc_pwmgr != null, "Access Cc[@mozilla.org/login-manager;1]");
-
-var Ci_pwmgr = SpecialPowers.Ci.nsILoginManager;
-ok(Ci_pwmgr != null, "Access Ci.nsILoginManager");
-
-var pwmgr = Cc_pwmgr.getService(Ci_pwmgr);
-ok(pwmgr != null, "pwmgr getService()");
-
-
-// Remove all logins, so future test runs start off clean.
-pwmgr.removeAllLogins();
-
-var logins = pwmgr.getAllLogins();
-ok(logins != null, "getAllLogins()");
-is(logins.length, 0, "ensure no remaining logins");
-
-// Remove any disabled hosts
-var disabledHosts = pwmgr.getAllDisabledHosts();
-ok(disabledHosts != null, "getAllDisabledHosts()");
-disabledHosts.forEach(host => pwmgr.setLoginSavingEnabled(host, true));
-
-disabledHosts = pwmgr.getAllDisabledHosts();
-ok(disabledHosts != null, "getAllDisabledHosts()");
-is(disabledHosts.length, 0, "ensure no remaining disabled hosts");
-
-</script>
-</pre>
-</body>
-</html>
-
rename from toolkit/components/places/tests/mochitest/bug_461710/iframe.html
rename to toolkit/components/places/tests/browser/461710_iframe.html
rename from toolkit/components/places/tests/mochitest/bug_461710/link_page-2.html
rename to toolkit/components/places/tests/browser/461710_link_page-2.html
--- a/toolkit/components/places/tests/mochitest/bug_461710/link_page-2.html
+++ b/toolkit/components/places/tests/browser/461710_link_page-2.html
@@ -3,11 +3,11 @@
   <head>
     <title>Link page 2</title>
     <style type="text/css">
       a:link { color: #0000ff; }
       a:visited { color: #ff0000; }
     </style>
   </head>
   <body>
-    <p><a href="visited_page.html" id="link">Link to the second visited page</a></p>
+    <p><a href="461710_visited_page.html" id="link">Link to the second visited page</a></p>
   </body>
 </html>
\ No newline at end of file
rename from toolkit/components/places/tests/mochitest/bug_461710/link_page-3.html
rename to toolkit/components/places/tests/browser/461710_link_page-3.html
--- a/toolkit/components/places/tests/mochitest/bug_461710/link_page-3.html
+++ b/toolkit/components/places/tests/browser/461710_link_page-3.html
@@ -3,11 +3,11 @@
   <head>
     <title>Link page 3</title>
     <style type="text/css">
       a:link { color: #0000ff; }
       a:visited { color: #ff0000; }
     </style>
   </head>
   <body>
-    <p><a href="visited_page.html" id="link">Link to the third visited page</a></p>
+    <p><a href="461710_visited_page.html" id="link">Link to the third visited page</a></p>
   </body>
 </html>
\ No newline at end of file
rename from toolkit/components/places/tests/mochitest/bug_461710/link_page.html
rename to toolkit/components/places/tests/browser/461710_link_page.html
--- a/toolkit/components/places/tests/mochitest/bug_461710/link_page.html
+++ b/toolkit/components/places/tests/browser/461710_link_page.html
@@ -3,11 +3,11 @@
   <head>
     <title>Link page</title>
     <style type="text/css">
       a:link { color: #0000ff; }
       a:visited { color: #ff0000; }
     </style>
   </head>
   <body>
-    <p><a href="visited_page.html" id="link">Link to the visited page</a></p>
+    <p><a href="461710_visited_page.html" id="link">Link to the visited page</a></p>
   </body>
 </html>
\ No newline at end of file
rename from toolkit/components/places/tests/mochitest/bug_461710/visited_page.html
rename to toolkit/components/places/tests/browser/461710_visited_page.html
--- a/toolkit/components/places/tests/browser/browser.ini
+++ b/toolkit/components/places/tests/browser/browser.ini
@@ -3,23 +3,25 @@ support-files =
   colorAnalyzer/category-discover.png
   colorAnalyzer/dictionaryGeneric-16.png
   colorAnalyzer/extensionGeneric-16.png
   colorAnalyzer/localeGeneric.png
   head.js
 
 [browser_bug248970.js]
 [browser_bug399606.js]
+[browser_bug461710.js]
 [browser_bug646422.js]
 [browser_bug680727.js]
 skip-if = buildapp == 'mulet' # Bug ?????? - test times out on try on all platforms, but works locally for markh!
 [browser_colorAnalyzer.js]
 [browser_double_redirect.js]
 [browser_favicon_privatebrowsing_perwindowpb.js]
 [browser_favicon_setAndFetchFaviconForPage.js]
 [browser_favicon_setAndFetchFaviconForPage_failures.js]
+[browser_history_post.js]
 [browser_notfound.js]
 [browser_redirect.js]
 [browser_settitle.js]
 [browser_visited_notfound.js]
 [browser_visituri.js]
 [browser_visituri_nohistory.js]
-[browser_visituri_privatebrowsing_perwindowpb.js]
+[browser_visituri_privatebrowsing_perwindowpb.js]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_bug461710.js
@@ -0,0 +1,82 @@
+const kRed = "rgb(255, 0, 0)";
+const kBlue = "rgb(0, 0, 255)";
+
+const prefix = "http://example.com/tests/toolkit/components/places/tests/browser/461710_";
+
+add_task(function* () {
+  let contentPage = prefix + "iframe.html";
+  let normalWindow = yield BrowserTestUtils.openNewBrowserWindow();
+
+  let browser = normalWindow.gBrowser.selectedBrowser;
+  BrowserTestUtils.loadURI(browser, contentPage);
+  yield BrowserTestUtils.browserLoaded(browser, contentPage);
+
+  let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+
+  browser = privateWindow.gBrowser.selectedBrowser;
+  BrowserTestUtils.loadURI(browser, contentPage);
+  yield BrowserTestUtils.browserLoaded(browser, contentPage);
+
+  let tests = [{
+    win: normalWindow,
+    topic: "uri-visit-saved",
+    subtest: "visited_page.html"
+  }, {
+    win: normalWindow,
+    topic: "visited-status-resolution",
+    subtest: "link_page.html",
+    color: kRed,
+    message: "Visited link coloring should work outside of private mode"
+  }, {
+    win: privateWindow,
+    topic: "visited-status-resolution",
+    subtest: "link_page-2.html",
+    color: kBlue,
+    message: "Visited link coloring should not work inside of private mode"
+  }, {
+    win: normalWindow,
+    topic: "visited-status-resolution",
+    subtest: "link_page-3.html",
+    color: kRed,
+    message: "Visited link coloring should work outside of private mode"
+  }];
+
+  let visited_page_url = prefix + tests[0].subtest;
+  for (let test of tests) {
+    let promise = new Promise(resolve => {
+      let uri = NetUtil.newURI(visited_page_url);
+      Services.obs.addObserver(function observe(aSubject) {
+        if (uri.equals(aSubject.QueryInterface(Ci.nsIURI))) {
+          Services.obs.removeObserver(observe, test.topic);
+          resolve();
+        }
+      }, test.topic, false);
+    });
+    ContentTask.spawn(test.win.gBrowser.selectedBrowser, prefix + test.subtest, function* (aSrc) {
+      content.document.getElementById("iframe").src = aSrc;
+    });
+    yield promise;
+
+    if (test.color) {
+      // In e10s waiting for visited-status-resolution is not enough to ensure links
+      // have been updated, because it only tells us that messages to update links
+      // have been dispatched. We must still wait for the actual links to update.
+      yield BrowserTestUtils.waitForCondition(function* () {
+        let color = yield ContentTask.spawn(test.win.gBrowser.selectedBrowser, null, function* () {
+          let iframe = content.document.getElementById("iframe");
+          let elem = iframe.contentDocument.getElementById("link");
+          return content.QueryInterface(Ci.nsIInterfaceRequestor)
+                        .getInterface(Ci.nsIDOMWindowUtils)
+                        .getVisitedDependentComputedStyle(elem, "", "color");
+        });
+        return (color == test.color);
+      }, test.message);
+      // The harness will consider the test as failed overall if there were no
+      // passes or failures, so record it as a pass.
+      ok(true, test.message);
+    }
+  }
+
+  yield BrowserTestUtils.closeWindow(normalWindow);
+  yield BrowserTestUtils.closeWindow(privateWindow);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_history_post.js
@@ -0,0 +1,28 @@
+const PAGE_URI = "http://example.com/tests/toolkit/components/places/tests/browser/history_post.html";
+const SJS_URI = NetUtil.newURI("http://example.com/tests/toolkit/components/places/tests/browser/history_post.sjs");
+
+add_task(function* () {
+  yield BrowserTestUtils.withNewTab({gBrowser, url: PAGE_URI}, Task.async(function* (aBrowser) {
+    yield ContentTask.spawn(aBrowser, null, function* () {
+      let doc = content.document;
+      let submit = doc.getElementById("submit");
+      let iframe = doc.getElementById("post_iframe");
+      let p = new Promise((resolve, reject) => {
+        iframe.addEventListener("load", function onLoad() {
+          iframe.removeEventListener("load", onLoad);
+          resolve();
+        });
+      });
+      submit.click();
+      yield p;
+    });
+    let visited = yield promiseIsURIVisited(SJS_URI);
+    ok(!visited, "The POST page should not be added to history");
+    let db = yield PlacesUtils.promiseDBConnection();
+    let rows = yield db.execute(
+      "SELECT 1 FROM moz_places WHERE url = :page_url",
+      {page_url: SJS_URI.spec});
+    is(rows.length, 0, "The page should not be in the database");
+    yield db.close();
+  }));
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/browser/history_post.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <title>Test post pages are not added to history</title>
+  </head>
+  <body>
+    <iframe name="post_iframe" id="post_iframe"></iframe>
+    <form method="post" action="http://example.com/tests/toolkit/components/places/tests/browser/history_post.sjs" target="post_iframe">
+      <input type="submit" id="submit"/>
+    </form>
+  </body>
+</html>
rename from toolkit/components/places/tests/chrome/history_post.sjs
rename to toolkit/components/places/tests/browser/history_post.sjs
deleted file mode 100644
--- a/toolkit/components/places/tests/bug94514-postpage.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=94514
-Specifically, this is a test page that actually submits a form.
--->
-<head>
-  <title>Test Page for Bug 94515</title>
-</head>
-<body>
-<form id="testForm" method="POST">
-  <input type="submit" id="send"/>
-</form>
-
-<script class="testbody" type="text/javascript">
-
-if (!window.location.href.match("posted=1")) {
-  // Here we just submit the form
-  var form = document.getElementById("testForm");
-  form.action = window.location.href + "?posted=1";
-  form.submit();
-} else {
-  window.location.href = "http://mochi.test:8888/tests/toolkit/components/places/tests/bug94514-postpage.html";
-}
-
-</script>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/components/places/tests/chrome.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[test_bug_94514.html]
-[test_bug_461710_perwindowpb.html]
--- a/toolkit/components/places/tests/chrome/chrome.ini
+++ b/toolkit/components/places/tests/chrome/chrome.ini
@@ -1,13 +1,11 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g'
-support-files = history_post.sjs
 
 [test_303567.xul]
 [test_341972a.xul]
 [test_341972b.xul]
 [test_342484.xul]
 [test_371798.xul]
 [test_381357.xul]
 [test_favicon_annotations.xul]
-[test_history_post.xul]
 [test_reloadLivemarks.xul]
deleted file mode 100644
--- a/toolkit/components/places/tests/chrome/test_history_post.xul
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0"?>
-
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
-   - You can obtain one at http://mozilla.org/MPL/2.0/.  -->
-
-<window title="Test post pages are not added to history"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        onload="test();">
-
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"/>
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
-
-  <script type="application/javascript">
-  <![CDATA[
-
-    const Cc = Components.classes;
-    const Ci = Components.interfaces;
-
-    Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
-    Components.utils.import("resource://gre/modules/NetUtil.jsm");
-
-    const SJS_URI = NetUtil.newURI("http://mochi.test:8888/tests/toolkit/components/places/tests/chrome/history_post.sjs");
-
-    function test()
-    {
-      SimpleTest.waitForExplicitFinish();
-      let submit = document.getElementById("submit");
-      submit.click();
-      let iframe = document.getElementById("post_iframe");
-      iframe.addEventListener("load", function onLoad() {
-        iframe.removeEventListener("load", onLoad);
-        let history = Cc["@mozilla.org/browser/history;1"]
-                        .getService(Ci.mozIAsyncHistory);
-        history.isURIVisited(SJS_URI, function (aURI, aIsVisited) {
-          ok(!aIsVisited, "The POST page should not be added to history");
-
-          let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
-                                  .DBConnection;
-          let stmt = db.createStatement(
-            "SELECT 1 FROM moz_places WHERE url = :page_url"
-          );
-          stmt.params.page_url = SJS_URI.spec;
-          ok(!stmt.executeStep(), "The page should not be in the database");
-          stmt.finalize();
-          SimpleTest.finish();
-        });
-      });
-    }
-
-  ]]>
-  </script>
-
-  <body xmlns="http://www.w3.org/1999/xhtml">
-    <iframe name="post_iframe" id="post_iframe"/>
-    <form method="post" action="http://mochi.test:8888/tests/toolkit/components/places/tests/chrome/history_post.sjs" target="post_iframe">
-      <input type="submit" id="submit"/>
-    </form>
-    <p id="display"></p>
-    <div id="content" style="display:none;"></div>
-    <pre id="test"></pre>
-  </body>
-</window>
deleted file mode 100644
--- a/toolkit/components/places/tests/mochitest/bug_461710/.eslintrc
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "extends": [
-    "../../../../../../testing/mochitest/mochitest.eslintrc"
-  ]
-}
--- a/toolkit/components/places/tests/moz.build
+++ b/toolkit/components/places/tests/moz.build
@@ -21,36 +21,42 @@ XPCSHELL_TESTS_MANIFESTS += [
     'network/xpcshell.ini',
     'queries/xpcshell.ini',
     'unifiedcomplete/xpcshell.ini',
     'unit/xpcshell.ini',
 ]
 
 BROWSER_CHROME_MANIFESTS += ['browser/browser.ini']
 MOCHITEST_CHROME_MANIFESTS += [
-    'chrome.ini',
     'chrome/chrome.ini',
 ]
 
 TEST_HARNESS_FILES.xpcshell.toolkit.components.places.tests += [
     'head_common.js',
 ]
 
 TEST_HARNESS_FILES.testing.mochitest.tests.toolkit.components.places.tests.browser += [
     'browser/399606-history.go-0.html',
     'browser/399606-httprefresh.html',
     'browser/399606-location.reload.html',
     'browser/399606-location.replace.html',
     'browser/399606-window.location.href.html',
     'browser/399606-window.location.html',
+    'browser/461710_iframe.html',
+    'browser/461710_link_page-2.html',
+    'browser/461710_link_page-3.html',
+    'browser/461710_link_page.html',
+    'browser/461710_visited_page.html',
     'browser/begin.html',
     'browser/favicon-normal16.png',
     'browser/favicon-normal32.png',
     'browser/favicon.html',
     'browser/final.html',
+    'browser/history_post.html',
+    'browser/history_post.sjs',
     'browser/redirect-target.html',
     'browser/redirect.sjs',
     'browser/redirect_once.sjs',
     'browser/redirect_twice.sjs',
     'browser/title1.html',
     'browser/title2.html',
 ]
 
@@ -58,20 +64,8 @@ TEST_HARNESS_FILES.testing.mochitest.tes
     'chrome/bad_links.atom',
     'chrome/link-less-items-no-site-uri.rss',
     'chrome/link-less-items.rss',
     'chrome/rss_as_html.rss',
     'chrome/rss_as_html.rss^headers^',
     'chrome/sample_feed.atom',
 ]
 
-TEST_HARNESS_FILES.testing.mochitest.tests.toolkit.components.places.tests += [
-    'bug94514-postpage.html',
-]
-
-TEST_HARNESS_FILES.testing.mochitest.tests.toolkit.components.places.tests.mochitest.bug_461710 += [
-    'mochitest/bug_461710/iframe.html',
-    'mochitest/bug_461710/link_page-2.html',
-    'mochitest/bug_461710/link_page-3.html',
-    'mochitest/bug_461710/link_page.html',
-    'mochitest/bug_461710/visited_page.html',
-]
-
deleted file mode 100644
--- a/toolkit/components/places/tests/test_bug_461710_perwindowpb.html
+++ /dev/null
@@ -1,237 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=461710
--->
-<head>
-  <title>Test for Bug 461710</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=461710">Mozilla Bug 461710</a>
-<p id="display"></p>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-/** Test for Bug 461710 **/
-
-SimpleTest.waitForExplicitFinish();
-
-const Ci = SpecialPowers.Ci;
-const Cc = SpecialPowers.Cc;
-const Cr = SpecialPowers.Cr;
-
-SpecialPowers.Cu.import("resource://gre/modules/NetUtil.jsm", window);
-var Services = SpecialPowers.Services;
-
-var gIframe;
-
-/**
- * Helper function which waits until another function returns true, and
- * then notifies a callback.
- *
- * Original function stolen from docshell/test/chrome/docshell_helpers.js.
- *
- * Parameters:
- *
- *    fn: a function which is evaluated repeatedly, and when it turns true,
- *        the onWaitComplete callback is notified.
- *
- *    onWaitComplete:  a callback which will be notified when fn() returns
- *        true.
- */
-function waitForTrue(fn, onWaitComplete) {
-  var start = new Date().valueOf();
-
-  // Loop until the test function returns true, or until a timeout occurs,
-  // if a timeout is defined.
-  var intervalid =
-    setInterval(
-      function() {
-        if (fn.call()) {
-          // Stop calling the test function and notify the callback.
-          clearInterval(intervalid);
-          onWaitComplete.call();
-        }
-      }, 20);
-}
-
-const kRed = "rgb(255, 0, 0)";
-const kBlue = "rgb(0, 0, 255)";
-
-var testpath = "/tests/toolkit/components/places/tests/mochitest/bug_461710/";
-var prefix = "http://mochi.test:8888" + testpath;
-var subtests = [
-                   "visited_page.html",   // 1
-                   "link_page.html",      // 2
-                   "link_page-2.html",    // 3
-                   "link_page-3.html"     // 4
-               ];
-
-var testNum = 0;
-function loadNextTest() {
-  // run the initialization code for each test
-  switch (++testNum) {
-    case 1:
-      gIframe = normalWindowIframe;
-      break;
-
-    case 2:
-      break;
-
-    case 3:
-      gIframe = privateWindowIframe;
-      break;
-
-    case 4:
-      gIframe = normalWindowIframe;
-      break;
-
-    default:
-      ok(false, "Unexpected call to loadNextTest for test #" + testNum);
-  }
-
-  if (testNum == 1)
-    observer.expectURL(prefix + subtests[0], "uri-visit-saved");
-  else
-    observer.expectURL(prefix + subtests[0]);
-
-  waitForTrue(() => observer.resolved, function() {
-    // And the nodes get notified after the "link-visited" topic, so
-    // we need to execute soon...
-    SimpleTest.executeSoon(handleLoad);
-  });
-
-  gIframe.src = prefix + subtests[testNum-1];
-}
-
-function getColor(doc, win, id) {
-  var elem = doc.getElementById(id);
-  var utils = SpecialPowers.getDOMWindowUtils(win);
-  return utils.getVisitedDependentComputedStyle(elem, "", "color");
-}
-
-function checkTest() {
-  switch (testNum) {
-    case 1:
-      // nothing to do here, we just want to mark the page as visited
-      break;
-
-    case 2:
-      // run outside of private mode, link should appear as visited
-      var doc = gIframe.contentDocument;
-      var win = doc.defaultView;
-      is(getColor(doc, win, "link"), kRed, "Visited link coloring should work outside of private mode");
-      break;
-
-    case 3:
-      // run inside of private mode, link should appear as not visited
-      doc = gIframe.contentDocument;
-      win = doc.defaultView;
-      is(getColor(doc, win, "link"), kBlue, "Visited link coloring should not work inside of private mode");
-      break;
-
-    case 4:
-      // run outside of private mode, link should appear as visited
-      doc = gIframe.contentDocument;
-      win = doc.defaultView;
-      is(getColor(doc, win, "link"), kRed, "Visited link coloring should work outside of private mode");
-      break;
-
-    default:
-      ok(false, "Unexpected call to checkTest for test #" + testNum);
-  }
-}
-
-function handleLoad() {
-  checkTest();
-
-  if (testNum < subtests.length) {
-    loadNextTest();
-  } else {
-    normalWindow.close();
-    privateWindow.close();
-
-    SimpleTest.finish();
-  }
-}
-
-var contentPage = "http://mochi.test:8888/tests/toolkit/components/places/tests/mochitest/bug_461710/iframe.html";
-
-function whenDelayedStartupFinished(aWindow, aCallback) {
-  Services.obs.addObserver(function observer(aSubject, aTopic) {
-    if (aWindow == aSubject) {
-      Services.obs.removeObserver(observer, aTopic);
-    }
-
-    if (aWindow.content == null || aWindow.content.location.href != contentPage) {
-      aWindow.addEventListener("DOMContentLoaded", function onInnerLoad() {
-        aWindow.removeEventListener("DOMContentLoaded", onInnerLoad, true);
-        SimpleTest.executeSoon(function() { aCallback(aWindow); });
-      }, true);
-
-      aWindow.gBrowser.loadURI(contentPage);
-    }
-  }, "browser-delayed-startup-finished", false);
-}
-
-function testOnWindow(aIsPrivate, callback) {
-  var mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                         .getInterface(Ci.nsIWebNavigation)
-                         .QueryInterface(Ci.nsIDocShellTreeItem)
-                         .rootTreeItem
-                         .QueryInterface(Ci.nsIInterfaceRequestor)
-                         .getInterface(Ci.nsIDOMWindow);
-  var win = mainWindow.OpenBrowserWindow({private: aIsPrivate});
-  whenDelayedStartupFinished(win, function() { callback(win); });
-}
-
-const URI_VISITED_RESOLUTION_TOPIC = "visited-status-resolution";
-var observer = {
-  uri: null,
-  resolved: true,
-  observe: function (aSubject, aTopic, aData) {
-
-    if (this.uri.equals(SpecialPowers.wrap(aSubject).QueryInterface(Ci.nsIURI))) {
-      this.resolved = true;
-
-      Services.obs.removeObserver(this, aTopic);
-    }
-  },
-  expectURL: function (url, aOverrideTopic) {
-    ok(this.resolved, "Can't set the expected URL when another is yet to be resolved");
-    this.resolved = false;
-
-    this.uri = SpecialPowers.wrap(NetUtil).newURI(url);
-    var topic = aOverrideTopic || URI_VISITED_RESOLUTION_TOPIC;
-    Services.obs.addObserver(this, topic, false);
-  }
-};
-
-var normalWindow;
-var privateWindow;
-
-var normalWindowIframe;
-var privateWindowIframe;
-
-testOnWindow(false, function(aWin) {
-  var selectedBrowser = aWin.gBrowser.selectedBrowser;
-
-   normalWindow = aWin;
-   normalWindowIframe = selectedBrowser.contentDocument.getElementById("iframe");
-
-  testOnWindow(true, function(aPrivateWin) {
-    selectedBrowser = aPrivateWin.gBrowser.selectedBrowser;
-
-    privateWindow = aPrivateWin;
-    privateWindowIframe = selectedBrowser.contentDocument.getElementById("iframe");
-
-    loadNextTest();
-  });
-});
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/components/places/tests/test_bug_94514.html
+++ /dev/null
@@ -1,79 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=94514
-Specifically, this tests that a page that is obtained via a post request does
-not get added to global history.
--->
-<head>
-  <title>Test for Bug 94515</title>
-  <script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/SimpleTest.js"></script>
-
-  <link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css" />
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=94514">Mozilla Bug 94514</a>
-
-<div id="content" style="display: none">
-  
-</div>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-SimpleTest.waitForExplicitFinish();
-
-var startURI = "http://mochi.test:8888/tests/toolkit/components/places/tests/bug94514-postpage.html";
-var postedURI = startURI + "?posted=1";
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-
-Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
-
-var ios = Cc["@mozilla.org/network/io-service;1"].
-          getService(Ci.nsIIOService);
-var startPage = ios.newURI(startURI, null, null);
-var postedPage = ios.newURI(postedURI, null, null);
-var w = null;
-
-// Because adding visits is async, we will not be notified imemdiately.
-var os = Cc["@mozilla.org/observer-service;1"].
-         getService(Ci.nsIObserverService);
-var visitObserver = {
-  _visitCount: 0,
-  observe: function(aSubject, aTopic, aData) {
-    if (!startPage.equals(aSubject.QueryInterface(Ci.nsIURI)) ||
-        ++this._visitCount < 2) {
-      return;
-    }
-    os.removeObserver(this, aTopic);
-    finishTest();
-  },
-};
-os.addObserver(visitObserver, "uri-visit-saved", false);
-
-PlacesUtils.asyncHistory.isURIVisited(startPage, function(aURI, aIsVisited) {
-  SimpleTest.ok(!aIsVisited, "Initial page does not start in global history. " +
-                "Note: this will also fail if you run the test twice.");
-  PlacesUtils.asyncHistory.isURIVisited(postedPage, function(aURI, aIsVisited) {
-    SimpleTest.ok(!aIsVisited, "Posted page does not start in global history.");
-    w = window.open(startURI, "", "width=10,height=10");
-  });
-});
-
-function finishTest() {
-  // We need to check that this was not added to global history.
-  PlacesUtils.asyncHistory.isURIVisited(startPage, function(aURI, aIsVisited) {
-    SimpleTest.ok(aIsVisited, "Initial page was added to global history.");
-    PlacesUtils.asyncHistory.isURIVisited(postedPage, function(aURI, aIsVisited) {
-      SimpleTest.ok(!aIsVisited, "Posted page was not added to global history.");
-      w.close();
-      SimpleTest.finish();
-    });
-  });
-}
-
-</script>
-</pre>
-</body>
-</html>
--- a/toolkit/components/satchel/test/parent_utils.js
+++ b/toolkit/components/satchel/test/parent_utils.js
@@ -1,9 +1,9 @@
-var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/FormHistory.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://testing-common/ContentTaskUtils.jsm");
 
 var gAutocompletePopup = Services.ww.activeWindow.
                                    document.
                                    getElementById("PopupAutoComplete");
--- a/toolkit/components/satchel/test/satchel_common.js
+++ b/toolkit/components/satchel/test/satchel_common.js
@@ -1,19 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var Services = SpecialPowers.Services;
 var gPopupShownListener;
 var gLastAutoCompleteResults;
+var gChromeScript;
 
 /*
- * $_
- *
  * Returns the element with the specified |name| attribute.
  */
 function $_(formNum, name) {
   var form = document.getElementById("form" + formNum);
   if (!form) {
     ok(false, "$_ couldn't find requested form " + formNum);
     return null;
   }
@@ -68,27 +66,33 @@ function getMenuEntries() {
     throw new Error("no autocomplete results");
   }
 
   var results = gLastAutoCompleteResults;
   gLastAutoCompleteResults = null;
   return results;
 }
 
+function checkArrayValues(actualValues, expectedValues, msg) {
+  is(actualValues.length, expectedValues.length, "Checking array values: " + msg);
+  for (var i = 0; i < expectedValues.length; i++)
+    is(actualValues[i], expectedValues[i], msg + " Checking array entry #" + i);
+}
+
 var checkObserver = {
   verifyStack: [],
   callback: null,
 
   init() {
-    script.sendAsyncMessage("addObserver");
-    script.addMessageListener("satchel-storage-changed", this.observe.bind(this));
+    gChromeScript.sendAsyncMessage("addObserver");
+    gChromeScript.addMessageListener("satchel-storage-changed", this.observe.bind(this));
   },
 
   uninit() {
-    script.sendAsyncMessage("removeObserver");
+    gChromeScript.sendAsyncMessage("removeObserver");
   },
 
   waitForChecks: function(callback) {
     if (this.verifyStack.length == 0)
       callback();
     else
       this.callback = callback;
   },
@@ -135,76 +139,118 @@ function getFormSubmitButton(formNum) {
   while (button && button.type != "submit") { button = button.nextSibling; }
   ok(button != null, "getting form submit button");
 
   return button;
 }
 
 // Count the number of entries with the given name and value, and call then(number)
 // when done. If name or value is null, then the value of that field does not matter.
-function countEntries(name, value, then) {
-  script.sendAsyncMessage("countEntries", { name, value });
-  script.addMessageListener("entriesCounted", function counted(data) {
-    script.removeMessageListener("entriesCounted", counted);
-    if (!data.ok) {
-      ok(false, "Error occurred counting form history");
-      SimpleTest.finish();
-      return;
-    }
+function countEntries(name, value, then = null) {
+  return new Promise(resolve => {
+    gChromeScript.sendAsyncMessage("countEntries", { name, value });
+    gChromeScript.addMessageListener("entriesCounted", function counted(data) {
+      gChromeScript.removeMessageListener("entriesCounted", counted);
+      if (!data.ok) {
+        ok(false, "Error occurred counting form history");
+        SimpleTest.finish();
+        return;
+      }
 
-    then(data.count);
+      if (then) {
+        then(data.count);
+      }
+      resolve(data.count);
+    });
   });
 }
 
 // Wrapper around FormHistory.update which handles errors. Calls then() when done.
-function updateFormHistory(changes, then) {
-  script.sendAsyncMessage("updateFormHistory", { changes });
-  script.addMessageListener("formHistoryUpdated", function updated({ ok }) {
-    script.removeMessageListener("formHistoryUpdated", updated);
-    if (!ok) {
-      ok(false, "Error occurred updating form history");
-      SimpleTest.finish();
-      return;
-    }
+function updateFormHistory(changes, then = null) {
+  return new Promise(resolve => {
+    gChromeScript.sendAsyncMessage("updateFormHistory", { changes });
+    gChromeScript.addMessageListener("formHistoryUpdated", function updated({ ok }) {
+      gChromeScript.removeMessageListener("formHistoryUpdated", updated);
+      if (!ok) {
+        ok(false, "Error occurred updating form history");
+        SimpleTest.finish();
+        return;
+      }
 
-    then();
+      if (then) {
+        then();
+      }
+      resolve();
+    });
   });
 }
 
-function notifyMenuChanged(expectedCount, expectedFirstValue, then) {
-  script.sendAsyncMessage("waitForMenuChange",
-                          { expectedCount,
-                            expectedFirstValue });
-  script.addMessageListener("gotMenuChange", function changed({ results }) {
-    script.removeMessageListener("gotMenuChange", changed);
-    gLastAutoCompleteResults = results;
-    then();
+function notifyMenuChanged(expectedCount, expectedFirstValue, then = null) {
+  return new Promise(resolve => {
+    gChromeScript.sendAsyncMessage("waitForMenuChange",
+                            { expectedCount,
+                              expectedFirstValue });
+    gChromeScript.addMessageListener("gotMenuChange", function changed({ results }) {
+      gChromeScript.removeMessageListener("gotMenuChange", changed);
+      gLastAutoCompleteResults = results;
+      if (then) {
+        then(results);
+      }
+      resolve(results);
+    });
   });
 }
 
-function notifySelectedIndex(expectedIndex, then) {
-  script.sendAsyncMessage("waitForSelectedIndex", { expectedIndex });
-  script.addMessageListener("gotSelectedIndex", function changed() {
-    script.removeMessageListener("gotSelectedIndex", changed);
-    then();
+function notifySelectedIndex(expectedIndex, then = null) {
+  return new Promise(resolve => {
+    gChromeScript.sendAsyncMessage("waitForSelectedIndex", { expectedIndex });
+    gChromeScript.addMessageListener("gotSelectedIndex", function changed() {
+      gChromeScript.removeMessageListener("gotSelectedIndex", changed);
+      if (then) {
+        then();
+      }
+      resolve();
+    });
+  });
+}
+
+function getPopupState(then = null) {
+  return new Promise(resolve => {
+    gChromeScript.sendAsyncMessage("getPopupState");
+    gChromeScript.addMessageListener("gotPopupState", function listener(state) {
+      gChromeScript.removeMessageListener("gotPopupState", listener);
+      if (then) {
+        then(state);
+      }
+      resolve(state);
+    });
   });
 }
 
-function getPopupState(then) {
-  script.sendAsyncMessage("getPopupState");
-  script.addMessageListener("gotPopupState", function listener(state) {
-    script.removeMessageListener("gotPopupState", listener);
-    then(state);
+/**
+ * Resolve at the next popupshown event for the autocomplete popup
+ * @return {Promise} with the results
+ */
+function promiseACShown() {
+  return new Promise(resolve => {
+    gChromeScript.addMessageListener("onpopupshown", ({ results }) => {
+      resolve(results);
+    });
   });
 }
 
-var chromeURL = SimpleTest.getTestFileURL("parent_utils.js");
-var script = SpecialPowers.loadChromeScript(chromeURL);
-script.addMessageListener("onpopupshown", ({ results }) => {
-  gLastAutoCompleteResults = results;
-  if (gPopupShownListener)
-    gPopupShownListener();
-});
+function satchelCommonSetup() {
+  var chromeURL = SimpleTest.getTestFileURL("parent_utils.js");
+  gChromeScript = SpecialPowers.loadChromeScript(chromeURL);
+  gChromeScript.addMessageListener("onpopupshown", ({ results }) => {
+    gLastAutoCompleteResults = results;
+    if (gPopupShownListener)
+      gPopupShownListener();
+  });
 
-SimpleTest.registerCleanupFunction(() => {
-  script.sendAsyncMessage("cleanup");
-  script.destroy();
-});
+  SimpleTest.registerCleanupFunction(() => {
+    gChromeScript.sendAsyncMessage("cleanup");
+    gChromeScript.destroy();
+  });
+}
+
+
+satchelCommonSetup();
--- a/toolkit/content/tests/chrome/bug263683_window.xul
+++ b/toolkit/content/tests/chrome/bug263683_window.xul
@@ -1,83 +1,149 @@
 <?xml version="1.0"?>
 
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+  href="chrome://mochikit/content/tests/SimpleTest/test.css"
+  type="text/css"?>
 
 <window id="263683test"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         width="600"
         height="600"
         onload="SimpleTest.executeSoon(startTest);"
         title="263683 test">
 
   <script type="application/javascript"><![CDATA[
-    const Ci = Components.interfaces;
-    const Cc = Components.classes;
-    const Cr = Components.results;
+    const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components;
+    Cu.import("resource://gre/modules/AppConstants.jsm");
+    Cu.import("resource://gre/modules/Task.jsm");
+    Cu.import("resource://testing-common/ContentTask.jsm");
+    ContentTask.setTestScope(window.opener.wrappedJSObject);
 
     var gFindBar = null;
     var gBrowser;
 
-    var imports = ["SimpleTest", "ok"];
+    var imports = ["SimpleTest", "ok", "info"];
     for (var name of imports) {
       window[name] = window.opener.wrappedJSObject[name];
     }
 
-    function finish() {
-      window.close();
-      SimpleTest.finish();
+    function startTest() {
+      Task.spawn(function* () {
+        gFindBar = document.getElementById("FindToolbar");
+        for (let browserId of ["content", "content-remote"]) {
+          yield startTestWithBrowser(browserId);
+        }
+      }).then(() => {
+        window.close();
+        SimpleTest.finish();
+      });
     }
 
-    function startTest() {
-      gFindBar = document.getElementById("FindToolbar");
-      gBrowser = document.getElementById("content");
-      gBrowser.addEventListener("pageshow", onPageShow, false);
+    function* startTestWithBrowser(browserId) {
+      // We're bailing out when testing a remote browser on OSX 10.6, because it
+      // fails permanently.
+      if (browserId.endsWith("remote") && AppConstants.isPlatformAndVersionAtMost("macosx", 11)) {
+        return;
+      }
+
+      info("Starting test with browser '" + browserId + "'");
+      gBrowser = document.getElementById(browserId);
+      gFindBar.browser = gBrowser;
+      let promise = ContentTask.spawn(gBrowser, null, function* () {
+        return new Promise(resolve => {
+          addEventListener("DOMContentLoaded", function listener() {
+            removeEventListener("DOMContentLoaded", listener);
+            resolve();
+          });
+        });
+      });
       gBrowser.loadURI('data:text/html,<h2>Text mozilla</h2><input id="inp" type="text" />');
+      yield promise;
+      yield onDocumentLoaded();
     }
 
-    function onPageShow() {
+    function toggleHighlightAndWait(highlight) {
+      return new Promise(resolve => {
+        let listener = {
+          onHighlightFinished: function() {
+            gFindBar.browser.finder.removeResultListener(listener);
+            resolve();
+          }
+        };
+        gFindBar.browser.finder.addResultListener(listener);
+        gFindBar.toggleHighlight(highlight);
+      });
+    }
+
+    function* onDocumentLoaded() {
       gFindBar.open();
       var search = "mozilla";
       gFindBar._findField.value = search;
       var matchCase = gFindBar.getElement("find-case-sensitive");
       if (matchCase.checked)
         matchCase.doCommand();
 
       gFindBar._find();
-      var highlightButton = gFindBar.getElement("highlight");
-      if (!highlightButton.checked)
-        highlightButton.click();
+      yield toggleHighlightAndWait(true);
+
+      yield ContentTask.spawn(gBrowser, { search }, function* (args) {
+        let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                                 .getInterface(Ci.nsISelectionDisplay)
+                                 .QueryInterface(Ci.nsISelectionController);
+        Assert.ok("SELECTION_FIND" in controller, "Correctly detects new selection type");
+        let selection = controller.getSelection(controller.SELECTION_FIND);
+        
+        Assert.equal(selection.rangeCount, 1,
+          "Correctly added a match to the selection type");
+        Assert.equal(selection.getRangeAt(0).toString().toLowerCase(),
+          args.search, "Added the correct match");
+      });
+
+      yield toggleHighlightAndWait(false);
+
+      yield ContentTask.spawn(gBrowser, { search }, function* (args) {
+        let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                                 .getInterface(Ci.nsISelectionDisplay)
+                                 .QueryInterface(Ci.nsISelectionController);
+        let selection = controller.getSelection(controller.SELECTION_FIND);
+        Assert.equal(selection.rangeCount, 0, "Correctly removed the range");
 
-      var controller = gFindBar.browser.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
-                                       .getInterface(Ci.nsISelectionDisplay)
-                                       .QueryInterface(Ci.nsISelectionController);
-      ok('SELECTION_FIND' in controller, "Correctly detects new selection type");
-      var selection = controller.getSelection(controller.SELECTION_FIND);
-      
-      ok(selection.rangeCount == 1, "Correctly added a match to the selection type");
-      ok(selection.getRangeAt(0).toString().toLowerCase() == search, "Added the correct match");
-      highlightButton.click();
-      ok(selection.rangeCount == 0, "Correctly removed the range");
+        let input = content.document.getElementById("inp");
+        input.value = args.search;
+      });
+ 
+      yield toggleHighlightAndWait(true);
+
+      yield ContentTask.spawn(gBrowser, { search }, function* (args) {
+        let input = content.document.getElementById("inp");
+        let inputController = input.editor.selectionController;
+        let inputSelection = inputController.getSelection(inputController.SELECTION_FIND);
 
-      var input = gBrowser.contentDocument.getElementById("inp");
-      input.value = search;
- 
-      highlightButton.click();
+        Assert.equal(inputSelection.rangeCount, 1,
+          "Correctly added a match from input to the selection type");
+        Assert.equal(inputSelection.getRangeAt(0).toString().toLowerCase(),
+          args.search, "Added the correct match");
+      });
+
+      yield toggleHighlightAndWait(false);
 
-      var inputController = input.editor.selectionController;
-      var inputSelection = inputController.getSelection(inputController.SELECTION_FIND);
+      yield ContentTask.spawn(gBrowser, null, function* () {
+        let input = content.document.getElementById("inp");
+        let inputController = input.editor.selectionController;
+        let inputSelection = inputController.getSelection(inputController.SELECTION_FIND);
 
-      ok(inputSelection.rangeCount == 1, "Correctly added a match from input to the selection type");
-      ok(inputSelection.getRangeAt(0).toString().toLowerCase() == search, "Added the correct match");
-      highlightButton.click();
-      ok(inputSelection.rangeCount == 0, "Correctly removed the range");
-      finish();
+        Assert.equal(inputSelection.rangeCount, 0, "Correctly removed the range");
+      });
+
+      gFindBar.close();
     }
   ]]></script>
 
   <browser type="content-primary" flex="1" id="content" src="about:blank"/>
+  <browser type="content-primary" flex="1" id="content-remote" remote="true" src="about:blank"/>
   <findbar id="FindToolbar" browserid="content"/>
 </window>
--- a/toolkit/content/tests/chrome/bug304188_window.xul
+++ b/toolkit/content/tests/chrome/bug304188_window.xul
@@ -1,55 +1,94 @@
 <?xml version="1.0"?>
 
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+  href="chrome://mochikit/content/tests/SimpleTest/test.css"
+  type="text/css"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         width="600"
         height="600"
         onload="onLoad();"
         title="FindbarTest for bug 304188 - 
 find-menu appears in editor element which has had makeEditable() called but designMode not set">
 
   <script type="application/javascript"><![CDATA[
+    const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components;
+    Cu.import("resource://gre/modules/Task.jsm");
+    Cu.import("resource://testing-common/ContentTask.jsm");
+    ContentTask.setTestScope(window.opener.wrappedJSObject);
+
     var gFindBar = null;
     var gBrowser;
 
-    function ok(condition, message) {
-      window.opener.wrappedJSObject.SimpleTest.ok(condition, message);
-    }
-    function finish() {
-      window.close();
-      window.opener.wrappedJSObject.SimpleTest.finish();
+    var imports = ["SimpleTest", "ok", "info"];
+    for (var name of imports) {
+      window[name] = window.opener.wrappedJSObject[name];
     }
 
     function onLoad() {
-      gFindBar = document.getElementById("FindToolbar");
-      gBrowser = document.getElementById("content");
-      var webnav = gBrowser.webNavigation;
-      var edsession = webnav.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                            .getInterface(Components.interfaces.nsIEditingSession);
-      edsession.makeWindowEditable(gBrowser.contentWindow, "html", false, true, false);
-      gBrowser.contentWindow.focus();
-      enterStringIntoEditor("'");
-      enterStringIntoEditor("/");
+      Task.spawn(function* () {
+        gFindBar = document.getElementById("FindToolbar");
+        for (let browserId of ["content", "content-remote"]) {
+          yield startTestWithBrowser(browserId);
+        }
+      }).then(() => {
+        window.close();
+        SimpleTest.finish();
+      });
+    }
+
+    function* startTestWithBrowser(browserId) {
+      info("Starting test with browser '" + browserId + "'");
+      gBrowser = document.getElementById(browserId);
+      gFindBar.browser = gBrowser;
+      let promise = ContentTask.spawn(gBrowser, null, function* () {
+        return new Promise(resolve => {
+          addEventListener("DOMContentLoaded", function listener() {
+            removeEventListener("DOMContentLoaded", listener);
+            resolve();
+          });
+        });
+      });
+      gBrowser.loadURI("data:text/html;charset=utf-8,some%20random%20text");
+      yield promise;
+      yield onDocumentLoaded();
+    }
+
+    function* onDocumentLoaded() {
+      yield ContentTask.spawn(gBrowser, null, function* () {
+        var edsession = content.QueryInterface(Ci.nsIInterfaceRequestor)
+                               .getInterface(Ci.nsIWebNavigation)
+                               .QueryInterface(Ci.nsIInterfaceRequestor)
+                               .getInterface(Ci.nsIEditingSession);
+        edsession.makeWindowEditable(content, "html", false, true, false);
+        content.focus();
+      });
+      
+      yield enterStringIntoEditor("'");
+      yield enterStringIntoEditor("/");
+
       ok(gFindBar.hidden,
          "Findfield should have stayed hidden after entering editor test");
-      finish();
     }
 
-    function enterStringIntoEditor(aString) {
-      for (var i=0; i < aString.length; i++) {
-        var event = gBrowser.contentDocument.createEvent("KeyEvents");
-        event.initKeyEvent("keypress", true, true, null, false, false,
-                           false, false, 0, aString.charCodeAt(i));
-        gBrowser.contentDocument.body.dispatchEvent(event);
+    function* enterStringIntoEditor(aString) {
+      for (let i = 0; i < aString.length; i++) {
+        yield ContentTask.spawn(gBrowser, { charCode: aString.charCodeAt(i) }, function* (args) {
+          let event = content.document.createEvent("KeyEvents");
+          event.initKeyEvent("keypress", true, true, null, false, false,
+                             false, false, 0, args.charCode);
+          content.document.body.dispatchEvent(event);
+        });
       }
     }
   ]]></script>
 
-  <browser id="content" flex="1" src="data:text/html;charset=utf-8,some%20random%20text" type="content-primary"/>
+  <browser id="content" flex="1" src="about:blank" type="content-primary"/>
+  <browser id="content-remote" remote="true" flex="1" src="about:blank" type="content-primary"/>
   <findbar id="FindToolbar" browserid="content"/>
 </window>
--- a/toolkit/content/tests/chrome/bug331215_window.xul
+++ b/toolkit/content/tests/chrome/bug331215_window.xul
@@ -1,73 +1,104 @@
 <?xml version="1.0"?>
 
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+  href="chrome://mochikit/content/tests/SimpleTest/test.css"
+  type="text/css"?>
 
 <window id="331215test"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         width="600"
         height="600"
         onload="SimpleTest.executeSoon(startTest);"
         title="331215 test">
 
   <script type="application/javascript"><![CDATA[
-    const Ci = Components.interfaces;
-    const Cc = Components.classes;
-    const Cr = Components.results;
+    const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components;
+    Cu.import("resource://gre/modules/Task.jsm");
+    Cu.import("resource://testing-common/ContentTask.jsm");
+    ContentTask.setTestScope(window.opener.wrappedJSObject);
 
     var gFindBar = null;
     var gBrowser;
 
-    var imports = ["SimpleTest", "ok"];
+    var imports = ["SimpleTest", "ok", "info"];
     for (var name of imports) {
       window[name] = window.opener.wrappedJSObject[name];
     }
 
-    function finish() {
-      window.close();
-      SimpleTest.finish();
+    function startTest() {
+      Task.spawn(function* () {
+        gFindBar = document.getElementById("FindToolbar");
+        for (let browserId of ["content", "content-remote"]) {
+          yield startTestWithBrowser(browserId);
+        }
+      }).then(() => {
+        window.close();
+        SimpleTest.finish();
+      });
     }
 
-    function startTest() {
-      gFindBar = document.getElementById("FindToolbar");
-      gBrowser = document.getElementById("content");
-      gBrowser.addEventListener("pageshow", onPageShow, false);
+    function* startTestWithBrowser(browserId) {
+      info("Starting test with browser '" + browserId + "'");
+      gBrowser = document.getElementById(browserId);
+      gFindBar.browser = gBrowser;
+      let promise = ContentTask.spawn(gBrowser, null, function* () {
+        return new Promise(resolve => {
+          addEventListener("DOMContentLoaded", function listener() {
+            removeEventListener("DOMContentLoaded", listener);
+            resolve();
+          });
+        });
+      });
       gBrowser.loadURI("data:text/plain,latest");
+      yield promise;
+      yield onDocumentLoaded();
     }
 
-    function onPageShow() {
+    function* onDocumentLoaded() {
       document.getElementById("cmd_find").doCommand();
-      enterStringIntoFindField("test");
+      yield enterStringIntoFindField("test");
       document.commandDispatcher
               .getControllerForCommand("cmd_moveTop")
               .doCommand("cmd_moveTop");
-      enterStringIntoFindField("l");
+      yield enterStringIntoFindField("l");
       ok(gFindBar._findField.getAttribute("status") == "notfound",
          "Findfield status attribute should have been 'notfound'" +
          " after entering ltest");              
-      enterStringIntoFindField("a");
+      yield enterStringIntoFindField("a");
       ok(gFindBar._findField.getAttribute("status") != "notfound",
          "Findfield status attribute should not have been 'notfound'" +
          " after entering latest");
-      finish();
     }
 
-    function enterStringIntoFindField(aString) {
-      for (var i=0; i < aString.length; i++) {
-        var event = document.createEvent("KeyEvents");
+    function* enterStringIntoFindField(aString) {
+      for (let i = 0; i < aString.length; i++) {      
+        let event = document.createEvent("KeyEvents");
+        let promise = new Promise(resolve => {
+          let listener = {
+            onFindResult: function() {
+              gFindBar.browser.finder.removeResultListener(listener);
+              resolve();
+            }
+          };
+          gFindBar.browser.finder.addResultListener(listener);
+        });
         event.initKeyEvent("keypress", true, true, null, false, false,
                            false, false, 0, aString.charCodeAt(i));
         gFindBar._findField.inputField.dispatchEvent(event);
+        yield promise;
       }
     }
   ]]></script>
 
   <commandset>
     <command id="cmd_find" oncommand="document.getElementById('FindToolbar').onFindCommand();"/>
   </commandset>
   <browser type="content-primary" flex="1" id="content" src="about:blank"/>
+  <browser type="content-primary" flex="1" id="content-remote" remote="true" src="about:blank"/>
   <findbar id="FindToolbar" browserid="content"/>
 </window>
--- a/toolkit/modules/RemoteFinder.jsm
+++ b/toolkit/modules/RemoteFinder.jsm
@@ -24,26 +24,28 @@ function RemoteFinder(browser) {
 }
 
 RemoteFinder.prototype = {
   swapBrowser: function(aBrowser) {
     if (this._messageManager) {
       this._messageManager.removeMessageListener("Finder:Result", this);
       this._messageManager.removeMessageListener("Finder:MatchesResult", this);
       this._messageManager.removeMessageListener("Finder:CurrentSelectionResult",this);
+      this._messageManager.removeMessageListener("Finder:HighlightFinished",this);
     }
     else {
       aBrowser.messageManager.sendAsyncMessage("Finder:Initialize");
     }
 
     this._browser = aBrowser;
     this._messageManager = this._browser.messageManager;
     this._messageManager.addMessageListener("Finder:Result", this);
     this._messageManager.addMessageListener("Finder:MatchesResult", this);
     this._messageManager.addMessageListener("Finder:CurrentSelectionResult", this);
+    this._messageManager.addMessageListener("Finder:HighlightFinished", this);
 
     // Ideally listeners would have removed themselves but that doesn't happen
     // right now
     this._listeners.clear();
   },
 
   addResultListener: function (aListener) {
     this._listeners.add(aListener);
@@ -66,16 +68,20 @@ RemoteFinder.prototype = {
       case "Finder:MatchesResult":
         callback = "onMatchesCountResult";
         params = [ aMessage.data ];
         break;
       case "Finder:CurrentSelectionResult":
         callback = "onCurrentSelection";
         params = [ aMessage.data.selection, aMessage.data.initial ];
         break;
+      case "Finder:HighlightFinished":
+        callback = "onHighlightFinished";
+        params = [ aMessage.data ];
+        break;
     }
 
     for (let l of this._listeners) {
       // Don't let one callback throwing stop us calling the rest
       try {
         l[callback].apply(l, params);
       }
       catch (e) {
@@ -197,16 +203,20 @@ RemoteFinderListener.prototype = {
   },
 
   // When the child receives messages with results of requestMatchesCount,
   // it passes them forward to the parent.
   onMatchesCountResult: function (aData) {
     this._global.sendAsyncMessage("Finder:MatchesResult", aData);
   },
 
+  onHighlightFinished: function(aData) {
+    this._global.sendAsyncMessage("Finder:HighlightFinished", aData);
+  },
+
   receiveMessage: function (aMessage) {
     let data = aMessage.data;
 
     switch (aMessage.name) {
       case "Finder:CaseSensitive":
         this._finder.caseSensitive = data.caseSensitive;
         break;
 
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -268,43 +268,44 @@ function createAppInfo(ID, name, version
     crashReporter: true,
     extraProps: {
       browserTabsRemoteAutostart: false,
     },
   });
   gAppInfo = tmp.getAppInfo();
 }
 
-function getManifestURIForBundle(file) {
+function getManifestURIForBundle(file, manifest="manifest.json") {
   if (file.isDirectory()) {
-    file.append("install.rdf");
-    if (file.exists()) {
-      return NetUtil.newURI(file);
+    let path = file.clone();
+    path.append("install.rdf");
+    if (path.exists()) {
+      return NetUtil.newURI(path);
     }
 
-    file.leafName = "manifest.json";
-    if (file.exists()) {
-      return NetUtil.newURI(file);
+    path.leafName = manifest;
+    if (path.exists()) {
+      return NetUtil.newURI(path);
     }
 
     throw new Error("No manifest file present");
   }
 
   let zip = AM_Cc["@mozilla.org/libjar/zip-reader;1"].
             createInstance(AM_Ci.nsIZipReader);
   zip.open(file);
   try {
     let uri = NetUtil.newURI(file);
 
     if (zip.hasEntry("install.rdf")) {
       return NetUtil.newURI("jar:" + uri.spec + "!/" + "install.rdf");
     }
 
-    if (zip.hasEntry("manifest.json")) {
-      return NetUtil.newURI("jar:" + uri.spec + "!/" + "manifest.json");
+    if (zip.hasEntry(manifest)) {
+      return NetUtil.newURI("jar:" + uri.spec + "!/" + manifest);
     }
 
     throw new Error("No manifest file present");
   }
   finally {
     zip.close();
   }
 }
@@ -336,18 +337,22 @@ let getIDForManifest = Task.async(functi
                      getService(AM_Ci.nsIRDFService);
 
     let rdfID = ds.GetTarget(rdfService.GetResource("urn:mozilla:install-manifest"),
                              rdfService.GetResource("http://www.mozilla.org/2004/em-rdf#id"),
                              true);
     return rdfID.QueryInterface(AM_Ci.nsIRDFLiteral).Value;
   }
   else {
-    let manifest = JSON.parse(data);
-    return manifest.applications.gecko.id;
+    try {
+      let manifest = JSON.parse(data);
+      return manifest.applications.gecko.id;
+    } catch (err) {
+      return null;
+    }
   }
 });
 
 let gUseRealCertChecks = false;
 function overrideCertDB(handler) {
   // Unregister the real database. This only works because the add-ons manager
   // hasn't started up and grabbed the certificate database yet.
   let registrar = Components.manager.QueryInterface(AM_Ci.nsIComponentRegistrar);
@@ -375,16 +380,25 @@ function overrideCertDB(handler) {
       return;
     }
 
     try {
       let manifestURI = getManifestURIForBundle(file);
 
       let id = yield getIDForManifest(manifestURI);
 
+      if (!id) {
+        manifestURI = getManifestURIForBundle(file, "mozilla.json");
+        id = yield getIDForManifest(manifestURI);
+      }
+
+      if (!id) {
+        throw new Error("Cannot find addon ID");
+      }
+
       // Make sure to close the open zip file or it will be locked.
       if (file.isFile()) {
         Services.obs.notifyObservers(file, "flush-cache-entry", "cert-override");
       }
 
       let fakeCert = {
         commonName: id
       }
@@ -1107,54 +1121,68 @@ function writeInstallRDFForExtension(aDa
  * @param   aManifest
  *          The data to write
  * @param   aDir
  *          The install directory to add the extension to
  * @param   aId
  *          An optional string to override the default installation aId
  * @return  A file pointing to where the extension was installed
  */
-function writeWebManifestForExtension(aData, aDir, aId = undefined) {
+function writeWebManifestForExtension(aData, aDir, aId = undefined, aMozData = undefined) {
   if (!aId)
     aId = aData.applications.gecko.id;
 
   if (TEST_UNPACKED) {
     let dir = aDir.clone();
     dir.append(aId);
     if (!dir.exists())
       dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
 
-    let file = dir.clone();
-    file.append("manifest.json");
-    if (file.exists())
-      file.remove(true);
+    function writeOne(filename, raw) {
+      let file = dir.clone();
+      file.append(filename);
+      if (file.exists())
+        file.remove(true);
 
-    let data = JSON.stringify(aData);
-    let fos = AM_Cc["@mozilla.org/network/file-output-stream;1"].
-              createInstance(AM_Ci.nsIFileOutputStream);
-    fos.init(file,
-             FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE,
-             FileUtils.PERMS_FILE, 0);
-    fos.write(data, data.length);
-    fos.close();
+      let data = JSON.stringify(raw);
+      let fos = AM_Cc["@mozilla.org/network/file-output-stream;1"].
+          createInstance(AM_Ci.nsIFileOutputStream);
+      fos.init(file,
+               FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE,
+               FileUtils.PERMS_FILE, 0);
+      fos.write(data, data.length);
+      fos.close();
+    }
+
+    writeOne("manifest.json", aData);
+    if (aMozData) {
+      writeOne("mozilla.json", aMozData);
+    }
 
     return dir;
   }
   else {
     let file = aDir.clone();
     file.append(aId + ".xpi");
 
     let stream = AM_Cc["@mozilla.org/io/string-input-stream;1"].
                  createInstance(AM_Ci.nsIStringInputStream);
     stream.setData(JSON.stringify(aData), -1);
     let zipW = AM_Cc["@mozilla.org/zipwriter;1"].
                createInstance(AM_Ci.nsIZipWriter);
     zipW.open(file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE);
     zipW.addEntryStream("manifest.json", 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE,
                         stream, false);
+    if (aMozData) {
+      let mozStream = AM_Cc["@mozilla.org/io/string-input-stream;1"].
+                      createInstance(AM_Ci.nsIStringInputStream);
+      mozStream.setData(JSON.stringify(aMozData), -1);
+      zipW.addEntryStream("mozilla.json", 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE,
+                          mozStream, false);
+    }
     zipW.close();
 
     return file;
   }
 }
 
 /**
  * Writes an install.rdf manifest into a packed extension using the properties passed
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
@@ -154,16 +154,51 @@ add_task(function*() {
   let file = getFileForAddon(profileDir, ID);
   do_check_true(file.exists());
 
   addon.uninstall();
 
   yield promiseRestartManager();
 });
 
+// applications.gecko.id may be in mozilla.json
+add_task(function* test_mozilla_json() {
+  writeWebManifestForExtension({
+    name: "Web Extension Name",
+    version: "1.0",
+    manifest_version: 2,
+  }, profileDir, ID, {
+    applications: {
+      gecko: {
+        id: ID
+      }
+    }
+  });
+
+  yield promiseRestartManager();
+
+  let addon = yield promiseAddonByID(ID);
+  do_check_neq(addon, null);
+  do_check_eq(addon.version, "1.0");
+  do_check_eq(addon.name, "Web Extension Name");
+  do_check_true(addon.isCompatible);
+  do_check_false(addon.appDisabled);
+  do_check_true(addon.isActive);
+  do_check_false(addon.isSystem);
+  do_check_eq(addon.type, "extension");
+  do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+
+  let file = getFileForAddon(profileDir, ID);
+  do_check_true(file.exists());
+
+  addon.uninstall();
+
+  yield promiseRestartManager();
+});
+
 add_task(function* test_manifest_localization() {
   const ID = "webextension3@tests.mozilla.org";
 
   yield promiseInstallAllFiles([do_get_addon("webextension_3")], true);
   yield promiseAddonStartup();
 
   let addon = yield promiseAddonByID(ID);
   addon.userDisabled = true;
--- a/toolkit/themes/osx/global/jar.mn
+++ b/toolkit/themes/osx/global/jar.mn
@@ -170,16 +170,17 @@ toolkit.jar:
   skin/classic/global/scale/scale-tray-vert.gif                      (scale/scale-tray-vert.gif)
   skin/classic/global/splitter/dimple.png                            (splitter/dimple.png)
   skin/classic/global/splitter/grip-bottom.gif                       (splitter/grip-bottom.gif)
   skin/classic/global/splitter/grip-top.gif                          (splitter/grip-top.gif)
   skin/classic/global/splitter/grip-left.gif                         (splitter/grip-left.gif)
   skin/classic/global/splitter/grip-right.gif                        (splitter/grip-right.gif)
   skin/classic/global/toolbar/spring.png                             (toolbar/spring.png)
   skin/classic/global/toolbar/toolbar-separator.png                  (toolbar/toolbar-separator.png)
+  skin/classic/global/tree/arrow-disclosure.svg                      (tree/arrow-disclosure.svg)
   skin/classic/global/tree/columnpicker.gif                          (tree/columnpicker.gif)
   skin/classic/global/tree/folder.png                                (tree/folder.png)
   skin/classic/global/tree/folder@2x.png                             (tree/folder@2x.png)
 
 #if MOZ_BUILD_APP == browser
 [browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
 #elif MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES
 [extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
--- a/toolkit/themes/osx/global/shared.inc
+++ b/toolkit/themes/osx/global/shared.inc
@@ -8,23 +8,30 @@
 %define roundButtonShadow 0 1px rgba(255,255,255,.5), inset 0 1px 1px rgba(255,255,255,.5)
 %define roundButtonPressedBackground #dadada
 %define roundButtonPressedShadow 0 1px rgba(255,255,255,.4), inset 0 1px 3px rgba(0,0,0,.2)
 
 %define scopeBarBackground linear-gradient(#E8E8E8, #D0D0D0) repeat-x
 %define scopeBarSeparatorBorder 1px solid #888
 %define scopeBarTitleColor #6D6D6D
 
-%define sidebarItemBorderTop 1px solid #94A1C0
-%define sidebarItemBackground linear-gradient(#A0B0CF, #7386AB) repeat-x
-%define sidebarItemFocusedBorderTop 1px solid #5382C5
-%define sidebarItemFocusedBackground linear-gradient(#6494D4, #2559AC) repeat-x
-%define sidebarItemGraphiteBorderTop 1px solid #97A4B1
-%define sidebarItemGraphiteBackground linear-gradient(#AAB7C4, #8393A4) repeat-x
-%define sidebarItemGraphiteFocusedBorderTop 1px solid #6B798D
-%define sidebarItemGraphiteFocusedBackground linear-gradient(#8293A6, #425972) repeat-x
-%define sidebarItemInactiveBorderTop 1px solid #979797
-%define sidebarItemInactiveBackground linear-gradient(#B4B4B4, #8A8A8A) repeat-x
+%define sidebarItemBorderTop 1px solid #bcc5d5
+%define sidebarItemBorderBottom 1px solid #94a1b9
+%define sidebarItemBackground linear-gradient(#c3ccdf 1px, #bdc7db 2px, #9dabc5)
+%define sidebarItemFocusedBorderTop 1px solid #61a6dd
+%define sidebarItemFocusedBorderBottom 1px solid #387cc0
+%define sidebarItemFocusedBackground linear-gradient(#79bbe6 1px, #6eb2e3 2px, #3f8ad2)
+%define sidebarItemGraphiteBorderTop 1px solid #a9b5c1
+%define sidebarItemGraphiteBorderBottom 1px solid #8594a4
+%define sidebarItemGraphiteBackground linear-gradient(#bfcad3 1px, #b6c2cc 2px, #94a1b1)
+%define sidebarItemGraphiteFocusedBorderTop 1px solid #7c8c9d
+%define sidebarItemGraphiteFocusedBorderBottom 1px solid #536c83
+%define sidebarItemGraphiteFocusedBackground linear-gradient(#9eacbb 1px, #8fa1b1 2px, #587087)
+
+%define sidebarItemBackgroundYosemite hsla(0,0%,0%,.1)
+%define sidebarItemFocusedBackgroundYosemite #2d8cf9
+%define sidebarItemGraphiteBackgroundYosemite hsla(0,0%,0%,.1)
+%define sidebarItemGraphiteFocusedBackgroundYosemite #86858c
 
 %define toolbarbuttonCornerRadius 3px
 %define toolbarbuttonBackground linear-gradient(#FFF, #ADADAD) repeat-x
 %define toolbarbuttonPressedInnerShadow inset rgba(0, 0, 0, 0.3) 0 -6px 10px, inset #000 0 1px 3px, inset rgba(0, 0, 0, 0.2) 0 1px 3px
 %define toolbarbuttonInactiveBorderColor rgba(146, 146, 146, 0.84)
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/osx/global/tree/arrow-disclosure.svg
@@ -0,0 +1,26 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+
+  <style>
+    .icon:not(:target) {
+      display: none;
+    }
+
+    .icon {
+      fill: #8c8c8c;
+    }
+
+    .icon.white {
+      fill: #fff;
+    }
+  </style>
+
+  <polygon id="arrow-disclosure-collapsed" class="icon" points="4,4  12,8.5  4,13" />
+  <polygon id="arrow-disclosure-collapsed-inverted" class="icon white" points="4,4  12,8.5  4,13" />
+
+  <polygon id="arrow-disclosure-expanded" class="icon" points="3,5  12,5  7.5,13" />
+  <polygon id="arrow-disclosure-expanded-inverted" class="icon white" points="3,5  12,5  7.5,13" />
+
+</svg>
--- a/uriloader/exthandler/nsExternalProtocolHandler.cpp
+++ b/uriloader/exthandler/nsExternalProtocolHandler.cpp
@@ -36,19 +36,17 @@ class nsILoadInfo;
 
 class nsExtProtocolChannel : public nsIChannel
 {
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSICHANNEL
     NS_DECL_NSIREQUEST
 
-    nsExtProtocolChannel();
-
-    nsresult SetURI(nsIURI*);
+    nsExtProtocolChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo);
 
 private:
     virtual ~nsExtProtocolChannel();
 
     nsresult OpenURL();
     void Finish(nsresult aResult);
     
     nsCOMPtr<nsIURI> mUrl;
@@ -66,18 +64,23 @@ NS_IMPL_ADDREF(nsExtProtocolChannel)
 NS_IMPL_RELEASE(nsExtProtocolChannel)
 
 NS_INTERFACE_MAP_BEGIN(nsExtProtocolChannel)
    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIChannel)
    NS_INTERFACE_MAP_ENTRY(nsIChannel)
    NS_INTERFACE_MAP_ENTRY(nsIRequest)
 NS_INTERFACE_MAP_END_THREADSAFE
 
-nsExtProtocolChannel::nsExtProtocolChannel() : mStatus(NS_OK), 
-                                               mWasOpened(false)
+nsExtProtocolChannel::nsExtProtocolChannel(nsIURI* aURI,
+                                           nsILoadInfo* aLoadInfo)
+  : mUrl(aURI)
+  , mOriginalURI(aURI)
+  , mStatus(NS_OK)
+  , mWasOpened(false)
+  , mLoadInfo(aLoadInfo)
 {
 }
 
 nsExtProtocolChannel::~nsExtProtocolChannel()
 {}
 
 NS_IMETHODIMP nsExtProtocolChannel::GetLoadGroup(nsILoadGroup * *aLoadGroup)
 {
@@ -125,22 +128,16 @@ NS_IMETHODIMP nsExtProtocolChannel::SetO
  
 NS_IMETHODIMP nsExtProtocolChannel::GetURI(nsIURI* *aURI)
 {
   *aURI = mUrl;
   NS_IF_ADDREF(*aURI);
   return NS_OK; 
 }
  
-nsresult nsExtProtocolChannel::SetURI(nsIURI* aURI)
-{
-  mUrl = aURI;
-  return NS_OK; 
-}
-
 nsresult nsExtProtocolChannel::OpenURL()
 {
   nsresult rv = NS_ERROR_FAILURE;
   nsCOMPtr<nsIExternalProtocolService> extProtService (do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID));
 
   if (extProtService)
   {
 #ifdef DEBUG
@@ -381,26 +378,27 @@ nsExternalProtocolHandler::AllowPort(int
 {
     // don't override anything.  
     *_retval = false;
     return NS_OK;
 }
 // returns TRUE if the OS can handle this protocol scheme and false otherwise.
 bool nsExternalProtocolHandler::HaveExternalProtocolHandler(nsIURI * aURI)
 {
-  bool haveHandler = false;
-  if (aURI)
-  {
-    nsAutoCString scheme;
-    aURI->GetScheme(scheme);
-    nsCOMPtr<nsIExternalProtocolService> extProtSvc(do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID));
-    if (extProtSvc)
-      extProtSvc->ExternalProtocolHandlerExists(scheme.get(), &haveHandler);
+  MOZ_ASSERT(aURI);
+  nsAutoCString scheme;
+  aURI->GetScheme(scheme);
+
+  nsCOMPtr<nsIExternalProtocolService> extProtSvc(do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID));
+  if (!extProtSvc) {
+    return false;
   }
 
+  bool haveHandler = false;
+  extProtSvc->ExternalProtocolHandlerExists(scheme.get(), &haveHandler);
   return haveHandler;
 }
 
 NS_IMETHODIMP nsExternalProtocolHandler::GetProtocolFlags(uint32_t *aUritype)
 {
     // Make it norelative since it is a simple uri
     *aUritype = URI_NORELATIVE | URI_NOAUTH | URI_LOADABLE_BY_ANYONE |
         URI_NON_PERSISTABLE | URI_DOES_NOT_RETURN_DATA;
@@ -421,43 +419,32 @@ NS_IMETHODIMP nsExternalProtocolHandler:
 
   NS_ADDREF(*_retval = uri);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsExternalProtocolHandler::NewChannel2(nsIURI* aURI,
                                        nsILoadInfo* aLoadInfo,
-                                       nsIChannel** _retval)
+                                       nsIChannel** aRetval)
 {
+  NS_ENSURE_TRUE(aURI, NS_ERROR_UNKNOWN_PROTOCOL);
+  NS_ENSURE_TRUE(aRetval, NS_ERROR_UNKNOWN_PROTOCOL);
+
   // Only try to return a channel if we have a protocol handler for the url.
   // nsOSHelperAppService::LoadUriInternal relies on this to check trustedness
   // for some platforms at least.  (win uses ::ShellExecute and unix uses
   // gnome_url_show.)
-  bool haveExternalHandler = HaveExternalProtocolHandler(aURI);
-  if (haveExternalHandler)
-  {
-    nsCOMPtr<nsIChannel> channel = new nsExtProtocolChannel();
-    if (!channel) return NS_ERROR_OUT_OF_MEMORY;
-
-    ((nsExtProtocolChannel*) channel.get())->SetURI(aURI);
-    channel->SetOriginalURI(aURI);
-
-    // set the loadInfo on the new channel
-    ((nsExtProtocolChannel*) channel.get())->SetLoadInfo(aLoadInfo);
-
-    if (_retval)
-    {
-      *_retval = channel;
-      NS_IF_ADDREF(*_retval);
-      return NS_OK;
-    }
+  if (!HaveExternalProtocolHandler(aURI)) {
+    return NS_ERROR_UNKNOWN_PROTOCOL;
   }
 
-  return NS_ERROR_UNKNOWN_PROTOCOL;
+  nsCOMPtr<nsIChannel> channel = new nsExtProtocolChannel(aURI, aLoadInfo);
+  channel.forget(aRetval);
+  return NS_OK;
 }
 
 NS_IMETHODIMP nsExternalProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval)
 {
   return NewChannel2(aURI, nullptr, _retval);
 }
 
 ///////////////////////////////////////////////////////////////////////