Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE
authorRazvan Maries <rmaries@mozilla.com>
Tue, 21 May 2019 12:37:53 +0300
changeset 474729 f3fae269992d4984480f731765bd7040def16c60
parent 474728 195e60ec879a7f2073464428000f3398021d4ae6 (current diff)
parent 474670 b74e5737da64a7af28ab4f81f996950917aa71c5 (diff)
child 474730 4d7ef530fffba20a2b352cff9fa23438128105a7
push id36045
push userrmaries@mozilla.com
push dateTue, 21 May 2019 16:30:25 +0000
treeherdermozilla-central@3c0f78074b72 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -142,18 +142,16 @@ var whitelist = [
   {file: "chrome://mozapps/skin/downloads/downloadButtons.png", platforms: ["linux", "win"]},
   // Bug 1348558
   {file: "chrome://mozapps/skin/update/downloadButtons.png",
    platforms: ["linux"]},
   // Bug 1348559
   {file: "chrome://pippki/content/resetpassword.xul"},
   // Bug 1337345
   {file: "resource://gre/modules/Manifest.jsm"},
-  // Bug 1548381
-  {file: "resource://gre/modules/PasswordGenerator.jsm"},
   // Bug 1351097
   {file: "resource://gre/modules/accessibility/AccessFu.jsm"},
   // Bug 1356043
   {file: "resource://gre/modules/PerfMeasurement.jsm"},
   // Bug 1356045
   {file: "chrome://global/content/test-ipc.xul"},
   // Bug 1378173 (warning: still used by devtools)
   {file: "resource://gre/modules/Promise.jsm"},
--- a/browser/themes/shared/autocomplete.inc.css
+++ b/browser/themes/shared/autocomplete.inc.css
@@ -2,17 +2,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/. */
 %endif
 
 /* General popup rules */
 
 #PopupAutoComplete > richlistbox > richlistitem {
-  height: 20px;
   min-height: 20px;
   border: 0;
   border-radius: 0;
   padding: 0px 1px 0px 1px;
 }
 
 #PopupAutoComplete > richlistbox > richlistitem > .ac-site-icon {
   margin-inline-start: 4px;
@@ -34,65 +33,76 @@
   background-color: var(--arrowpanel-dimmed);
 }
 
 .autocomplete-richlistitem[selected] {
   background-color: Highlight;
   color: HighlightText;
 }
 
-/* Login form autocompletion with and without origin showing */
-#PopupAutoComplete > richlistbox > richlistitem[originaltype="loginWithOrigin"] > .login-wrapper > .ac-site-icon,
+/* Autocomplete richlistitem support for a two-line label display */
+
+#PopupAutoComplete > richlistbox > richlistitem > .two-line-wrapper {
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: row;
+  margin: 0;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem > .two-line-wrapper > .ac-site-icon {
+  margin-inline-start: auto;
+  margin-inline-end: 4px;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem > .two-line-wrapper > .labels-wrapper {
+  /* The text should flex while the icon should not */
+  flex: 1;
+  /* width/min-width are needed to get the text-overflow: ellipsis to work for the children */
+  min-width: 0;
+  width: 0;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem > .two-line-wrapper > .labels-wrapper > .label-row {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem > .two-line-wrapper > .labels-wrapper > .line2-label {
+  padding-top: 2px !important;
+  opacity: .6;
+}
+
+/* Login form autocompletion (with and without origin showing) and generated passwords */
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="generatedPassword"] > .two-line-wrapper > .ac-site-icon,
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="loginWithOrigin"] > .two-line-wrapper > .ac-site-icon,
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="login"] > .ac-site-icon {
   display: initial;
   list-style-image: url(chrome://browser/skin/login.svg);
   -moz-context-properties: fill;
   fill: GrayText;
 }
 
-#PopupAutoComplete > richlistbox > richlistitem[originaltype="loginWithOrigin"][selected] > .login-wrapper > .ac-site-icon,
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="generatedPassword"][selected] > .two-line-wrapper > .ac-site-icon,
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="loginWithOrigin"][selected] > .two-line-wrapper > .ac-site-icon,
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="login"] > .ac-site-icon[selected] {
   fill: HighlightText;
 }
 
-/* Login form autocompletion with origin showing */
+/* Login form autocompletion with origin showing and generated passwords */
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="generatedPassword"],
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="loginWithOrigin"] {
-  height: auto;
   padding: 4px;
 }
 
-#PopupAutoComplete > richlistbox > richlistitem[originaltype="loginWithOrigin"] > .login-wrapper {
-  box-sizing: border-box;
-  display: flex;
-  flex-direction: row;
-  margin: 0;
-}
 
-#PopupAutoComplete > richlistbox > richlistitem[originaltype="loginWithOrigin"] > .login-wrapper > .ac-site-icon {
-  margin-inline-start: auto;
-  margin-inline-end: 4px;
-}
-
-#PopupAutoComplete > richlistbox > richlistitem[originaltype="loginWithOrigin"] > .login-wrapper > .login-text {
-  /* The text should flex while the icon should not */
-  flex: 1;
-  /* width/min-width are needed to get the text-overflow: ellipsis to work for the children */
-  min-width: 0;
-  width: 0;
-}
-
-#PopupAutoComplete > richlistbox > richlistitem[originaltype="loginWithOrigin"] > .login-wrapper > .login-text > .login-row {
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-}
-
-#PopupAutoComplete > richlistbox > richlistitem[originaltype="loginWithOrigin"] > .login-wrapper > .login-text > .login-origin {
-  padding-top: 2px !important;
-  opacity: .6;
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="login"] + richlistitem[originaltype="generatedPassword"],
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="loginWithOrigin"] + richlistitem[originaltype="generatedPassword"] {
+  /* Separator between logins and generated passwords */
+  border-top: 1px solid var(--panel-separator-color);
 }
 
 /* Insecure field warning */
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"] {
   background-color: var(--arrowpanel-dimmed);
   border-bottom: 1px solid var(--panel-separator-color);
   padding-bottom: 4px;
   padding-top: 4px;
--- a/dom/media/Intervals.h
+++ b/dom/media/Intervals.h
@@ -423,30 +423,34 @@ class IntervalSet {
     Add(aInterval);
     return *this;
   }
 
   // Mutate this TimeRange to be the intersection of this and aOther.
   SelfType& Intersection(const SelfType& aOther) {
     ContainerType intersection;
 
+    // Ensure the intersection has enough capacity to store the upper bound on
+    // the intersection size. This ensures that we don't spend time reallocating
+    // the storage as we append, at the expense of extra memory.
+    intersection.SetCapacity(std::min(aOther.Length(), mIntervals.Length()));
+
     const ContainerType& other = aOther.mIntervals;
     IndexType i = 0, j = 0;
     for (; i < mIntervals.Length() && j < other.Length();) {
       if (mIntervals[i].IntersectsStrict(other[j])) {
         intersection.AppendElement(mIntervals[i].Intersection(other[j]));
       }
       if (mIntervals[i].mEnd < other[j].mEnd) {
         i++;
       } else {
         j++;
       }
     }
-    mIntervals.Clear();
-    mIntervals.AppendElements(std::move(intersection));
+    mIntervals = std::move(intersection);
     return *this;
   }
 
   SelfType& Intersection(const ElemType& aInterval) {
     SelfType intervals(aInterval);
     return Intersection(intervals);
   }
 
new file mode 100644
--- /dev/null
+++ b/dom/media/test/crashtests/copyFromChannel-2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Crashtest for bug 1548816</title>
+  <script>
+let cx = new OfflineAudioContext({numberOfChannels: 1,
+                                  length: 1, sampleRate: 44100});
+let buffer = new AudioBuffer({numberOfChannels: 13,
+                              length: 22050, sampleRate: 44100});
+buffer.getChannelData(12)[0] = 1.0;
+let o2248 = new AudioBufferSourceNode(cx, {buffer: buffer});
+let array = new Float32Array(52428);
+buffer.copyFromChannel(array, 12);
+  </script>
+</head>
+</html>
--- a/dom/media/test/crashtests/crashtests.list
+++ b/dom/media/test/crashtests/crashtests.list
@@ -101,16 +101,17 @@ load 1450845.html
 load disconnect-wrong-destination.html
 load analyser-channels-1.html
 skip-if(verify&&isDebugBuild&&gtkWidget) load audiocontext-double-suspend.html
 load buffer-source-duration-1.html
 skip-if(verify&&isDebugBuild&&gtkWidget) load buffer-source-ended-1.html
 load buffer-source-resampling-start-1.html
 load buffer-source-slow-resampling-1.html
 load convolver-memory-report-1.html
+load copyFromChannel-2.html
 skip-if(verify&&isDebugBuild&&gtkWidget) HTTP load media-element-source-seek-1.html
 skip-if(verify&&isDebugBuild&&gtkWidget) load offline-buffer-source-ended-1.html
 load oscillator-ended-1.html
 load oscillator-ended-2.html
 skip-if(Android&&AndroidVersion=='22') load video-replay-after-audio-end.html # bug 1315125, bug 1358876
 # This needs to run at the end to avoid leaking busted state into other tests.
 skip-if(Android) load 691096-1.html # Bug 1365451
 load 1236639.html
--- a/js/public/CompilationAndEvaluation.h
+++ b/js/public/CompilationAndEvaluation.h
@@ -161,16 +161,35 @@ extern JS_PUBLIC_API bool EvaluateDontIn
  * [necessarily] UTF-8.)  If the contents contain any malformed UTF-8, an error
  * is reported.
  */
 extern JS_PUBLIC_API bool EvaluateUtf8Path(
     JSContext* cx, const ReadOnlyCompileOptions& options, const char* filename,
     MutableHandle<Value> rval);
 
 /**
+ * Evaluate the UTF-8 contents of the file at the given path, and return the
+ * completion value in |rval|.  (The path itself is in the system encoding, not
+ * [necessarily] UTF-8.)  If the contents contain any malformed UTF-8, an error
+ * is reported.
+ *
+ * The "DontInflate" suffix and (semantically unobservable) don't-inflate
+ * characteristic are temporary while bugs in UTF-8 compilation are ironed out.
+ * In the long term |JS::EvaluateUtf8Path| will just never inflate, and this
+ * separate function will die.
+ *
+ * NOTE: UTF-8 compilation is currently experimental, and it's possible it has
+ *       as-yet-undiscovered bugs that the UTF-16 compilation functions do not
+ *       have.  Use only if you're willing to take a risk!
+ */
+extern JS_PUBLIC_API bool EvaluateUtf8PathDontInflate(
+    JSContext* cx, const ReadOnlyCompileOptions& options, const char* filename,
+    MutableHandle<Value> rval);
+
+/**
  * Compile the provided script using the given options.  Return the script on
  * success, or return null on failure (usually with an error reported).
  */
 extern JS_PUBLIC_API JSScript* Compile(JSContext* cx,
                                        const ReadOnlyCompileOptions& options,
                                        SourceText<char16_t>& srcBuf);
 
 /**
@@ -222,16 +241,29 @@ extern JS_PUBLIC_API JSScript* CompileUt
  * Compile the UTF-8 contents of the file at the given path into a script.
  * (The path itself is in the system encoding, not [necessarily] UTF-8.)  It
  * is an error if the file's contents are invalid UTF-8.  Return the script on
  * success, or return null on failure (usually with an error reported).
  */
 extern JS_PUBLIC_API JSScript* CompileUtf8Path(
     JSContext* cx, const ReadOnlyCompileOptions& options, const char* filename);
 
+/**
+ * Compile the UTF-8 contents of the file at the given path into a script.
+ * (The path itself is in the system encoding, not [necessarily] UTF-8.)  It
+ * is an error if the file's contents are invalid UTF-8.  Return the script on
+ * success, or return null on failure (usually with an error reported).
+ *
+ * NOTE: UTF-8 compilation is currently experimental, and it's possible it has
+ *       as-yet-undiscovered bugs not present in |JS::CompileUtf8Path| that
+ *       first inflates to UTF-16.  Use only if you're willing to take a risk!
+ */
+extern JS_PUBLIC_API JSScript* CompileUtf8PathDontInflate(
+    JSContext* cx, const ReadOnlyCompileOptions& options, const char* filename);
+
 extern JS_PUBLIC_API JSScript* CompileForNonSyntacticScope(
     JSContext* cx, const ReadOnlyCompileOptions& options,
     SourceText<char16_t>& srcBuf);
 
 /**
  * Compile the provided UTF-8 data into a script in a non-syntactic scope.  It
  * is an error if the data contains invalid UTF-8.  Return the script on
  * success, or return null on failure (usually with an error reported).
--- a/js/src/gdb/tests/test-asmjs.cpp
+++ b/js/src/gdb/tests/test-asmjs.cpp
@@ -31,15 +31,15 @@ FRAGMENT(asmjs, segfault) {
   opts.setFileAndLine(__FILE__, line0 + 1);
   opts.asmJSOption = JS::AsmJSOption::Enabled;
 
   JS::SourceText<mozilla::Utf8Unit> srcBuf;
   JS::Rooted<JS::Value> rval(cx);
 
   bool ok = srcBuf.init(cx, chars, mozilla::ArrayLength(chars) - 1,
                         JS::SourceOwnership::Borrowed) &&
-            JS::Evaluate(cx, opts, srcBuf, &rval);
+            JS::EvaluateDontInflate(cx, opts, srcBuf, &rval);
 
   breakpoint();
 
   use(ok);
   use(rval);
 }
--- a/js/src/gdb/tests/test-unwind.cpp
+++ b/js/src/gdb/tests/test-unwind.cpp
@@ -1,14 +1,14 @@
 #include "gdb-tests.h"
 #include "jsapi.h"  // sundry symbols not moved to more-specific headers yet
 
 #include "jit/JitOptions.h"               // js::jit::JitOptions
 #include "js/CallArgs.h"                  // JS::CallArgs, JS::CallArgsFromVp
-#include "js/CompilationAndEvaluation.h"  // JS::Evaluate
+#include "js/CompilationAndEvaluation.h"  // JS::EvaluateDontInflate
 #include "js/CompileOptions.h"            // JS::CompileOptions
 #include "js/RootingAPI.h"                // JS::Rooted
 #include "js/SourceText.h"                // JS::Source{Ownership,Text}
 #include "js/Value.h"                     // JS::Value
 
 #include "mozilla/Utf8.h"  // mozilla::Utf8Unit
 
 #include <stdint.h>  // uint32_t
@@ -59,12 +59,12 @@ FRAGMENT(unwind, simple) {
   opts.setFileAndLine(__FILE__, line0 + 1);
 
   JS::SourceText<mozilla::Utf8Unit> srcBuf;
   if (!srcBuf.init(cx, bytes, strlen(bytes), JS::SourceOwnership::Borrowed)) {
     return;
   }
 
   JS::Rooted<JS::Value> rval(cx);
-  JS::Evaluate(cx, opts, srcBuf, &rval);
+  JS::EvaluateDontInflate(cx, opts, srcBuf, &rval);
 
   js::jit::JitOptions.baselineWarmUpThreshold = saveThreshold;
 }
--- a/js/src/jsapi-tests/testCallArgs.cpp
+++ b/js/src/jsapi-tests/testCallArgs.cpp
@@ -1,15 +1,15 @@
 /* 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 "mozilla/Utf8.h"  // mozilla::Utf8Unit
 
-#include "js/CompilationAndEvaluation.h"  // JS::Evaluate
+#include "js/CompilationAndEvaluation.h"  // JS::EvaluateDontInflate
 #include "js/SourceText.h"                // JS::Source{Ownership,Text}
 #include "jsapi-tests/tests.h"
 
 static bool CustomNative(JSContext* cx, unsigned argc, JS::Value* vp) {
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
 
   MOZ_RELEASE_ASSERT(!JS_IsExceptionPending(cx));
 
@@ -27,17 +27,17 @@ BEGIN_TEST(testCallArgs_isConstructing_n
   opts.setFileAndLine(__FILE__, __LINE__ + 4);
 
   JS::RootedValue result(cx);
 
   static const char code[] = "new customNative();";
   JS::SourceText<mozilla::Utf8Unit> srcBuf;
   CHECK(srcBuf.init(cx, code, strlen(code), JS::SourceOwnership::Borrowed));
 
-  CHECK(!JS::Evaluate(cx, opts, srcBuf, &result));
+  CHECK(!JS::EvaluateDontInflate(cx, opts, srcBuf, &result));
 
   CHECK(JS_IsExceptionPending(cx));
   JS_ClearPendingException(cx);
 
   EVAL("customNative();", &result);
   CHECK(result.isUndefined());
 
   return true;
--- a/js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp
+++ b/js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp
@@ -3,17 +3,17 @@
  */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/ArrayUtils.h"  // mozilla::ArrayLength
 #include "mozilla/Utf8.h"        // mozilla::Utf8Unit
 
-#include "js/CompilationAndEvaluation.h"  // JS::Evaluate
+#include "js/CompilationAndEvaluation.h"  // JS::EvaluateDontInflate
 #include "js/SourceText.h"                // JS::Source{Ownership,Text}
 #include "jsapi-tests/tests.h"
 
 static bool GlobalResolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
                           bool* resolvedp) {
   return JS_ResolveStandardClass(cx, obj, id, resolvedp);
 }
 
@@ -49,13 +49,14 @@ BEGIN_TEST(testRedefineGlobalEval) {
       "Object.defineProperty(this, 'eval', { configurable: false });";
 
   JS::CompileOptions opts(cx);
 
   JS::SourceText<mozilla::Utf8Unit> srcBuf;
   CHECK(srcBuf.init(cx, data, mozilla::ArrayLength(data) - 1,
                     JS::SourceOwnership::Borrowed));
 
-  CHECK(JS::Evaluate(cx, opts.setFileAndLine(__FILE__, __LINE__), srcBuf, &v));
+  CHECK(JS::EvaluateDontInflate(cx, opts.setFileAndLine(__FILE__, __LINE__),
+                                srcBuf, &v));
 
   return true;
 }
 END_TEST(testRedefineGlobalEval)
--- a/js/src/jsapi-tests/testGCOutOfMemory.cpp
+++ b/js/src/jsapi-tests/testGCOutOfMemory.cpp
@@ -3,17 +3,17 @@
  *
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/
  * Contributor: Igor Bukanov
  */
 
 #include "mozilla/Utf8.h"  // mozilla::Utf8Unit
 
-#include "js/CompilationAndEvaluation.h"  // JS::Evaluate
+#include "js/CompilationAndEvaluation.h"  // JS::EvaluateDontInflate
 #include "js/SourceText.h"                // JS::Source{Ownership,Text}
 #include "jsapi-tests/tests.h"
 
 BEGIN_TEST(testGCOutOfMemory) {
   // Count the number of allocations until we hit OOM, and store it in 'max'.
   static const char source[] =
       "var max = 0; (function() {"
       "    var array = [];"
@@ -23,17 +23,17 @@ BEGIN_TEST(testGCOutOfMemory) {
       "})();";
 
   JS::CompileOptions opts(cx);
 
   JS::SourceText<mozilla::Utf8Unit> srcBuf;
   CHECK(srcBuf.init(cx, source, strlen(source), JS::SourceOwnership::Borrowed));
 
   JS::RootedValue root(cx);
-  bool ok = JS::Evaluate(cx, opts, srcBuf, &root);
+  bool ok = JS::EvaluateDontInflate(cx, opts, srcBuf, &root);
 
   /* Check that we get OOM. */
   CHECK(!ok);
   CHECK(JS_GetPendingException(cx, &root));
   CHECK(root.isString());
   bool match = false;
   CHECK(JS_StringEqualsAscii(cx, root.toString(), "out of memory", &match));
   CHECK(match);
--- a/js/src/jsapi-tests/testSavedStacks.cpp
+++ b/js/src/jsapi-tests/testSavedStacks.cpp
@@ -6,17 +6,17 @@
 
 #include "mozilla/ArrayUtils.h"  // mozilla::ArrayLength
 #include "mozilla/Utf8.h"        // mozilla::Utf8Unit
 
 #include "jsfriendapi.h"
 #include "builtin/String.h"
 
 #include "builtin/TestingFunctions.h"
-#include "js/CompilationAndEvaluation.h"  // JS::Evaluate
+#include "js/CompilationAndEvaluation.h"  // JS::EvaluateDontInflate
 #include "js/SavedFrameAPI.h"
 #include "js/SourceText.h"  // JS::Source{Ownership,Text}
 #include "jsapi-tests/tests.h"
 #include "vm/ArrayObject.h"
 #include "vm/Realm.h"
 #include "vm/SavedStacks.h"
 
 BEGIN_TEST(testSavedStacks_withNoStack) {
@@ -317,17 +317,17 @@ BEGIN_TEST(test_JS_GetPendingExceptionSt
   JS::CompileOptions opts(cx);
   opts.setFileAndLine("filename.js", 1U);
 
   JS::SourceText<mozilla::Utf8Unit> srcBuf;
   CHECK(srcBuf.init(cx, sourceText, mozilla::ArrayLength(sourceText) - 1,
                     JS::SourceOwnership::Borrowed));
 
   JS::RootedValue val(cx);
-  bool ok = JS::Evaluate(cx, opts, srcBuf, &val);
+  bool ok = JS::EvaluateDontInflate(cx, opts, srcBuf, &val);
 
   CHECK(!ok);
   CHECK(JS_IsExceptionPending(cx));
   CHECK(val.isUndefined());
 
   JS::RootedObject stack(cx, JS::GetPendingExceptionStack(cx));
   CHECK(stack);
   CHECK(stack->is<js::SavedFrame>());
--- a/js/src/jsapi-tests/testSourcePolicy.cpp
+++ b/js/src/jsapi-tests/testSourcePolicy.cpp
@@ -1,15 +1,15 @@
 /* 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 "mozilla/Utf8.h"  // mozilla::Utf8Unit
 
-#include "js/CompilationAndEvaluation.h"  // JS::CompileFunction
+#include "js/CompilationAndEvaluation.h"  // JS::CompileFunction, JS::EvaluateDontInflate
 #include "js/MemoryFunctions.h"
 #include "js/SourceText.h"  // JS::Source{Ownership,Text}
 #include "jsapi-tests/tests.h"
 #include "vm/JSScript.h"
 
 BEGIN_TEST(testBug795104) {
   JS::RealmBehaviorsRef(cx->realm()).setDiscardSource(true);
 
@@ -21,21 +21,21 @@ BEGIN_TEST(testBug795104) {
   memset(s + 1, 'x', strLen - 2);
   s[strLen - 1] = '"';
 
   JS::SourceText<mozilla::Utf8Unit> srcBuf;
   CHECK(srcBuf.init(cx, s, strLen, JS::SourceOwnership::Borrowed));
 
   JS::CompileOptions opts(cx);
 
-  // We don't want an rval for our Evaluate call
+  // We don't want an rval for our JS::EvaluateDontInflate call
   opts.setNoScriptRval(true);
 
   JS::RootedValue unused(cx);
-  CHECK(JS::Evaluate(cx, opts, srcBuf, &unused));
+  CHECK(JS::EvaluateDontInflate(cx, opts, srcBuf, &unused));
 
   JS::RootedFunction fun(cx);
   JS::RootedObjectVector emptyScopeChain(cx);
 
   // But when compiling a function we don't want to use no-rval
   // mode, since it's not supported for functions.
   opts.setNoScriptRval(false);
 
--- a/js/src/jsapi-tests/tests.cpp
+++ b/js/src/jsapi-tests/tests.cpp
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jsapi-tests/tests.h"
 
 #include "mozilla/Utf8.h"  // mozilla::Utf8Unit
 
 #include <stdio.h>
 
-#include "js/CompilationAndEvaluation.h"  // JS::Evaluate
+#include "js/CompilationAndEvaluation.h"  // JS::EvaluateDontInflate
 #include "js/Initialization.h"
 #include "js/RootingAPI.h"
 #include "js/SourceText.h"  // JS::Source{Ownership,Text}
 
 JSAPITest* JSAPITest::list;
 
 bool JSAPITest::init() {
   cx = createContext();
@@ -49,39 +49,39 @@ void JSAPITest::uninit() {
 
 bool JSAPITest::exec(const char* utf8, const char* filename, int lineno) {
   JS::CompileOptions opts(cx);
   opts.setFileAndLine(filename, lineno);
 
   JS::SourceText<mozilla::Utf8Unit> srcBuf;
   JS::RootedValue v(cx);
   return (srcBuf.init(cx, utf8, strlen(utf8), JS::SourceOwnership::Borrowed) &&
-          JS::Evaluate(cx, opts, srcBuf, &v)) ||
+          JS::EvaluateDontInflate(cx, opts, srcBuf, &v)) ||
          fail(JSAPITestString(utf8), filename, lineno);
 }
 
 bool JSAPITest::execDontReport(const char* utf8, const char* filename,
                                int lineno) {
   JS::CompileOptions opts(cx);
   opts.setFileAndLine(filename, lineno);
 
   JS::SourceText<mozilla::Utf8Unit> srcBuf;
   JS::RootedValue v(cx);
   return srcBuf.init(cx, utf8, strlen(utf8), JS::SourceOwnership::Borrowed) &&
-         JS::Evaluate(cx, opts, srcBuf, &v);
+         JS::EvaluateDontInflate(cx, opts, srcBuf, &v);
 }
 
 bool JSAPITest::evaluate(const char* utf8, const char* filename, int lineno,
                          JS::MutableHandleValue vp) {
   JS::CompileOptions opts(cx);
   opts.setFileAndLine(filename, lineno);
 
   JS::SourceText<mozilla::Utf8Unit> srcBuf;
   return (srcBuf.init(cx, utf8, strlen(utf8), JS::SourceOwnership::Borrowed) &&
-          JS::Evaluate(cx, opts, srcBuf, vp)) ||
+          JS::EvaluateDontInflate(cx, opts, srcBuf, vp)) ||
          fail(JSAPITestString(utf8), filename, lineno);
 }
 
 bool JSAPITest::definePrint() {
   return JS_DefineFunction(cx, global, "print", (JSNative)print, 0, 0);
 }
 
 JSObject* JSAPITest::createGlobal(JSPrincipals* principals) {
--- a/js/src/vm/CompilationAndEvaluation.cpp
+++ b/js/src/vm/CompilationAndEvaluation.cpp
@@ -146,16 +146,29 @@ JSScript* JS::CompileUtf8Path(JSContext*
                               const char* filename) {
   AutoFile file;
   if (!file.open(cx, filename)) {
     return nullptr;
   }
 
   CompileOptions options(cx, optionsArg);
   options.setFileAndLine(filename, 1);
+  return CompileUtf8File(cx, options, file.fp());
+}
+
+JSScript* JS::CompileUtf8PathDontInflate(
+    JSContext* cx, const ReadOnlyCompileOptions& optionsArg,
+    const char* filename) {
+  AutoFile file;
+  if (!file.open(cx, filename)) {
+    return nullptr;
+  }
+
+  CompileOptions options(cx, optionsArg);
+  options.setFileAndLine(filename, 1);
   return CompileUtf8FileDontInflate(cx, options, file.fp());
 }
 
 JSScript* JS::CompileForNonSyntacticScope(
     JSContext* cx, const ReadOnlyCompileOptions& optionsArg,
     SourceText<char16_t>& srcBuf) {
   CompileOptions options(cx, optionsArg);
   options.setNonSyntacticScope(true);
@@ -614,8 +627,33 @@ JS_PUBLIC_API bool JS::EvaluateUtf8Path(
 
   JS::SourceText<Utf8Unit> srcBuf;
   if (!srcBuf.init(cx, contents, length, JS::SourceOwnership::Borrowed)) {
     return false;
   }
 
   return Evaluate(cx, options, srcBuf, rval);
 }
+
+JS_PUBLIC_API bool JS::EvaluateUtf8PathDontInflate(
+    JSContext* cx, const ReadOnlyCompileOptions& optionsArg,
+    const char* filename, MutableHandleValue rval) {
+  FileContents buffer(cx);
+  {
+    AutoFile file;
+    if (!file.open(cx, filename) || !file.readAll(cx, buffer)) {
+      return false;
+    }
+  }
+
+  CompileOptions options(cx, optionsArg);
+  options.setFileAndLine(filename, 1);
+
+  auto contents = reinterpret_cast<const char*>(buffer.begin());
+  size_t length = buffer.length();
+
+  JS::SourceText<Utf8Unit> srcBuf;
+  if (!srcBuf.init(cx, contents, length, JS::SourceOwnership::Borrowed)) {
+    return false;
+  }
+
+  return EvaluateDontInflate(cx, options, srcBuf, rval);
+}
--- a/layout/base/OverflowChangedTracker.h
+++ b/layout/base/OverflowChangedTracker.h
@@ -134,26 +134,16 @@ class OverflowChangedTracker {
         overflowChanged = true;
       }
 
       // If the frame style changed (e.g. positioning offsets)
       // then we need to update the parent with the overflow areas of its
       // children.
       if (overflowChanged) {
         nsIFrame* parent = frame->GetParent();
-        while (parent && parent != mSubtreeRoot &&
-               parent->FrameMaintainsOverflow() &&
-               parent->Combines3DTransformWithAncestors()) {
-          // Passing frames in between the frame and the establisher of
-          // 3D rendering context.
-          parent = parent->GetParent();
-          MOZ_ASSERT(parent,
-                     "Root frame should never return true for "
-                     "Combines3DTransformWithAncestors");
-        }
 
         // It's possible that the parent is already in a nondisplay context,
         // should not add it to the list if that's true.
         if (parent && parent != mSubtreeRoot &&
             parent->FrameMaintainsOverflow()) {
           Entry* parentEntry =
               mEntryList.find(Entry(parent, entry->mDepth - 1));
           if (parentEntry) {
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -7394,16 +7394,19 @@ nsRect nsIFrame::GetPreEffectsVisualOver
 bool nsIFrame::UpdateOverflow() {
   MOZ_ASSERT(FrameMaintainsOverflow(),
              "Non-display SVG do not maintain visual overflow rects");
 
   nsRect rect(nsPoint(0, 0), GetSize());
   nsOverflowAreas overflowAreas(rect, rect);
 
   if (!ComputeCustomOverflow(overflowAreas)) {
+    // If updating overflow wasn't supported by this frame, then it should
+    // have scheduled any necessary reflows. We can return false to say nothing
+    // changed, and wait for reflow to correct it.
     return false;
   }
 
   UnionChildOverflow(overflowAreas);
 
   if (FinishAndStoreOverflow(overflowAreas, GetSize())) {
     nsView* view = GetView();
     if (view) {
@@ -7414,17 +7417,22 @@ bool nsIFrame::UpdateOverflow() {
         nsViewManager* vm = view->GetViewManager();
         vm->ResizeView(view, overflowAreas.VisualOverflow(), true);
       }
     }
 
     return true;
   }
 
-  return false;
+  // Frames that combine their 3d transform with their ancestors
+  // only compute a pre-transform overflow rect, and then contribute
+  // to the normal overflow rect of the preserve-3d root. Always return
+  // true here so that we propagate changes up to the root for final
+  // calculation.
+  return Combines3DTransformWithAncestors();
 }
 
 /* virtual */
 bool nsFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) {
   return true;
 }
 
 /* virtual */
new file mode 100644
--- /dev/null
+++ b/layout/painting/crashtests/1551389-1.html
@@ -0,0 +1,6 @@
+<style>
+dl::first-letter { float: right }
+* { -webkit-box-shadow: -moz-cellhighlighttext 0px 34px 1px }
+</style>
+<s dir="RTL">
+<dl style="break-inside: avoid">AA</iframe>
--- a/layout/painting/crashtests/crashtests.list
+++ b/layout/painting/crashtests/crashtests.list
@@ -14,9 +14,10 @@ load 1455944-1.html
 load 1465305-1.html
 load 1468124-1.html
 load 1469472.html
 load 1477831-1.html
 load 1504033.html
 load 1514544-1.html
 load 1547420-1.html
 load 1549909.html
+asserts(6) load 1551389-1.html # bug 847368
 
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -384,17 +384,17 @@ struct InlineBackgroundData {
       bool isValid1, isValid2;
       nsBlockInFlowLineIterator it1(blockFrame, aFrame1, &isValid1);
       nsBlockInFlowLineIterator it2(blockFrame, aFrame2, &isValid2);
       return isValid1 && isValid2 &&
              // Make sure aFrame1 and aFrame2 are in the same continuation of
              // blockFrame.
              it1.GetContainer() == it2.GetContainer() &&
              // And on the same line in it
-             it1.GetLine() == it2.GetLine();
+             it1.GetLine().get() == it2.GetLine().get();
     }
     if (nsRubyTextContainerFrame* rtcFrame = do_QueryFrame(mLineContainer)) {
       nsBlockFrame* block = nsLayoutUtils::FindNearestBlockAncestor(rtcFrame);
       // Ruby text container can only hold one line of text, so if they
       // are in the same continuation, they are in the same line. Since
       // ruby text containers are bidi isolate, they are never split for
       // bidi reordering, which means being in different continuation
       // indicates being in different lines.
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform-3d/1544995-1-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+  .scene {
+    transform-style: preserve-3d;
+  }
+  .hidden {
+    backface-visibility: hidden;
+  }
+  #inner {
+    width: 20px;
+    height: 20px;
+    box-shadow: rgb(255, 205, 31) 0px 0px 0px 3px;
+  }
+</style>
+</head>
+<body>
+<div class="scene">
+    <div class="hidden">
+        <div id="inner">
+        </div>
+    </div>
+</div
+</body>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform-3d/1544995-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<style>
+  .scene {
+    transform-style: preserve-3d;
+  }
+  .hidden {
+    backface-visibility: hidden;
+  }
+  #inner {
+    width: 20px;
+    height: 20px;
+  }
+</style>
+<script type="text/javascript">
+  function doTest() {
+    document.getElementById("inner").style.boxShadow = "rgb(255, 205, 31) 0px 0px 0px 3px";
+    document.documentElement.removeAttribute('class');
+  }
+  window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</head>
+<body>
+<div class="scene">
+    <div class="hidden">
+        <div id="inner">
+        </div>
+    </div>
+</div
+</body>
--- a/layout/reftests/transform-3d/reftest.list
+++ b/layout/reftests/transform-3d/reftest.list
@@ -89,8 +89,9 @@ fuzzy(0-255,0-150) == split-intersect2.h
 fuzzy(0-255,0-100) == split-non-ortho1.html split-non-ortho1-ref.html
 fuzzy-if(winWidget,0-150,0-120) == component-alpha-1.html component-alpha-1-ref.html
 == nested-transform-1.html nested-transform-1-ref.html
 == transform-geometry-1.html transform-geometry-1-ref.html
 == intermediate-1.html intermediate-1-ref.html
 == preserves3d-nested-filter-1.html preserves3d-nested-filter-1-ref.html
 != preserve3d-scale.html about:blank
 == perspective-overflow-1.html perspective-overflow-1-ref.html
+== 1544995-1.html 1544995-1-ref.html
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4719,16 +4719,18 @@ pref("font.name-list.monospace.x-unicode
 // Login Manager prefs
 pref("signon.rememberSignons",              true);
 pref("signon.rememberSignons.visibilityToggle", true);
 pref("signon.autofillForms",                true);
 pref("signon.autofillForms.autocompleteOff", true);
 pref("signon.autofillForms.http",           false);
 pref("signon.autologin.proxy",              false);
 pref("signon.formlessCapture.enabled",      true);
+pref("signon.generation.available",         false);
+pref("signon.generation.enabled",           false);
 pref("signon.privateBrowsingCapture.enabled", false);
 pref("signon.storeWhenAutocompleteOff",     true);
 pref("signon.debug",                        false);
 pref("signon.recipes.path",                 "chrome://passwordmgr/content/recipes.json");
 pref("signon.schemeUpgrades",               false);
 // This temporarily prevents the master password to reprompt for autocomplete.
 pref("signon.masterPasswordReprompt.timeout_ms", 900000); // 15 Minutes
 pref("signon.showAutoCompleteFooter", false);
--- a/testing/mozbase/mozrunner/mozrunner/devices/android_device.py
+++ b/testing/mozbase/mozrunner/mozrunner/devices/android_device.py
@@ -506,18 +506,19 @@ class AndroidEmulator(object):
                 self.gpu = False
 
         env = os.environ
         env['ANDROID_AVD_HOME'] = os.path.join(EMULATOR_HOME_DIR, "avd")
         command = [self.emulator_path, "-avd", self.avd_info.name]
         if self.gpu:
             command += ['-gpu', 'swiftshader_indirect']
         if self.avd_info.extra_args:
-            # -enable-kvm option is not valid on OSX
-            if _get_host_platform() == 'macosx64' and '-enable-kvm' in self.avd_info.extra_args:
+            # -enable-kvm option is not valid on OSX and Windows
+            if _get_host_platform() in ('macosx64', 'win32') and \
+               '-enable-kvm' in self.avd_info.extra_args:
                 self.avd_info.extra_args.remove('-enable-kvm')
             command += self.avd_info.extra_args
         log_path = os.path.join(EMULATOR_HOME_DIR, 'emulator.log')
         self.emulator_log = open(log_path, 'w')
         _log_debug("Starting the emulator with this command: %s" %
                    ' '.join(command))
         _log_debug("Emulator output will be written to '%s'" %
                    log_path)
@@ -835,16 +836,18 @@ def _tooltool_fetch():
         if proc.poll() is None:
             proc.kill(signal.SIGTERM)
 
 
 def _get_host_platform():
     plat = None
     if 'darwin' in str(sys.platform).lower():
         plat = 'macosx64'
+    elif 'win32' in str(sys.platform).lower():
+        plat = 'win32'
     elif 'linux' in str(sys.platform).lower():
         if '64' in platform.architecture()[0]:
             plat = 'linux64'
         else:
             plat = 'linux32'
     return plat
 
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-multicol/multicol-fill-auto-block-children-003.html.ini
@@ -0,0 +1,2 @@
+[multicol-fill-auto-block-children-003.html]
+  prefs: [layout.css.column-span.enabled:true]
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-multicol/multicol-fill-auto-block-children-003-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>CSS Multi-column Layout Test Reference: 'column-fill: auto' and height constrained of a multi-column container</title>
+  <link rel="author" title="Ting-Yu Lin" href="tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+
+  <style>
+  article {
+    column-fill: auto;
+    column-count: 2;
+    width: 200px;
+    height: 200px;
+  }
+  div {
+    height: 400px;
+    background-color: lightgreen;
+  }
+  </style>
+
+  <p>This test passes if you see two green strips with equal height.</p>
+  <article>
+    <div></div>
+  </article>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-multicol/multicol-fill-auto-block-children-003.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>CSS Multi-column Layout Test: 'column-fill: auto' and height constrained of a multi-column container</title>
+  <link rel="author" title="Ting-Yu Lin" href="tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-multicol/#cf">
+  <link rel="match" href="multicol-fill-auto-block-children-003-ref.html">
+  <meta name="assert" content="This test verifies that 'max-height' on multi-column container imposes constraint on column boxes' height.">
+
+  <style>
+  article {
+    column-fill: auto;
+    column-count: 2;
+    width: 200px;
+    /* Test max-height imposes constraint on column boxes' height. */
+    max-height: 200px;
+  }
+  div {
+    height: 400px;
+    background-color: lightgreen;
+  }
+  </style>
+
+  <p>This test passes if you see two green strips with equal height.</p>
+  <article>
+    <div></div>
+  </article>
+</html>
--- a/toolkit/components/passwordmgr/LoginAutoCompleteResult.jsm
+++ b/toolkit/components/passwordmgr/LoginAutoCompleteResult.jsm
@@ -117,49 +117,65 @@ class LoginAutocompleteItem extends Auto
       return username;
     });
 
     XPCOMUtils.defineLazyGetter(this, "value", () => {
       return isPasswordField ? login.password : login.username;
     });
 
     XPCOMUtils.defineLazyGetter(this, "comment", () => {
-      return JSON.stringify({
-        loginOrigin: login.hostname,
-      });
+      try {
+        let uri = Services.io.newURI(login.hostname);
+        // Fallback to handle file: URIs
+        return uri.displayHostPort || login.hostname;
+      } catch (ex) {
+        // Fallback to origin below
+      }
+      return login.hostname;
     });
   }
 
   removeFromStorage() {
     if (this._messageManager) {
       let vanilla = LoginHelper.loginToVanillaObject(this._login);
       this._messageManager.sendAsyncMessage("PasswordManager:removeLogin",
                                             { login: vanilla });
     } else {
       Services.logins.removeLogin(this._login);
     }
   }
 }
 
+class GeneratedPasswordAutocompleteItem extends AutocompleteItem {
+  constructor(generatedPassword) {
+    super("generatedPassword");
+    this.comment = generatedPassword;
+    this.value = generatedPassword;
+
+    XPCOMUtils.defineLazyGetter(this, "label", () => {
+      return getLocalizedString("useGeneratedPassword");
+    });
+  }
+}
+
 class LoginsFooterAutocompleteItem extends AutocompleteItem {
   constructor(hostname) {
     super("loginsFooter");
+    this.comment = hostname;
 
     XPCOMUtils.defineLazyGetter(this, "label", () => {
-      return JSON.stringify({
-        label: getLocalizedString("viewSavedLogins.label"),
-        hostname,
-      });
+      return getLocalizedString("viewSavedLogins.label");
     });
   }
 }
 
 
 // nsIAutoCompleteResult implementation
 function LoginAutoCompleteResult(aSearchString, matchingLogins, {
+  generatedPassword,
   isSecure,
   messageManager,
   isPasswordField,
   hostname,
 }) {
   let hidingFooterOnPWFieldAutoOpened = false;
   function isFooterEnabled() {
     // We need to check LoginHelper.enabled here since the insecure warning should
@@ -170,17 +186,18 @@ function LoginAutoCompleteResult(aSearch
 
     // Don't show the footer on non-empty password fields as it's not providing
     // value and only adding noise since a password was already filled.
     if (isPasswordField && aSearchString) {
       log.debug("Hiding footer: non-empty password field");
       return false;
     }
 
-    if (!matchingLogins.length && isPasswordField && formFillController.passwordPopupAutomaticallyOpened) {
+    if (!matchingLogins.length && !generatedPassword && isPasswordField
+        && formFillController.passwordPopupAutomaticallyOpened) {
       hidingFooterOnPWFieldAutoOpened = true;
       log.debug("Hiding footer: no logins and the popup was opened upon focus of the pw. field");
       return false;
     }
 
     return true;
   }
 
@@ -202,16 +219,19 @@ function LoginAutoCompleteResult(aSearch
   for (let login of logins) {
     let item = new LoginAutocompleteItem(login, isPasswordField, dateAndTimeFormatter,
                                          duplicateUsernames, messageManager);
     this._rows.push(item);
   }
 
   // The footer comes last if it's enabled
   if (isFooterEnabled()) {
+    if (generatedPassword) {
+      this._rows.push(new GeneratedPasswordAutocompleteItem(generatedPassword));
+    }
     this._rows.push(new LoginsFooterAutocompleteItem(hostname));
   }
 
   // Determine the result code and default index.
   if (this.matchCount > 0) {
     this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
     this.defaultIndex = 0;
   } else if (hidingFooterOnPWFieldAutoOpened) {
@@ -339,25 +359,30 @@ LoginAutoComplete.prototype = {
     //   able to affect the identity icon in the address bar by adding a password field.
     if (isSecure) {
       let form = LoginFormFactory.createFromField(aElement);
       isSecure = InsecurePasswordUtils.isFormSecure(form);
     }
     let isPasswordField = aElement.type == "password";
     let hostname = aElement.ownerDocument.documentURIObject.host;
 
-    let completeSearch = (autoCompleteLookupPromise, { logins, messageManager }) => {
+    let completeSearch = (autoCompleteLookupPromise, {
+      generatedPassword,
+      logins,
+      messageManager,
+    }) => {
       // If the search was canceled before we got our
       // results, don't bother reporting them.
       if (this._autoCompleteLookupPromise !== autoCompleteLookupPromise) {
         return;
       }
 
       this._autoCompleteLookupPromise = null;
       let results = new LoginAutoCompleteResult(aSearchString, logins, {
+        generatedPassword,
         messageManager,
         isSecure,
         isPasswordField,
         hostname,
       });
       aCallback.onSearchCompletion(results);
     };
 
@@ -389,19 +414,17 @@ LoginAutoComplete.prototype = {
       previousResult = {
         searchString: aPreviousResult.searchString,
         logins: aPreviousResult.wrappedJSObject.logins,
       };
     } else {
       previousResult = null;
     }
 
-    let rect = BrowserUtils.getElementBoundingScreenRect(aElement);
     let acLookupPromise = this._autoCompleteLookupPromise =
-      LoginManagerContent._autoCompleteSearchAsync(aSearchString, previousResult,
-                                                   aElement, rect);
+      LoginManagerContent._autoCompleteSearchAsync(aSearchString, previousResult, aElement);
     acLookupPromise.then(completeSearch.bind(this, acLookupPromise)).catch(log.error);
   },
 
   stopSearch() {
     this._autoCompleteLookupPromise = null;
   },
 };
--- a/toolkit/components/passwordmgr/LoginHelper.jsm
+++ b/toolkit/components/passwordmgr/LoginHelper.jsm
@@ -23,16 +23,18 @@ const {XPCOMUtils} = ChromeUtils.import(
 
 /**
  * Contains functions shared by different Login Manager components.
  */
 var LoginHelper = {
   debug: null,
   enabled: null,
   formlessCaptureEnabled: null,
+  generationAvailable: null,
+  generationEnabled: null,
   insecureAutofill: null,
   managementURI: null,
   privateBrowsingCaptureEnabled: null,
   schemeUpgrades: null,
   showAutoCompleteFooter: null,
 
   init() {
     // Watch for pref changes to update cached pref values.
@@ -42,16 +44,18 @@ var LoginHelper = {
   },
 
   updateSignonPrefs() {
     this.autofillForms = Services.prefs.getBoolPref("signon.autofillForms");
     this.autofillAutocompleteOff = Services.prefs.getBoolPref("signon.autofillForms.autocompleteOff");
     this.debug = Services.prefs.getBoolPref("signon.debug");
     this.enabled = Services.prefs.getBoolPref("signon.rememberSignons");
     this.formlessCaptureEnabled = Services.prefs.getBoolPref("signon.formlessCapture.enabled");
+    this.generationAvailable = Services.prefs.getBoolPref("signon.generation.available");
+    this.generationEnabled = Services.prefs.getBoolPref("signon.generation.enabled");
     this.insecureAutofill = Services.prefs.getBoolPref("signon.autofillForms.http");
     this.managementURI = Services.prefs.getStringPref("signon.management.overrideURI", null);
     this.privateBrowsingCaptureEnabled =
       Services.prefs.getBoolPref("signon.privateBrowsingCapture.enabled");
     this.schemeUpgrades = Services.prefs.getBoolPref("signon.schemeUpgrades");
     this.showAutoCompleteFooter = Services.prefs.getBoolPref("signon.showAutoCompleteFooter");
     this.storeWhenAutocompleteOff = Services.prefs.getBoolPref("signon.storeWhenAutocompleteOff");
   },
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -288,17 +288,21 @@ var LoginManagerContent = {
         });
         break;
       }
 
       case "PasswordManager:loginsAutoCompleted": {
         let loginsFound = LoginHelper.vanillaObjectsToLogins(msg.data.logins);
         let messageManager = msg.target;
         let request = this._takeRequest(msg);
-        request.promise.resolve({ logins: loginsFound, messageManager });
+        request.promise.resolve({
+          generatedPassword: msg.data.generatedPassword,
+          logins: loginsFound,
+          messageManager,
+        });
         break;
       }
 
       case "FormAutoComplete:PopupOpened": {
         let {chromeEventHandler} = msg.target.docShell;
         chromeEventHandler.addEventListener("keydown", this._onKeyDown,
                                             true);
         break;
@@ -340,39 +344,42 @@ var LoginManagerContent = {
                         options };
 
     return this._sendRequest(messageManager, requestData,
                              "PasswordManager:findLogins",
                              messageData);
   },
 
   _autoCompleteSearchAsync(aSearchString, aPreviousResult,
-                           aElement, aRect) {
+                           aElement) {
     let doc = aElement.ownerDocument;
     let form = LoginFormFactory.createFromField(aElement);
     let win = doc.defaultView;
 
     let formOrigin = LoginHelper.getLoginOrigin(doc.documentURI);
     let actionOrigin = LoginHelper.getFormActionOrigin(form);
+    let autocompleteInfo = aElement.getAutocompleteInfo();
 
     let messageManager = win.docShell.messageManager;
 
     let previousResult = aPreviousResult ?
                            { searchString: aPreviousResult.searchString,
                              logins: LoginHelper.loginsToVanillaObjects(aPreviousResult.logins) } :
                            null;
 
     let requestData = {};
-    let messageData = { formOrigin,
-                        actionOrigin,
-                        searchString: aSearchString,
-                        previousResult,
-                        rect: aRect,
-                        isSecure: InsecurePasswordUtils.isFormSecure(form),
-                        isPasswordField: aElement.type == "password",
+    let messageData = {
+      autocompleteInfo,
+      browsingContextId: win.docShell.browsingContext.id,
+      formOrigin,
+      actionOrigin,
+      searchString: aSearchString,
+      previousResult,
+      isSecure: InsecurePasswordUtils.isFormSecure(form),
+      isPasswordField: aElement.type == "password",
     };
 
     if (LoginHelper.showAutoCompleteFooter) {
       messageManager.addMessageListener("FormAutoComplete:PopupOpened", this);
       messageManager.addMessageListener("FormAutoComplete:PopupClosed", this);
     }
 
     return this._sendRequest(messageManager, requestData,
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -10,28 +10,39 @@ const {Services} = ChromeUtils.import("r
 XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
 
 ChromeUtils.defineModuleGetter(this, "AutoCompletePopup",
                                "resource://gre/modules/AutoCompletePopup.jsm");
 ChromeUtils.defineModuleGetter(this, "DeferredTask",
                                "resource://gre/modules/DeferredTask.jsm");
 ChromeUtils.defineModuleGetter(this, "LoginHelper",
                                "resource://gre/modules/LoginHelper.jsm");
+ChromeUtils.defineModuleGetter(this, "PasswordGenerator",
+                               "resource://gre/modules/PasswordGenerator.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
                                "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let logger = LoginHelper.createLogger("LoginManagerParent");
   return logger.log.bind(logger);
 });
 
 var EXPORTED_SYMBOLS = [ "LoginManagerParent" ];
 
 var LoginManagerParent = {
   /**
+   * A map of a principal's origin (including suffixes) to a generated password string so that we
+   * can offer the same password later (e.g. in a confirmation field).
+   *
+   * We don't currently evict from this cache so entries should last until the end of the browser
+   * session. That may change later but for now a typical session would max out at a few entries.
+   */
+  _generatedPasswordsByPrincipalOrigin: new Map(),
+
+  /**
    * Reference to the default LoginRecipesParent (instead of the initialization promise) for
    * synchronous access. This is a temporary hack and new consumers should yield on
    * recipeParentPromise instead.
    *
    * @type LoginRecipesParent
    * @deprecated
    */
   _recipeManager: null,
@@ -239,19 +250,26 @@ var LoginManagerParent = {
     var jsLogins = LoginHelper.loginsToVanillaObjects(logins);
     target.sendAsyncMessage("PasswordManager:loginsFound", {
       requestId,
       logins: jsLogins,
       recipes,
     });
   },
 
-  doAutocompleteSearch({ formOrigin, actionOrigin,
-                         searchString, previousResult,
-                         rect, requestId, isSecure, isPasswordField,
+  doAutocompleteSearch({
+    autocompleteInfo,
+    browsingContextId,
+    formOrigin,
+    actionOrigin,
+    searchString,
+    previousResult,
+    requestId,
+    isSecure,
+    isPasswordField,
   }, target) {
     // Note: previousResult is a regular object, not an
     // nsIAutoCompleteResult.
 
     // Cancel if we unsuccessfully prompted for the master password too recently.
     if (!Services.logins.isLoggedIn) {
       let timeDiff = Date.now() - this._lastMPLoginCancelled;
       if (timeDiff < this._repromptTimeout) {
@@ -283,32 +301,66 @@ var LoginManagerParent = {
       logins = this._searchAndDedupeLogins(formOrigin, actionOrigin, {looseActionOriginMatch: true});
     }
 
     let matchingLogins = logins.filter(function(fullMatch) {
       let match = fullMatch.username;
 
       // Remove results that are too short, or have different prefix.
       // Also don't offer empty usernames as possible results except
-      // for password field.
+      // for on password fields.
       if (isPasswordField) {
         return true;
       }
       return match && match.toLowerCase().startsWith(searchStringLower);
     });
 
+    let generatedPassword = null;
+    if (isPasswordField && autocompleteInfo.fieldName == "new-password") {
+      generatedPassword = this.getGeneratedPassword(browsingContextId);
+    }
+
     // Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
     // doesn't support structured cloning.
     var jsLogins = LoginHelper.loginsToVanillaObjects(matchingLogins);
     target.messageManager.sendAsyncMessage("PasswordManager:loginsAutoCompleted", {
       requestId,
+      generatedPassword,
       logins: jsLogins,
     });
   },
 
+  /**
+   * Expose `BrowsingContext` so we can stub it in tests.
+   */
+  get _browsingContextGlobal() {
+    return BrowsingContext;
+  },
+
+  getGeneratedPassword(browsingContextId) {
+    if (!LoginHelper.enabled || !LoginHelper.generationAvailable || !LoginHelper.generationEnabled) {
+      return null;
+    }
+
+    let browsingContext = BrowsingContext.get(browsingContextId);
+    if (!browsingContext) {
+      return null;
+    }
+    let framePrincipalOrigin = browsingContext.currentWindowGlobal.documentPrincipal.origin;
+    // Use the same password if we already generated one for this origin so that it doesn't change
+    // with each search/keystroke and the user can easily re-enter a password in a confirmation field.
+    let generatedPW = this._generatedPasswordsByPrincipalOrigin.get(framePrincipalOrigin);
+    if (generatedPW) {
+      return generatedPW;
+    }
+    generatedPW = PasswordGenerator.generatePassword();
+    this._generatedPasswordsByPrincipalOrigin.set(framePrincipalOrigin, generatedPW);
+    return generatedPW;
+  },
+
   onFormSubmit({hostname, formSubmitURL, autoFilledLoginGuid,
                 usernameField, newPasswordField,
                 oldPasswordField, openerTopWindowID,
                 dismissedPrompt, target}) {
     function getPrompter() {
       var prompterSvc = Cc["@mozilla.org/login-manager/prompter;1"].
                         createInstance(Ci.nsILoginManagerPrompter);
       prompterSvc.init(target.ownerGlobal);
--- a/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
+++ b/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
@@ -27,16 +27,19 @@ skip-if = toolkit == 'android' && !is_fe
 [test_autocomplete_highlight.html]
 scheme = https
 skip-if = toolkit == 'android' # autocomplete
 [test_autocomplete_highlight_non_login.html]
 scheme = https
 skip-if = toolkit == 'android' # autocomplete
 [test_autocomplete_https_upgrade.html]
 skip-if = toolkit == 'android' # autocomplete
+[test_autocomplete_new_password.html]
+scheme = https
+skip-if = toolkit == 'android' # autocomplete
 [test_autocomplete_password_open.html]
 scheme = https
 skip-if = toolkit == 'android' # autocomplete
 [test_autocomplete_sandboxed.html]
 scheme = https
 skip-if = toolkit == 'android' # autocomplete
 [test_autofill_autocomplete_types.html]
 scheme = https
@@ -106,19 +109,16 @@ scheme = https
 skip-if = os != 'mac' # Tests desktop prompts and bug 1333264
 support-files =
   chrome_timeout.js
   subtst_master_pass.html
 [test_maxlength.html]
 [test_munged_username.html]
 scheme = https
 skip-if = toolkit == 'android' # bug 1527403
-[test_autocomplete_new_password.html]
-scheme = https
-skip-if = toolkit == 'android' # autocomplete
 [test_one_doorhanger_per_un_pw.html]
 scheme = https
 skip-if = toolkit == 'android' # bug 1535505
 [test_onsubmit_value_change.html]
 [test_passwords_in_type_password.html]
 [test_prompt.html]
 skip-if = os == "linux" || toolkit == 'android' # Tests desktop prompts
 [test_prompt_async.html]
--- a/toolkit/components/passwordmgr/test/mochitest/pwmgr_common.js
+++ b/toolkit/components/passwordmgr/test/mochitest/pwmgr_common.js
@@ -1,14 +1,16 @@
 /**
  * Helpers for password manager mochitest-plain tests.
  */
 
 /* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
 
+const GENERATED_PASSWORD_LENGTH = 15;
+const GENERATED_PASSWORD_REGEX = /^[a-km-np-zA-HJ-NP-Z2-9]{15}$/;
 // Copied from LoginTestUtils.masterPassword.masterPassword to use from the content process.
 const MASTER_PASSWORD = "omgsecret!";
 const TESTS_DIR = "/tests/toolkit/components/passwordmgr/test/";
 
 /**
  * Returns the element with the specified |name| attribute.
  */
 function $_(formNum, name) {
@@ -35,26 +37,25 @@ function $_(formNum, name) {
     return null;
   }
 
   return element;
 }
 
 /**
  * Check autocomplete popup results to ensure that expected
- * values are being shown correctly as items in the popup.
+ * *labels* are being shown correctly as items in the popup.
  */
 function checkAutoCompleteResults(actualValues, expectedValues, hostname, msg) {
   if (hostname !== null) {
     isnot(actualValues.length, 0, "There should be items in the autocomplete popup: " + JSON.stringify(actualValues));
 
     // Check the footer first.
     let footerResult = actualValues[actualValues.length - 1];
-    ok(footerResult.includes("View Saved Logins"), "the footer text is shown correctly");
-    ok(footerResult.includes(hostname), "the footer has the correct hostname attribute");
+    is(footerResult, "View Saved Logins", "the footer text is shown correctly");
   }
 
   if (hostname === null) {
     checkArrayValues(actualValues, expectedValues, msg);
     return;
   }
 
   if (actualValues.length == 1) {
@@ -63,16 +64,27 @@ function checkAutoCompleteResults(actual
     return;
   }
 
   // Check the rest of the autocomplete item values.
   checkArrayValues(actualValues.slice(0, -1), expectedValues, msg);
 }
 
 /**
+ * Check for expected username/password in form.
+ * @see `checkForm` below for a similar function.
+ */
+function checkLoginForm(usernameField, expectedUsername, passwordField, expectedPassword) {
+  let formID = usernameField.parentNode.id;
+  is(usernameField.value, expectedUsername, "Checking " + formID + " username is: " + expectedUsername);
+  is(passwordField.value, expectedPassword, "Checking " + formID + " password is: " + expectedPassword);
+}
+
+
+/**
  * Check a form for expected values. If an argument is null, a field's
  * expected value will be the default value.
  *
  * <form id="form#">
  * checkForm(#, "foo");
  */
 function checkForm(formNum, val1, val2, val3) {
   var e, form = document.getElementById("form" + formNum);
@@ -205,24 +217,25 @@ function isLoggedIn() {
 function logoutMasterPassword() {
   runInParent(function parent_logoutMasterPassword() {
     var sdr = Cc["@mozilla.org/security/sdr;1"].getService(Ci.nsISecretDecoderRing);
     sdr.logoutAndTeardown();
   });
 }
 
 /**
- * Resolves when a specified number of forms have been processed.
+ * Resolves when a specified number of forms have been processed for (potential) filling.
  */
 function promiseFormsProcessed(expectedCount = 1) {
   var processedCount = 0;
   return new Promise((resolve, reject) => {
     function onProcessedForm(subject, topic, data) {
       processedCount++;
       if (processedCount == expectedCount) {
+        info(`${processedCount} form(s) processed`);
         SpecialPowers.removeObserver(onProcessedForm, "passwordmgr-processed-form");
         resolve(SpecialPowers.Cu.waiveXrays(subject), data);
       }
     }
     SpecialPowers.addObserver(onProcessedForm, "passwordmgr-processed-form");
   });
 }
 
@@ -391,14 +404,8 @@ this.LoginManager = new Proxy({}, {
         args: cloneableArgs,
         loginInfoIndices,
         methodName: prop,
       })[0][0];
     };
   },
 });
 
-// Check for expected username/password in form.
-function checkLoginForm(usernameField, expectedUsername, passwordField, expectedPassword) {
-  let formID = usernameField.parentNode.id;
-  is(usernameField.value, expectedUsername, "Checking " + formID + " username is: " + expectedUsername);
-  is(passwordField.value, expectedPassword, "Checking " + formID + " password is: " + expectedPassword);
-}
--- a/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_new_password.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_new_password.html
@@ -1,18 +1,18 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
-  <title>Test autofill with autocomplete=new-password fields</title>
+  <title>Test autofill and autocomplete on autocomplete=new-password fields</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script src="/tests/SimpleTest/EventUtils.js"></script>
-  <script type="text/javascript" src="pwmgr_common.js"></script>
-  <script type="text/javascript" src="../../../satchel/test/satchel_common.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script src="pwmgr_common.js"></script>
+  <script src="../../../satchel/test/satchel_common.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: autofill with autocomplete=new-password fields
 
 <script>
 let chromeScript = runInParent(function initLogins() {
   const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
@@ -33,74 +33,168 @@ let readyPromise = registerRunTests();
 
   <!-- form1 is the reference, sanity-check -->
   <form id="form1" action="https://autofill" onsubmit="return false;">
     <input type="text" name="uname">
     <input type="password" name="p">
     <button type="submit">Submit</button>
   </form>
 
-  <!-- form2 uses the new-password field -->
+  <!-- form2 uses a new-password type=password field -->
   <form id="form2" action="https://autofill" onsubmit="return false;">
     <input type="text" name="uname">
-    <input type="password" autocomplete="new-password">
+    <input type="password" name="pword" autocomplete="new-password">
     <button type="submit">Submit</button>
   </form>
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 const {ContentTaskUtils} =
   SpecialPowers.Cu.import("resource://testing-common/ContentTaskUtils.jsm", {});
 
-async function getAutocompleteResult(input, expectedValues) {
-  input.focus();
-
-  const shownPromise = promiseACShown();
-  synthesizeKey("KEY_ArrowDown");
-  await shownPromise;
-  synthesizeKey("KEY_ArrowDown");
-  await synthesizeKey("KEY_Enter");
-
-  let didAutocomplete;
-  try {
-    await ContentTaskUtils.waitForCondition(() => {
-      for (let [selector, expectedValue] of Object.entries(expectedValues)) {
-        if (document.querySelector(selector).value !== expectedValue) {
-          return false;
-        }
-      }
-      return true;
-    });
-    didAutocomplete = true;
-  } catch (ex) {
-    info("waitForCondition exception: " + ex.message);
-    didAutocomplete = false;
-  }
-  return didAutocomplete;
-}
-
 add_task(async function setup() {
   ok(readyPromise, "check promise is available");
   await readyPromise;
 });
 
-add_task(async function test_autofillAutocompleteNewPassword() {
+add_task(async function test_autofillAutocompleteUsername_noGeneration() {
   // reference form was filled as expected?
   checkForm(1, "user1", "pass1");
 
   // 2nd form should not be filled
   checkForm(2, "", "");
 
-  let form = document.getElementById("form2");
-  let userInput = form.querySelector("[name='uname']");
+  $_(2, "uname").focus();
+  const shownPromise = promiseACShown();
+  synthesizeKey("KEY_ArrowDown");
+  let results = await shownPromise;
+  let expectedACLabels = ["user1"];
+  checkAutoCompleteResults(results, expectedACLabels, "example.com", "Check all rows are correct");
+  synthesizeKey("KEY_ArrowDown");
+  synthesizeKey("KEY_Enter");
+  await promiseFormsProcessed();
+  checkForm(2, "user1", "pass1");
+
+  document.getElementById("form2").reset();
+});
+
+add_task(async function test_autofillAutocompletePassword_noGeneration() {
+  // 2nd form should not be filled
+  checkForm(2, "", "");
+
+  let pword = $_(2, "pword");
+  pword.focus();
+  const shownPromise = promiseACShown();
+  synthesizeKey("KEY_ArrowDown");
+  let results = await shownPromise;
+  let expectedACLabels = ["user1"];
+  checkAutoCompleteResults(results, expectedACLabels, "example.com", "Check all rows are correct");
+  synthesizeKey("KEY_ArrowDown");
+  synthesizeKey("KEY_Enter");
+  // Can't use promiseFormsProcessed() when autocomplete fills the field directly.
+  await SimpleTest.promiseWaitForCondition(() => pword.value == "pass1", "Check pw filled");
+  checkForm(2, "", "pass1");
 
-  const didAutocomplete = await getAutocompleteResult(userInput, {
-    "#form2 [name='uname']": "user1",
-    "#form2 [type='password']": "pass1",
-  });
-  ok(didAutocomplete, "Autocomplete of user and password fields should happen");
+  // No autocomplete results should appear for non-empty pw fields.
+  synthesizeKey("KEY_ArrowDown");
+  await promiseNoUnexpectedPopupShown();
+
+  document.getElementById("form2").reset();
+});
+
+// All tests below this are with generation prefs enabled.
+
+add_task(async function test_autofillAutocompleteUsername_noGeneration() {
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["signon.generation.available", true],
+    ["signon.generation.enabled", true],
+    ["signon.showAutoCompleteOrigins", true],
+  ]});
+
+  // 2nd form should not be filled
+  checkForm(2, "", "");
+
+  $_(2, "uname").focus();
+  const shownPromise = promiseACShown();
+  synthesizeKey("KEY_ArrowDown");
+  let results = await shownPromise;
+  // No generation option on username fields.
+  let expectedACLabels = ["user1"];
+  checkAutoCompleteResults(results, expectedACLabels, "example.com", "Check all rows are correct");
+  synthesizeKey("KEY_ArrowDown");
+  synthesizeKey("KEY_Enter");
+  await promiseFormsProcessed();
+  checkForm(2, "user1", "pass1");
+
+  document.getElementById("form2").reset();
 });
 
+add_task(async function test_autofillAutocompletePassword_withGeneration() {
+  // 2nd form should not be filled
+  checkForm(2, "", "");
+
+  let pword = $_(2, "pword");
+  pword.focus();
+  let shownPromise = promiseACShown();
+  synthesizeKey("KEY_ArrowDown");
+  let results = await shownPromise;
+  let expectedACLabels = [
+    "user1",
+    "Use Generated Password",
+  ];
+  checkAutoCompleteResults(results, expectedACLabels, "example.com", "Check all rows are correct");
+  synthesizeKey("KEY_ArrowDown");
+  synthesizeKey("KEY_Enter");
+  // Can't use promiseFormsProcessed() when autocomplete fills the field directly.
+  await SimpleTest.promiseWaitForCondition(() => pword.value == "pass1", "Check pw filled");
+  checkForm(2, "", "pass1");
+
+  // No autocomplete results should appear for non-empty pw fields.
+  synthesizeKey("KEY_ArrowDown");
+  await promiseNoUnexpectedPopupShown();
+
+  while (pword.value) {
+    synthesizeKey("KEY_Backspace");
+  }
+
+  info("This time select the generated password");
+  shownPromise = promiseACShown();
+  synthesizeKey("KEY_ArrowDown");
+  results = await shownPromise;
+  expectedACLabels = [
+    "user1",
+    "Use Generated Password",
+  ];
+  checkAutoCompleteResults(results, expectedACLabels, "example.com", "Check all rows are correct");
+  synthesizeKey("KEY_ArrowDown");
+  synthesizeKey("KEY_ArrowDown");
+  synthesizeKey("KEY_Enter");
+  await SimpleTest.promiseWaitForCondition(() => !!pword.value, "Check generated pw filled");
+  let generatedPW = pword.value;
+  is(generatedPW.length, GENERATED_PASSWORD_LENGTH, "Check generated password length");
+  ok(generatedPW.match(GENERATED_PASSWORD_REGEX), "Check generated password format");
+
+  while (pword.value) {
+    synthesizeKey("KEY_Backspace");
+  }
+
+  shownPromise = promiseACShown();
+  synthesizeKey("KEY_ArrowDown");
+  results = await shownPromise;
+  expectedACLabels = [
+    "user1",
+    "Use Generated Password",
+  ];
+  checkAutoCompleteResults(results, expectedACLabels, "example.com", "Check all rows are correct");
+  synthesizeKey("KEY_ArrowDown");
+  synthesizeKey("KEY_ArrowDown");
+  synthesizeKey("KEY_Enter");
+  await SimpleTest.promiseWaitForCondition(() => !!pword.value, "Check generated pw filled");
+  // Same generated password should be used.
+  checkForm(2, "", generatedPW);
+
+  document.getElementById("form2").reset();
+});
 </script>
 </pre>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_doAutocompleteSearch.js
@@ -0,0 +1,92 @@
+/**
+ * Test LoginManagerParent.doAutocompleteSearch()
+ */
+
+"use strict";
+
+const {sinon} = ChromeUtils.import("resource://testing-common/Sinon.jsm");
+const {LoginManagerParent: LMP} = ChromeUtils.import("resource://gre/modules/LoginManagerParent.jsm");
+
+add_task(async function test_doAutocompleteSearch_generated_noLogins() {
+  Services.prefs.setBoolPref("signon.generation.available", true); // TODO: test both with false
+  Services.prefs.setBoolPref("signon.generation.enabled", true);
+
+  ok(LMP.doAutocompleteSearch, "doAutocompleteSearch exists");
+
+  // Default to the happy path
+  let arg1 = {
+    autocompleteInfo: {
+      section: "",
+      addressType: "",
+      contactType: "",
+      fieldName: "new-password",
+      canAutomaticallyPersist: false,
+    },
+    browsingContextId: 123,
+    formOrigin: "https://example.com",
+    actionOrigin: "https://mozilla.org",
+    searchString: "",
+    previousResult: null,
+    requestId: "foo",
+    isSecure: true,
+    isPasswordField: true,
+  };
+
+  let sendMessageStub = sinon.stub();
+  let fakeBrowser = {
+    messageManager: {
+      sendAsyncMessage: sendMessageStub,
+    },
+  };
+
+  sinon.stub(LMP._browsingContextGlobal, "get").withArgs(123).callsFake(() => {
+    return {
+      currentWindowGlobal: {
+        documentPrincipal: Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("https://www.example.com^userContextId=1"),
+      },
+    };
+  });
+
+  LMP.doAutocompleteSearch(arg1, fakeBrowser);
+  ok(sendMessageStub.calledOnce, "sendAsyncMessage was called");
+  let msg1 = sendMessageStub.firstCall.args[1];
+  equal(msg1.requestId, arg1.requestId, "requestId matches");
+  equal(msg1.logins.length, 0, "no logins");
+  ok(msg1.generatedPassword, "has a generated password");
+  equal(msg1.generatedPassword.length, 15, "generated password length");
+  sendMessageStub.resetHistory();
+
+  info("repeat the search and ensure the same password was used");
+  LMP.doAutocompleteSearch(arg1, fakeBrowser);
+  ok(sendMessageStub.calledOnce, "sendAsyncMessage was called");
+  let msg2 = sendMessageStub.firstCall.args[1];
+  equal(msg2.requestId, arg1.requestId, "requestId matches");
+  equal(msg2.logins.length, 0, "no logins");
+  equal(msg2.generatedPassword, msg1.generatedPassword, "same generated password");
+  sendMessageStub.resetHistory();
+
+  info("Check cases where a password shouldn't be generated");
+
+  LMP.doAutocompleteSearch({...arg1, ...{isPasswordField: false}}, fakeBrowser);
+  ok(sendMessageStub.calledOnce, "sendAsyncMessage was called");
+  let msg = sendMessageStub.firstCall.args[1];
+  equal(msg.requestId, arg1.requestId, "requestId matches");
+  equal(msg.generatedPassword, null, "no generated password when not a pw. field");
+  sendMessageStub.resetHistory();
+
+  let arg1_2 = {...arg1};
+  arg1_2.autocompleteInfo.fieldName = "";
+  LMP.doAutocompleteSearch(arg1_2, fakeBrowser);
+  ok(sendMessageStub.calledOnce, "sendAsyncMessage was called");
+  msg = sendMessageStub.firstCall.args[1];
+  equal(msg.requestId, arg1.requestId, "requestId matches");
+  equal(msg.generatedPassword, null, "no generated password when not autocomplete=new-password");
+  sendMessageStub.resetHistory();
+
+  LMP.doAutocompleteSearch({...arg1, ...{browsingContextId: 999}}, fakeBrowser);
+  ok(sendMessageStub.calledOnce, "sendAsyncMessage was called");
+  msg = sendMessageStub.firstCall.args[1];
+  equal(msg.requestId, arg1.requestId, "requestId matches");
+  equal(msg.generatedPassword, null, "no generated password with a missing browsingContextId");
+  sendMessageStub.resetHistory();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_getGeneratedPassword.js
@@ -0,0 +1,69 @@
+/**
+ * Test LoginManagerParent.getGeneratedPassword()
+ */
+
+"use strict";
+
+const {sinon} = ChromeUtils.import("resource://testing-common/Sinon.jsm");
+const {LoginManagerParent: LMP} = ChromeUtils.import("resource://gre/modules/LoginManagerParent.jsm");
+
+add_task(async function test_getGeneratedPassword() {
+  // Force the feature to be enabled.
+  Services.prefs.setBoolPref("signon.generation.available", true);
+  Services.prefs.setBoolPref("signon.generation.enabled", true);
+
+  ok(LMP.getGeneratedPassword, "LMP.getGeneratedPassword exists");
+  equal(LMP._generatedPasswordsByPrincipalOrigin.size, 0, "Empty cache to start");
+
+  equal(LMP.getGeneratedPassword(99), null, "Null with no BrowsingContext");
+
+  ok(LMP._browsingContextGlobal, "Check _browsingContextGlobal exists");
+  ok(!LMP._browsingContextGlobal.get(99), "BrowsingContext 99 shouldn't exist yet");
+  info("Stubbing BrowsingContext.get(99)");
+  sinon.stub(LMP._browsingContextGlobal, "get").withArgs(99).callsFake(() => {
+    return {
+      currentWindowGlobal: {
+        documentPrincipal: Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("https://www.example.com^userContextId=6"),
+      },
+    };
+  });
+  ok(LMP._browsingContextGlobal.get(99), "Checking BrowsingContext.get(99) stub");
+
+  let password1 = LMP.getGeneratedPassword(99);
+  notEqual(password1, null, "Check password was returned");
+  equal(password1.length, 15, "Check password length");
+  equal(LMP._generatedPasswordsByPrincipalOrigin.size, 1, "1 added to cache");
+  equal(LMP._generatedPasswordsByPrincipalOrigin.get("https://www.example.com^userContextId=6"),
+        password1, "Cache key and value");
+  let password2 = LMP.getGeneratedPassword(99);
+  equal(password1, password2, "Same password should be returned for the same origin");
+
+  info("Changing the documentPrincipal to simulate a navigation in the frame");
+  LMP._browsingContextGlobal.get.restore();
+  sinon.stub(LMP._browsingContextGlobal, "get").withArgs(99).callsFake(() => {
+    return {
+      currentWindowGlobal: {
+        documentPrincipal: Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("https://www.mozilla.org^userContextId=2"),
+      },
+    };
+  });
+  let password3 = LMP.getGeneratedPassword(99);
+  notEqual(password2, password3, "Different password for a different origin for the same BC");
+  equal(password3.length, 15, "Check password3 length");
+
+  info("Now checks cases where null should be returned");
+
+  Services.prefs.setBoolPref("signon.rememberSignons", false);
+  equal(LMP.getGeneratedPassword(99), null, "Prevented when pwmgr disabled");
+  Services.prefs.setBoolPref("signon.rememberSignons", true);
+
+  Services.prefs.setBoolPref("signon.generation.available", false);
+  equal(LMP.getGeneratedPassword(99), null, "Prevented when unavailable");
+  Services.prefs.setBoolPref("signon.generation.available", true);
+
+  Services.prefs.setBoolPref("signon.generation.enabled", false);
+  equal(LMP.getGeneratedPassword(99), null, "Prevented when disabled");
+  Services.prefs.setBoolPref("signon.generation.enabled", true);
+
+  equal(LMP.getGeneratedPassword(123), null, "Prevented when browsingContext is missing");
+});
--- a/toolkit/components/passwordmgr/test/unit/test_login_autocomplete_result.js
+++ b/toolkit/components/passwordmgr/test/unit/test_login_autocomplete_result.js
@@ -1,14 +1,13 @@
 const {LoginAutoCompleteResult} = ChromeUtils.import("resource://gre/modules/LoginAutoCompleteResult.jsm");
 var nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
                                          Ci.nsILoginInfo, "init");
 
 const PREF_INSECURE_FIELD_WARNING_ENABLED = "security.insecure_field_warning.contextual.enabled";
-const PREF_INSECURE_AUTOFILLFORMS_ENABLED = "signon.autofillForms.http";
 
 let matchingLogins = [];
 matchingLogins.push(new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
                                     "", "emptypass1", "uname", "pword"));
 
 matchingLogins.push(new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
                                     "tempuser1", "temppass1", "uname", "pword"));
 
@@ -25,457 +24,738 @@ let meta = matchingLogins[0].QueryInterf
 let dateAndTimeFormatter = new Services.intl.DateTimeFormat(undefined,
                                                             { dateStyle: "medium" });
 let time = dateAndTimeFormatter.format(new Date(meta.timePasswordChanged));
 const LABEL_NO_USERNAME = "No username (" + time + ")";
 
 let expectedResults = [
   {
     insecureFieldWarningEnabled: true,
-    insecureAutoFillFormsEnabled: true,
     isSecure: true,
     isPasswordField: false,
     matchingLogins,
     items: [{
       value: "",
       label: LABEL_NO_USERNAME,
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "tempuser1",
       label: "tempuser1",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testuser2",
       label: "testuser2",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testuser3",
       label: "testuser3",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "zzzuser4",
       label: "zzzuser4",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
     }],
   },
   {
     insecureFieldWarningEnabled: true,
-    insecureAutoFillFormsEnabled: true,
+    isSecure: false,
+    isPasswordField: false,
+    matchingLogins: [],
+    items: [{
+      value: "",
+      label: "This connection is not secure. Logins entered here could be compromised. Learn More",
+      style: "insecureWarning",
+      comment: "",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
+    }],
+  },
+  {
+    insecureFieldWarningEnabled: true,
     isSecure: false,
     isPasswordField: false,
     matchingLogins,
     items: [{
       value: "",
       label: "This connection is not secure. Logins entered here could be compromised. Learn More",
       style: "insecureWarning",
+      comment: "",
     }, {
       value: "",
       label: LABEL_NO_USERNAME,
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "tempuser1",
       label: "tempuser1",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testuser2",
       label: "testuser2",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testuser3",
       label: "testuser3",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "zzzuser4",
       label: "zzzuser4",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
     }],
   },
   {
     insecureFieldWarningEnabled: true,
-    insecureAutoFillFormsEnabled: true,
     isSecure: true,
     isPasswordField: true,
     matchingLogins,
     items: [{
       value: "emptypass1",
       label: LABEL_NO_USERNAME,
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "temppass1",
       label: "tempuser1",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testpass2",
       label: "testuser2",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testpass3",
       label: "testuser3",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "zzzpass4",
       label: "zzzuser4",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
     }],
   },
   {
     insecureFieldWarningEnabled: true,
-    insecureAutoFillFormsEnabled: true,
     isSecure: false,
     isPasswordField: true,
     matchingLogins,
     items: [{
       value: "",
       label: "This connection is not secure. Logins entered here could be compromised. Learn More",
       style: "insecureWarning",
+      comment: "",
     }, {
       value: "emptypass1",
       label: LABEL_NO_USERNAME,
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "temppass1",
       label: "tempuser1",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testpass2",
       label: "testuser2",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testpass3",
       label: "testuser3",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "zzzpass4",
       label: "zzzuser4",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
     }],
   },
   {
     insecureFieldWarningEnabled: false,
-    insecureAutoFillFormsEnabled: true,
     isSecure: true,
     isPasswordField: false,
     matchingLogins,
     items: [{
       value: "",
       label: LABEL_NO_USERNAME,
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "tempuser1",
       label: "tempuser1",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testuser2",
       label: "testuser2",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testuser3",
       label: "testuser3",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "zzzuser4",
       label: "zzzuser4",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
     }],
   },
   {
     insecureFieldWarningEnabled: false,
-    insecureAutoFillFormsEnabled: true,
     isSecure: false,
     isPasswordField: false,
     matchingLogins,
     items: [{
       value: "",
       label: LABEL_NO_USERNAME,
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "tempuser1",
       label: "tempuser1",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testuser2",
       label: "testuser2",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testuser3",
       label: "testuser3",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "zzzuser4",
       label: "zzzuser4",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
     }],
   },
   {
     insecureFieldWarningEnabled: false,
-    insecureAutoFillFormsEnabled: true,
     isSecure: true,
     isPasswordField: true,
     matchingLogins,
     items: [{
       value: "emptypass1",
       label: LABEL_NO_USERNAME,
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "temppass1",
       label: "tempuser1",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testpass2",
       label: "testuser2",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testpass3",
       label: "testuser3",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "zzzpass4",
       label: "zzzuser4",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
     }],
   },
   {
     insecureFieldWarningEnabled: false,
-    insecureAutoFillFormsEnabled: true,
     isSecure: false,
     isPasswordField: true,
     matchingLogins,
     items: [{
       value: "emptypass1",
       label: LABEL_NO_USERNAME,
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "temppass1",
       label: "tempuser1",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testpass2",
       label: "testuser2",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testpass3",
       label: "testuser3",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "zzzpass4",
       label: "zzzuser4",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
     }],
   },
   {
     insecureFieldWarningEnabled: true,
-    insecureAutoFillFormsEnabled: false,
     isSecure: true,
     isPasswordField: false,
     matchingLogins,
     items: [{
       value: "",
       label: LABEL_NO_USERNAME,
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "tempuser1",
       label: "tempuser1",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testuser2",
       label: "testuser2",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testuser3",
       label: "testuser3",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "zzzuser4",
       label: "zzzuser4",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
     }],
   },
   {
     insecureFieldWarningEnabled: true,
-    insecureAutoFillFormsEnabled: false,
     isSecure: false,
     isPasswordField: false,
     matchingLogins,
     items: [{
       value: "",
       label: "This connection is not secure. Logins entered here could be compromised. Learn More",
       style: "insecureWarning",
+      comment: "",
     }, {
       value: "",
       label: LABEL_NO_USERNAME,
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "tempuser1",
       label: "tempuser1",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testuser2",
       label: "testuser2",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testuser3",
       label: "testuser3",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "zzzuser4",
       label: "zzzuser4",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
     }],
   },
   {
     insecureFieldWarningEnabled: true,
-    insecureAutoFillFormsEnabled: false,
     isSecure: true,
     isPasswordField: true,
     matchingLogins,
     items: [{
       value: "emptypass1",
       label: LABEL_NO_USERNAME,
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "temppass1",
       label: "tempuser1",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testpass2",
       label: "testuser2",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testpass3",
       label: "testuser3",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "zzzpass4",
       label: "zzzuser4",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
     }],
   },
   {
     insecureFieldWarningEnabled: true,
-    insecureAutoFillFormsEnabled: false,
     isSecure: false,
     isPasswordField: true,
     matchingLogins,
     items: [{
       value: "",
       label: "This connection is not secure. Logins entered here could be compromised. Learn More",
       style: "insecureWarning",
+      comment: "",
     }, {
       value: "emptypass1",
       label: LABEL_NO_USERNAME,
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "temppass1",
       label: "tempuser1",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testpass2",
       label: "testuser2",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testpass3",
       label: "testuser3",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "zzzpass4",
       label: "zzzuser4",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
     }],
   },
   {
     insecureFieldWarningEnabled: false,
-    insecureAutoFillFormsEnabled: false,
     isSecure: true,
     isPasswordField: false,
     matchingLogins,
     items: [{
       value: "",
       label: LABEL_NO_USERNAME,
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "tempuser1",
       label: "tempuser1",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testuser2",
       label: "testuser2",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testuser3",
       label: "testuser3",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "zzzuser4",
       label: "zzzuser4",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
+    }],
+  },
+  {
+    insecureFieldWarningEnabled: false,
+    isSecure: false,
+    isPasswordField: false,
+    matchingLogins: [],
+    items: [{
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
     }],
   },
   {
     insecureFieldWarningEnabled: false,
-    insecureAutoFillFormsEnabled: false,
+    isSecure: false,
+    isPasswordField: false,
+    matchingLogins: [],
+    searchString: "foo",
+    items: [{
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
+    }],
+  },
+  {
+    insecureFieldWarningEnabled: false,
     isSecure: false,
     isPasswordField: false,
     matchingLogins,
-    items: [],
+    items: [{
+      value: "",
+      label: LABEL_NO_USERNAME,
+      style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "tempuser1",
+      label: "tempuser1",
+      style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "testuser2",
+      label: "testuser2",
+      style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "testuser3",
+      label: "testuser3",
+      style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "zzzuser4",
+      label: "zzzuser4",
+      style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
+    }],
   },
   {
     insecureFieldWarningEnabled: false,
-    insecureAutoFillFormsEnabled: false,
     isSecure: true,
     isPasswordField: true,
     matchingLogins,
     items: [{
       value: "emptypass1",
       label: LABEL_NO_USERNAME,
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "temppass1",
       label: "tempuser1",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testpass2",
       label: "testuser2",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "testpass3",
+      label: "testuser3",
+      style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "zzzpass4",
+      label: "zzzuser4",
+      style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
+    }],
+  },
+  {
+    insecureFieldWarningEnabled: false,
+    isSecure: false,
+    isPasswordField: true,
+    matchingLogins,
+    items: [{
+      value: "emptypass1",
+      label: LABEL_NO_USERNAME,
+      style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "temppass1",
+      label: "tempuser1",
+      style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "testpass2",
+      label: "testuser2",
+      style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "testpass3",
       label: "testuser3",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
     }, {
       value: "zzzpass4",
       label: "zzzuser4",
       style: "loginWithOrigin",
+      comment: "mochi.test:8888",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
+    }],
+  },
+  {
+    insecureFieldWarningEnabled: true,
+    isSecure: true,
+    isPasswordField: true,
+    matchingLogins: [],
+    items: [{
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
     }],
   },
   {
-    insecureFieldWarningEnabled: false,
-    insecureAutoFillFormsEnabled: false,
-    isSecure: false,
+    insecureFieldWarningEnabled: true,
+    isSecure: true,
+    isPasswordField: true,
+    matchingLogins: [],
+    searchString: "foo",
+    items: [],
+  },
+  {
+    generatedPassword: "9ljgfd4shyktb45",
+    insecureFieldWarningEnabled: true,
+    isSecure: true,
     isPasswordField: true,
-    matchingLogins,
+    matchingLogins: [],
+    items: [{
+      value: "9ljgfd4shyktb45",
+      label: "Use Generated Password",
+      style: "generatedPassword",
+      comment: "9ljgfd4shyktb45",
+    }, {
+      value: "",
+      label: "View Saved Logins",
+      style: "loginsFooter",
+      comment: "mochi.test",
+    }],
+  },
+  {
+    generatedPassword: "9ljgfd4shyktb45",
+    insecureFieldWarningEnabled: true,
+    isSecure: true,
+    isPasswordField: true,
+    matchingLogins: [],
+    searchString: "9ljgfd4shyktb45",
     items: [],
   },
 ];
 
 add_task(async function test_all_patterns() {
   LoginHelper.createLogger("LoginAutoCompleteResult");
+  Services.prefs.setBoolPref("signon.showAutoCompleteFooter", true);
   Services.prefs.setBoolPref("signon.showAutoCompleteOrigins", true);
 
   expectedResults.forEach(pattern => {
+    info(JSON.stringify(pattern, null, 2));
     Services.prefs.setBoolPref(PREF_INSECURE_FIELD_WARNING_ENABLED,
                                pattern.insecureFieldWarningEnabled);
-    Services.prefs.setBoolPref(PREF_INSECURE_AUTOFILLFORMS_ENABLED,
-                               pattern.insecureAutoFillFormsEnabled);
-    let actual = new LoginAutoCompleteResult("", pattern.matchingLogins, {
+    let actual = new LoginAutoCompleteResult(pattern.searchString || "", pattern.matchingLogins, {
+      hostname: "mochi.test",
+      generatedPassword: pattern.generatedPassword,
       isSecure: pattern.isSecure,
       isPasswordField: pattern.isPasswordField,
     });
+    equal(actual.matchCount, pattern.items.length, "Check matching row count");
     pattern.items.forEach((item, index) => {
-      equal(actual.getValueAt(index), item.value);
-      equal(actual.getLabelAt(index), item.label);
-      equal(actual.getStyleAt(index), item.style);
+      equal(actual.getValueAt(index), item.value, `Value ${index}`);
+      equal(actual.getLabelAt(index), item.label, `Label ${index}`);
+      equal(actual.getStyleAt(index), item.style, `Style ${index}`);
+      equal(actual.getCommentAt(index), item.comment, `Comment ${index}`);
     });
 
     if (pattern.items.length != 0) {
       Assert.throws(() => actual.getValueAt(pattern.items.length),
                     /Index out of range\./);
 
       Assert.throws(() => actual.getLabelAt(pattern.items.length),
                     /Index out of range\./);
--- a/toolkit/components/passwordmgr/test/unit/xpcshell.ini
+++ b/toolkit/components/passwordmgr/test/unit/xpcshell.ini
@@ -22,16 +22,20 @@ run-if = buildapp == "browser"
 [test_doLoginsMatch.js]
 [test_getFormFields.js]
 [test_getPasswordFields.js]
 [test_getPasswordOrigin.js]
 [test_getUserNameAndPasswordFields.js]
 [test_isOriginMatching.js]
 [test_isUsernameFieldType.js]
 [test_legacy_empty_formSubmitURL.js]
+[test_LoginManagerParent_doAutocompleteSearch.js]
+skip-if = os == "android" # Password generation not packaged/used on Android
+[test_LoginManagerParent_getGeneratedPassword.js]
+skip-if = os == "android" # Password generation not packaged/used on Android
 [test_legacy_validation.js]
 [test_login_autocomplete_result.js]
 skip-if = os == "android"
 [test_logins_change.js]
 [test_logins_decrypt_failure.js]
 skip-if = os == "android" # Bug 1171687: Needs fixing on Android
 [test_logins_metainfo.js]
 [test_logins_search.js]
--- a/toolkit/content/widgets/autocomplete-popup.js
+++ b/toolkit/content/widgets/autocomplete-popup.js
@@ -372,16 +372,17 @@ MozElements.MozAutocompleteRichlistboxPo
 
         // The styles on the list which have different <content> structure and overrided
         // _adjustAcItem() are unreusable.
         const UNREUSEABLE_STYLES = [
           "autofill-profile",
           "autofill-footer",
           "autofill-clear-button",
           "autofill-insecureWarning",
+          "generatedPassword",
           "insecureWarning",
           "loginsFooter",
           "loginWithOrigin",
         ];
         // Reuse the item when its style is exactly equal to the previous style or
         // neither of their style are in the UNREUSEABLE_STYLES.
         reusable = originalType === style ||
           !(UNREUSEABLE_STYLES.includes(style) || UNREUSEABLE_STYLES.includes(originalType));
@@ -404,18 +405,19 @@ MozElements.MozAutocompleteRichlistboxPo
             options = { is: "autocomplete-creditcard-insecure-field" };
             break;
           case "insecureWarning":
             options = { is: "autocomplete-richlistitem-insecure-warning" };
             break;
           case "loginsFooter":
             options = { is: "autocomplete-richlistitem-logins-footer" };
             break;
+          case "generatedPassword":
           case "loginWithOrigin":
-            options = { is: "autocomplete-richlistitem-login-with-origin" };
+            options = { is: "autocomplete-two-line-richlistitem" };
             break;
           default:
             options = { is: "autocomplete-richlistitem" };
         }
         item = document.createXULElement("richlistitem", options);
         item.className = "autocomplete-richlistitem";
       }
 
--- a/toolkit/content/widgets/autocomplete-richlistitem.js
+++ b/toolkit/content/widgets/autocomplete-richlistitem.js
@@ -983,83 +983,71 @@ class MozAutocompleteRichlistitemLoginsF
   constructor() {
     super();
 
     function handleEvent(event) {
       if (event.button != 0) {
         return;
       }
 
+      // ac-label gets populated from getCommentAt despite the attribute name.
+      // The "comment" is used to populate additional visible text.
+      let formHostname = this.getAttribute("ac-label");
       LoginHelper.openPasswordManager(this.ownerGlobal, {
-        filterString: this._data.hostname,
+        filterString: formHostname,
         entryPoint: "autocomplete",
       });
     }
 
     this.addEventListener("click", handleEvent);
   }
-
-  get _data() {
-    return JSON.parse(this.getAttribute("ac-value"));
-  }
-
-  _adjustAcItem() {
-    this._titleText.textContent = this._data.label;
-  }
 }
 
-class MozAutocompleteRichlistitemLoginWithOrigin extends MozElements.MozRichlistitem {
+class MozAutocompleteTwoLineRichlistitem extends MozElements.MozRichlistitem {
   connectedCallback() {
     if (this.delayConnectedCallback()) {
       return;
     }
 
     this.textContent = "";
     this.appendChild(MozXULElement.parseXULToFragment(this._markup));
     this.initializeAttributeInheritance();
     this._adjustAcItem();
   }
 
 
   static get inheritedAttributes() {
     return {
-      ".login-username": "text=ac-value",
+      // getLabelAt:
+      ".line1-label": "text=ac-value",
+      // getCommentAt:
+      ".line2-label": "text=ac-label",
     };
   }
 
   get _markup() {
     return `
       <div xmlns="http://www.w3.org/1999/xhtml"
            xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-           class="login-wrapper">
+           class="two-line-wrapper">
         <xul:image class="ac-site-icon"></xul:image>
-        <div class="login-text">
-          <div class="login-row login-username"></div>
-          <div class="login-row login-origin"></div>
+        <div class="labels-wrapper">
+          <div class="label-row line1-label"></div>
+          <div class="label-row line2-label"></div>
         </div>
       </div>
     `;
   }
 
   _adjustAcItem() {
     let outerBoxRect = this.parentNode.getBoundingClientRect();
 
     // Make item fit in popup as XUL box could not constrain
     // item's width
     this.firstElementChild.style.width = outerBoxRect.width + "px";
-
-    let data = JSON.parse(this.getAttribute("ac-label"));
-    let originElement = this.querySelector(".login-origin");
-    try {
-      let uri = Services.io.newURI(data.loginOrigin);
-      // Fallback to handle file: URIs
-      originElement.textContent = uri.displayHostPort || data.loginOrigin;
-    } catch (ex) {
-      originElement.textContent = data.loginOrigin;
-    }
   }
 
   _onOverflow() {}
 
   _onUnderflow() {}
 
   handleOverUnderflow() {}
 }
@@ -1071,12 +1059,12 @@ customElements.define("autocomplete-rich
 customElements.define("autocomplete-richlistitem-insecure-warning", MozAutocompleteRichlistitemInsecureWarning, {
   extends: "richlistitem",
 });
 
 customElements.define("autocomplete-richlistitem-logins-footer", MozAutocompleteRichlistitemLoginsFooter, {
   extends: "richlistitem",
 });
 
-customElements.define("autocomplete-richlistitem-login-with-origin", MozAutocompleteRichlistitemLoginWithOrigin, {
+customElements.define("autocomplete-two-line-richlistitem", MozAutocompleteTwoLineRichlistitem, {
   extends: "richlistitem",
 });
 }
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -1010,16 +1010,17 @@
 
               // The styles on the list which have different <content> structure and overrided
               // _adjustAcItem() are unreusable.
               const UNREUSEABLE_STYLES = [
                 "autofill-profile",
                 "autofill-footer",
                 "autofill-clear-button",
                 "autofill-insecureWarning",
+                "generatedPassword",
                 "insecureWarning",
                 "loginsFooter",
                 "loginWithOrigin",
               ];
               // Reuse the item when its style is exactly equal to the previous style or
               // neither of their style are in the UNREUSEABLE_STYLES.
               reusable = originalType === style ||
                 !(UNREUSEABLE_STYLES.includes(style) || UNREUSEABLE_STYLES.includes(originalType));
@@ -1042,18 +1043,19 @@
                   options = { is: "autocomplete-creditcard-insecure-field" };
                   break;
                 case "insecureWarning":
                   options = { is: "autocomplete-richlistitem-insecure-warning" };
                   break;
                 case "loginsFooter":
                   options = { is: "autocomplete-richlistitem-logins-footer" };
                   break;
+                case "generatedPassword":
                 case "loginWithOrigin":
-                  options = { is: "autocomplete-richlistitem-login-with-origin" };
+                  options = { is: "autocomplete-two-line-richlistitem" };
                   break;
                 default:
                   options = { is: "autocomplete-richlistitem" };
               }
               item = document.createXULElement("richlistitem", options);
               item.className = "autocomplete-richlistitem";
             }
 
--- a/toolkit/locales/en-US/chrome/passwordmgr/passwordmgr.properties
+++ b/toolkit/locales/en-US/chrome/passwordmgr/passwordmgr.properties
@@ -40,23 +40,28 @@ passwordChangeTitle = Confirm Password C
 # LOCALIZATION NOTE (updatePasswordMsg):
 # String is the username for the login.
 updatePasswordMsg = Would you like to update the saved password for “%S”?
 updatePasswordMsgNoUser = Would you like to update the saved password?
 userSelectText2 = Select which login to update:
 removeLoginPrompt=Are you sure you wish to remove this login?
 removeLoginTitle=Remove login
 loginsDescriptionAll2=Logins for the following sites are stored on your computer
+
+# LOCALIZATION NOTE (useGeneratedPassword):
+# Shown in the autocomplete popup to allow filling a generated password into a password field.
+useGeneratedPassword=Use Generated Password
 # LOCALIZATION NOTE (loginHostAge):
 # This is used to show the context menu login items with their age.
 # 1st string is the username for the login, 2nd is the login's age.
 loginHostAge=%1$S (%2$S)
 # LOCALIZATION NOTE (noUsername):
 # String is used on the context menu when a login doesn't have a username.
 noUsername=No username
+
 duplicateLoginTitle=Login already exists
 duplicateLogin=A duplicate login already exists.
 
 # LOCALIZATION NOTE (insecureFieldWarningDescription2, insecureFieldWarningDescription3):
 # %1$S will contain insecureFieldWarningLearnMore and look like a link to indicate that clicking will open a tab with support information.
 insecureFieldWarningDescription2 = This connection is not secure. Logins entered here could be compromised. %1$S
 insecureFieldWarningDescription3 = Logins entered here could be compromised. %1$S
 insecureFieldWarningLearnMore = Learn More