Merge inbound to mozilla-central. a=merge
authorshindli <shindli@mozilla.com>
Tue, 05 Mar 2019 23:41:05 +0200
changeset 520279 996a48b30652
parent 520273 bd68f490ca07 (current diff)
parent 520278 cb76b90f829c (diff)
child 520321 5c6fc3df4737
child 520395 e02a16f52eaf
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
996a48b30652 / 67.0a1 / 20190305214137 / files
nightly linux64
996a48b30652 / 67.0a1 / 20190305214137 / files
nightly mac
996a48b30652 / 67.0a1 / 20190305214137 / files
nightly win32
996a48b30652 / 67.0a1 / 20190305214137 / files
nightly win64
996a48b30652 / 67.0a1 / 20190305214137 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
devtools/client/inspector/layout/components/ComputedProperty.js
--- a/devtools/client/inspector/boxmodel/components/BoxModelProperties.js
+++ b/devtools/client/inspector/boxmodel/components/BoxModelProperties.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { LocalizationHelper } = require("devtools/shared/l10n");
 
-const ComputedProperty = createFactory(require("devtools/client/inspector/layout/components/ComputedProperty"));
+const ComputedProperty = createFactory(require("devtools/client/inspector/boxmodel/components/ComputedProperty"));
 
 const Types = require("../types");
 
 const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties";
 const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
 
 class BoxModelProperties extends PureComponent {
   static get propTypes() {
rename from devtools/client/inspector/layout/components/ComputedProperty.js
rename to devtools/client/inspector/boxmodel/components/ComputedProperty.js
--- a/devtools/client/inspector/layout/components/ComputedProperty.js
+++ b/devtools/client/inspector/boxmodel/components/ComputedProperty.js
@@ -2,26 +2,30 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { LocalizationHelper } = require("devtools/shared/l10n");
 
 loader.lazyGetter(this, "Rep", function() {
   return require("devtools/client/shared/components/reps/reps").REPS.Rep;
 });
 loader.lazyGetter(this, "MODE", function() {
   return require("devtools/client/shared/components/reps/reps").MODE;
 });
 
 loader.lazyRequireGetter(this, "translateNodeFrontToGrip", "devtools/client/inspector/shared/utils", true);
 
+const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties";
+const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
+
 class ComputedProperty extends PureComponent {
   static get propTypes() {
     return {
       name: PropTypes.string.isRequired,
       onHideBoxModelHighlighter: PropTypes.func,
       onShowBoxModelHighlighterForNode: PropTypes.func,
       referenceElement: PropTypes.object,
       referenceElementType: PropTypes.string,
@@ -51,17 +55,23 @@ class ComputedProperty extends PureCompo
     } = this.props;
 
     if (!referenceElement) {
       return null;
     }
 
     return (
       dom.div({ className: "reference-element" },
-        dom.span({ className: "reference-element-type" }, referenceElementType),
+        dom.span(
+          {
+            className: "reference-element-type",
+            title: BOXMODEL_L10N.getStr("boxmodel.offsetParent.title"),
+          },
+          referenceElementType
+        ),
         Rep({
           defaultRep: referenceElement,
           mode: MODE.TINY,
           object: translateNodeFrontToGrip(referenceElement),
           onInspectIconClick: () => setSelectedNode(referenceElement,
             { reason: "box-model" }),
           onDOMNodeMouseOver: () => onShowBoxModelHighlighterForNode(referenceElement),
           onDOMNodeMouseOut: () => onHideBoxModelHighlighter(),
--- a/devtools/client/inspector/boxmodel/components/moz.build
+++ b/devtools/client/inspector/boxmodel/components/moz.build
@@ -5,9 +5,10 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'BoxModel.js',
     'BoxModelEditable.js',
     'BoxModelInfo.js',
     'BoxModelMain.js',
     'BoxModelProperties.js',
+    'ComputedProperty.js',
 )
--- a/devtools/client/inspector/layout/components/moz.build
+++ b/devtools/client/inspector/layout/components/moz.build
@@ -2,11 +2,10 @@
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
     'Accordion.css',
     'Accordion.js',
-    'ComputedProperty.js',
     'LayoutApp.js',
 )
--- a/devtools/client/locales/en-US/boxmodel.properties
+++ b/devtools/client/locales/en-US/boxmodel.properties
@@ -24,8 +24,15 @@ boxmodel.geometryButton.tooltip=Edit pos
 # for showing and collapsing the properties underneath the box model in the layout view
 boxmodel.propertiesLabel=Box Model Properties
 
 # LOCALIZATION NOTE: (boxmodel.offsetParent) This label is displayed inside the list of
 # properties, below the box model, in the layout view. It is displayed next to the
 # position property, when position is absolute, relative, sticky. This label tells users
 # what the DOM node previewed next to it is: an offset parent for the position element.
 boxmodel.offsetParent=offset
+
+# LOCALIZATION NOTE: (boxmodel.offsetParent.title) This label is displayed as a
+# tooltip that appears when hovering over the offset label, inside the list of properties,
+# below the box model, in the layout view. This label tells users
+# what the DOM node previewed next to it is: an offset parent for the position element.
+boxmodel.offsetParent.title=Offset parent of the selected element
+
--- a/js/src/jit-test/tests/wasm/passive-segs-boundary.js
+++ b/js/src/jit-test/tests/wasm/passive-segs-boundary.js
@@ -153,24 +153,34 @@ mem_test("",
          "(memory.init 1 (i32.const 1234) (i32.const 2) (i32.const 3))",
          WebAssembly.RuntimeError, /index out of bounds/);
 
 // init: seg ix is valid passive, but implies copying beyond end of dst
 mem_test("",
          "(memory.init 1 (i32.const 0xFFFE) (i32.const 1) (i32.const 3))",
          WebAssembly.RuntimeError, /index out of bounds/);
 
-// init: seg ix is valid passive, zero len, but src offset out of bounds
+// init: seg ix is valid passive, zero len, but src offset out of bounds.
+// At edge of segment is OK
 mem_test("",
-         "(memory.init 1 (i32.const 1234) (i32.const 4) (i32.const 0))",
+         "(memory.init 1 (i32.const 1234) (i32.const 4) (i32.const 0))");
+
+// One past end of segment is not OK
+mem_test("",
+         "(memory.init 1 (i32.const 1234) (i32.const 5) (i32.const 0))",
          WebAssembly.RuntimeError, /index out of bounds/);
 
-// init: seg ix is valid passive, zero len, but dst offset out of bounds
+// init: seg ix is valid passive, zero len, but dst offset out of bounds.
+// At edge of memory is OK.
 mem_test("",
-         "(memory.init 1 (i32.const 0x10000) (i32.const 2) (i32.const 0))",
+         "(memory.init 1 (i32.const 0x10000) (i32.const 2) (i32.const 0))");
+
+// One past end of memory is not OK.
+mem_test("",
+         "(memory.init 1 (i32.const 0x10001) (i32.const 2) (i32.const 0))",
          WebAssembly.RuntimeError, /index out of bounds/);
 
 // drop: too many args
 mem_test("data.drop 1 (i32.const 42)", "",
          WebAssembly.CompileError,
          /unused values not explicitly dropped by end of block/);
 
 // init: too many args
@@ -251,24 +261,34 @@ tab_test("",
          "(table.init 1 (i32.const 12) (i32.const 2) (i32.const 3))",
          WebAssembly.RuntimeError, /index out of bounds/);
 
 // init: seg ix is valid passive, but implies copying beyond end of dst
 tab_test("",
          "(table.init 1 (i32.const 28) (i32.const 1) (i32.const 3))",
          WebAssembly.RuntimeError, /index out of bounds/);
 
-// init: seg ix is valid passive, zero len, but src offset out of bounds
+// init: seg ix is valid passive, zero len, but src offset out of bounds.
+// At edge of segment is OK.
 tab_test("",
-         "(table.init 1 (i32.const 12) (i32.const 4) (i32.const 0))",
+         "(table.init 1 (i32.const 12) (i32.const 4) (i32.const 0))");
+
+// One past edge of segment is not OK.
+tab_test("",
+         "(table.init 1 (i32.const 12) (i32.const 5) (i32.const 0))",
          WebAssembly.RuntimeError, /index out of bounds/);
 
-// init: seg ix is valid passive, zero len, but dst offset out of bounds
+// init: seg ix is valid passive, zero len, but dst offset out of bounds.
+// At edge of table is OK.
 tab_test("",
-         "(table.init 1 (i32.const 30) (i32.const 2) (i32.const 0))",
+         "(table.init 1 (i32.const 30) (i32.const 2) (i32.const 0))");
+
+// One past edge of table is not OK.
+tab_test("",
+         "(table.init 1 (i32.const 31) (i32.const 2) (i32.const 0))",
          WebAssembly.RuntimeError, /index out of bounds/);
 
 // drop: too many args
 tab_test("elem.drop 1 (i32.const 42)", "",
          WebAssembly.CompileError,
          /unused values not explicitly dropped by end of block/);
 
 // init: too many args
@@ -327,17 +347,27 @@ tab_test("(table.copy (i32.const 15) (i3
          "",
          WebAssembly.RuntimeError, /index out of bounds/);
 
 // copy: zero length with both offsets in-bounds is OK
 tab_test_nofail(
     "(table.copy (i32.const 15) (i32.const 25) (i32.const 0))",
     "");
 
-// copy: zero length with dst offset out of bounds
+// copy: zero length with dst offset out of bounds.
+// At edge of table is OK.
 tab_test("(table.copy (i32.const 30) (i32.const 15) (i32.const 0))",
+         "");
+
+// One past edge of table is not OK.
+tab_test("(table.copy (i32.const 31) (i32.const 15) (i32.const 0))",
          "",
          WebAssembly.RuntimeError, /index out of bounds/);
 
 // copy: zero length with src offset out of bounds
+// At edge of table is OK.
 tab_test("(table.copy (i32.const 15) (i32.const 30) (i32.const 0))",
+         "");
+
+// One past edge of table is not OK.
+tab_test("(table.copy (i32.const 15) (i32.const 31) (i32.const 0))",
          "",
          WebAssembly.RuntimeError, /index out of bounds/);
--- a/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
+++ b/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
@@ -19,21 +19,21 @@ let tab_expmod_t =
         (func (export "ef1") (result i32) (i32.const 1))
         (func (export "ef2") (result i32) (i32.const 2))
         (func (export "ef3") (result i32) (i32.const 3))
         (func (export "ef4") (result i32) (i32.const 4))
      )`;
 
 // .. and this one imports those 5 functions.  It adds 5 of its own, creates a
 // 30 element table using both active and passive initialisers, with a mixture
-// of the imported and local functions.  |testfn| is exported.  It uses the
-// supplied |insn| to modify the table somehow, and then will indirect-call
-// the table entry number specified as a parameter.  That will either return a
-// value 0 to 9 indicating the function called, or will throw an exception if
-// the table entry is empty.
+// of the imported and local functions.  |setup| and |check| are exported.
+// |setup| uses the supplied |insn| to modify the table somehow.  |check| will
+// indirect-call the table entry number specified as a parameter.  That will
+// either return a value 0 to 9 indicating the function called, or will throw an
+// exception if the table entry is empty.
 function gen_tab_impmod_t(insn)
 {
   let t =
   `(module
      ;; -------- Types --------
      (type (func (result i32)))  ;; type #0
      ;; -------- Tables --------
      (table 30 30 funcref)
@@ -50,18 +50,19 @@ function gen_tab_impmod_t(insn)
      (import "a" "if4" (result i32))    ;; index 4
      ;; -------- Functions --------
      (func (result i32) (i32.const 5))  ;; index 5
      (func (result i32) (i32.const 6))
      (func (result i32) (i32.const 7))
      (func (result i32) (i32.const 8))
      (func (result i32) (i32.const 9))  ;; index 9
 
-     (func (export "testfn") (param i32) (result i32)
-       ${insn}
+     (func (export "setup")
+       ${insn})
+     (func (export "check") (param i32) (result i32)
        ;; call the selected table entry, which will either return a value,
        ;; or will cause an exception.
        get_local 0      ;; callIx
        call_indirect 0  ;; and its return value is our return value.
      )
    )`;
    return t;
 };
@@ -74,28 +75,30 @@ function gen_tab_impmod_t(insn)
 function tab_test(instruction, expected_result_vector)
 {
     let tab_expmod_b = wasmTextToBinary(tab_expmod_t);
     let tab_expmod_i = new Instance(new Module(tab_expmod_b));
 
     let tab_impmod_t = gen_tab_impmod_t(instruction);
     let tab_impmod_b = wasmTextToBinary(tab_impmod_t);
 
+    let inst = new Instance(new Module(tab_impmod_b),
+                            {a:{if0:tab_expmod_i.exports.ef0,
+                                if1:tab_expmod_i.exports.ef1,
+                                if2:tab_expmod_i.exports.ef2,
+                                if3:tab_expmod_i.exports.ef3,
+                                if4:tab_expmod_i.exports.ef4
+                               }});
+    inst.exports.setup();
+
     for (let i = 0; i < expected_result_vector.length; i++) {
-        let inst = new Instance(new Module(tab_impmod_b),
-                                {a:{if0:tab_expmod_i.exports.ef0,
-                                    if1:tab_expmod_i.exports.ef1,
-                                    if2:tab_expmod_i.exports.ef2,
-                                    if3:tab_expmod_i.exports.ef3,
-                                    if4:tab_expmod_i.exports.ef4
-                                   }});
         let expected = expected_result_vector[i];
         let actual = undefined;
         try {
-            actual = inst.exports.testfn(i);
+            actual = inst.exports.check(i);
             assertEq(actual !== null, true);
         } catch (e) {
             if (!(e instanceof Error &&
                   e.message.match(/indirect call to null/)))
                 throw e;
             // actual remains undefined
         }
         assertEq(actual, expected,
@@ -542,26 +545,39 @@ function checkRange(arr, minIx, maxIxPlu
        )
      )`
     );
     inst.exports.testfn();
     let b = new Uint8Array(inst.exports.memory.buffer);
     checkRange(b, 0x00000, 0x10000, 0x00);
 }
 
-// Zero len with offset out-of-bounds gets an exception
+// Zero len with offset out-of-bounds is OK if it's at the edge of the
+// memory, but not if it is one past that.
 {
     let inst = wasmEvalText(
     `(module
        (memory (export "memory") 1 1)
        (func (export "testfn")
          (memory.fill (i32.const 0x10000) (i32.const 0x55) (i32.const 0))
        )
      )`
     );
+    inst.exports.testfn();
+}
+
+{
+    let inst = wasmEvalText(
+    `(module
+       (memory (export "memory") 1 1)
+       (func (export "testfn")
+         (memory.fill (i32.const 0x10001) (i32.const 0x55) (i32.const 0))
+       )
+     )`
+    );
     assertErrorMessage(() => inst.exports.testfn(),
                        WebAssembly.RuntimeError, /index out of bounds/);
 }
 
 // Very large range
 {
     let inst = wasmEvalText(
     `(module
@@ -710,40 +726,66 @@ function checkRange(arr, minIx, maxIxPlu
      )`
     );
     inst.exports.testfn();
     let b = new Uint8Array(inst.exports.memory.buffer);
     checkRange(b, 0x00000, 0x08000, 0x55);
     checkRange(b, 0x08000, 0x10000, 0xAA);
 }
 
-// Zero len with dest offset out-of-bounds is an exception
+// Zero len with dest offset out-of-bounds but at the edge of memory is OK
 {
     let inst = wasmEvalText(
     `(module
        (memory (export "memory") 1 1)
        (func (export "testfn")
          (memory.copy (i32.const 0x10000) (i32.const 0x7000) (i32.const 0))
        )
      )`
     );
+    inst.exports.testfn();
+}
+
+// Ditto, but one further out is not OK.
+{
+    let inst = wasmEvalText(
+    `(module
+       (memory (export "memory") 1 1)
+       (func (export "testfn")
+         (memory.copy (i32.const 0x10001) (i32.const 0x7000) (i32.const 0))
+       )
+     )`
+    );
     assertErrorMessage(() => inst.exports.testfn(),
                        WebAssembly.RuntimeError, /index out of bounds/);
 }
 
-// Zero len with src offset out-of-bounds is an exception
+// Zero len with src offset out-of-bounds but at the edge of memory is OK
 {
     let inst = wasmEvalText(
     `(module
        (memory (export "memory") 1 1)
        (func (export "testfn")
          (memory.copy (i32.const 0x9000) (i32.const 0x10000) (i32.const 0))
        )
      )`
     );
+    inst.exports.testfn();
+}
+
+// Ditto, but one element further out is not OK.
+{
+    let inst = wasmEvalText(
+    `(module
+       (memory (export "memory") 1 1)
+       (func (export "testfn")
+         (memory.copy (i32.const 0x9000) (i32.const 0x10001) (i32.const 0))
+       )
+     )`
+    );
     assertErrorMessage(() => inst.exports.testfn(),
                        WebAssembly.RuntimeError, /index out of bounds/);
 }
 
 // 100 random fills followed by 100 random copies, in a single-page buffer,
 // followed by verification of the (now heavily mashed-around) buffer.
 {
     let inst = wasmEvalText(
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -422,18 +422,19 @@ Instance::wake(Instance* instance, uint3
 
 /* static */ int32_t /* -1 to signal trap; 0 for ok */
 Instance::memCopy(Instance* instance, uint32_t dstByteOffset,
                   uint32_t srcByteOffset, uint32_t len) {
   WasmMemoryObject* mem = instance->memory();
   uint32_t memLen = mem->volatileMemoryLength();
 
   if (len == 0) {
-    // Even though the length is zero, we must check for a valid offset.
-    if (dstByteOffset < memLen && srcByteOffset < memLen) {
+    // Even though the length is zero, we must check for a valid offset.  But
+    // zero-length operations at the edge of the memory are allowed.
+    if (dstByteOffset <= memLen && srcByteOffset <= memLen) {
       return 0;
     }
   } else {
     // Here, we know that |len - 1| cannot underflow.
     bool mustTrap = false;
 
     // As we're supposed to write data until we trap we have to deal with
     // arithmetic overflow in the limit calculation.
@@ -508,18 +509,19 @@ Instance::dataDrop(Instance* instance, u
 
 /* static */ int32_t /* -1 to signal trap; 0 for ok */
 Instance::memFill(Instance* instance, uint32_t byteOffset, uint32_t value,
                   uint32_t len) {
   WasmMemoryObject* mem = instance->memory();
   uint32_t memLen = mem->volatileMemoryLength();
 
   if (len == 0) {
-    // Even though the length is zero, we must check for a valid offset.
-    if (byteOffset < memLen) {
+    // Even though the length is zero, we must check for a valid offset.  But
+    // zero-length operations at the edge of the memory are allowed.
+    if (byteOffset <= memLen) {
       return 0;
     }
   } else {
     // Here, we know that |len - 1| cannot underflow.
 
     bool mustTrap = false;
 
     // We must write data until we trap, so we have to deal with arithmetic
@@ -580,18 +582,20 @@ Instance::memInit(Instance* instance, ui
 
   // We are proposing to copy
   //
   //   seg.bytes.begin()[ srcOffset .. srcOffset + len - 1 ]
   // to
   //   memoryBase[ dstOffset .. dstOffset + len - 1 ]
 
   if (len == 0) {
-    // Even though the length is zero, we must check for valid offsets.
-    if (dstOffset < memLen && srcOffset < segLen) {
+    // Even though the length is zero, we must check for valid offsets.  But
+    // zero-length operations at the edge of the memory or the segment are
+    // allowed.
+    if (dstOffset <= memLen && srcOffset <= segLen) {
       return 0;
     }
   } else {
     // Here, we know that |len - 1| cannot underflow.
 
     bool mustTrap = false;
 
     // As we're supposed to write data until we trap we have to deal with
@@ -640,19 +644,20 @@ Instance::tableCopy(Instance* instance, 
                     uint32_t srcTableIndex) {
   const SharedTable& srcTable = instance->tables()[srcTableIndex];
   uint32_t srcTableLen = srcTable->length();
 
   const SharedTable& dstTable = instance->tables()[dstTableIndex];
   uint32_t dstTableLen = dstTable->length();
 
   if (len == 0) {
-    // Even though the number of items to copy is zero, we must check
-    // for valid offsets.
-    if (dstOffset < dstTableLen && srcOffset < srcTableLen) {
+    // Even though the number of items to copy is zero, we must check for valid
+    // offsets.  But zero-length operations at the edge of the table are
+    // allowed.
+    if (dstOffset <= dstTableLen && srcOffset <= srcTableLen) {
       return 0;
     }
   } else {
     // Here, we know that |len - 1| cannot underflow.
     bool mustTrap = false;
 
     // As we're supposed to write data until we trap we have to deal with
     // arithmetic overflow in the limit calculation.
@@ -804,18 +809,19 @@ Instance::tableInit(Instance* instance, 
 
   // We are proposing to copy
   //
   //   seg[ srcOffset .. srcOffset + len - 1 ]
   // to
   //   tableBase[ dstOffset .. dstOffset + len - 1 ]
 
   if (len == 0) {
-    // Even though the length is zero, we must check for valid offsets.
-    if (dstOffset < tableLen && srcOffset < segLen) {
+    // Even though the length is zero, we must check for valid offsets.  But
+    // zero-length operations at the edge of the table or segment are allowed.
+    if (dstOffset <= tableLen && srcOffset <= segLen) {
       return 0;
     }
   } else {
     // Here, we know that |len - 1| cannot underflow.
     bool mustTrap = false;
 
     // As we're supposed to write data until we trap we have to deal with
     // arithmetic overflow in the limit calculation.
--- a/layout/xul/nsMenuFrame.cpp
+++ b/layout/xul/nsMenuFrame.cpp
@@ -1263,16 +1263,23 @@ nsMenuFrame::GetActiveChild(dom::Element
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMenuFrame::SetActiveChild(dom::Element* aChild) {
   nsMenuPopupFrame* popupFrame = GetPopup();
   if (!popupFrame) return NS_ERROR_FAILURE;
 
+  // Force the child frames within the popup to be generated.
+  AutoWeakFrame weakFrame(popupFrame);
+  popupFrame->GenerateFrames();
+  if (!weakFrame.IsAlive()) {
+    return NS_OK;
+  }
+
   if (!aChild) {
     // Remove the current selection
     popupFrame->ChangeMenuItem(nullptr, false, false);
     return NS_OK;
   }
 
   nsMenuFrame* menu = do_QueryFrame(aChild->GetPrimaryFrame());
   if (menu) popupFrame->ChangeMenuItem(menu, false, false);
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -1607,16 +1607,28 @@ nsresult nsMenuPopupFrame::SetPopupPosit
     if (aNotify) {
       nsXULPopupPositionedEvent::DispatchIfNeeded(mContent, false, false);
     }
   }
 
   return NS_OK;
 }
 
+void nsMenuPopupFrame::GenerateFrames()
+{
+  const bool generateFrames = IsLeaf();
+  MOZ_ASSERT_IF(generateFrames, !mGeneratedChildren);
+  mGeneratedChildren = true;
+  if (generateFrames) {
+    MOZ_ASSERT(PrincipalChildList().IsEmpty());
+    nsCOMPtr<nsIPresShell> presShell = PresContext()->PresShell();
+    presShell->FrameConstructor()->GenerateChildFrames(this);
+  }
+}
+
 /* virtual */
 nsMenuFrame* nsMenuPopupFrame::GetCurrentMenuItem() { return mCurrentMenu; }
 
 LayoutDeviceIntRect nsMenuPopupFrame::GetConstraintRect(
     const LayoutDeviceIntRect& aAnchorRect,
     const LayoutDeviceIntRect& aRootScreenRect, nsPopupLevel aPopupLevel) {
   LayoutDeviceIntRect screenRectPixels;
 
--- a/layout/xul/nsMenuPopupFrame.h
+++ b/layout/xul/nsMenuPopupFrame.h
@@ -261,18 +261,18 @@ class nsMenuPopupFrame final : public ns
   // (or the frame for mAnchorContent if aAnchorFrame is null), anchored at a
   // rectangle, or at a specific point if a screen position is set. The popup
   // will be adjusted so that it is on screen. If aIsMove is true, then the
   // popup is being moved, and should not be flipped. If aNotify is true, then
   // a popuppositioned event is sent.
   nsresult SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove,
                             bool aSizedToPopup, bool aNotify);
 
-  bool HasGeneratedChildren() { return mGeneratedChildren; }
-  void SetGeneratedChildren() { mGeneratedChildren = true; }
+  // Force the children to be generated if they have not already been generated.
+  void GenerateFrames();
 
   // called when the Enter key is pressed while the popup is open. This will
   // just pass the call down to the current menu, if any. If a current menu
   // should be opened as a result, this method should return the frame for
   // that menu, or null if no menu should be opened. Also, calling Enter will
   // reset the current incremental search string, calculated in
   // FindMenuWithShortcut.
   nsMenuFrame* Enter(mozilla::WidgetGUIEvent* aEvent);
--- a/layout/xul/nsXULPopupManager.cpp
+++ b/layout/xul/nsXULPopupManager.cpp
@@ -1274,36 +1274,29 @@ void nsXULPopupManager::FirePopupShowing
                                               bool aIsContextMenu,
                                               bool aSelectFirstItem,
                                               Event* aTriggerEvent) {
   nsCOMPtr<nsIContent> popup = aPopup;  // keep a strong reference to the popup
 
   nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
   if (!popupFrame) return;
 
+  popupFrame->GenerateFrames();
+
+  // get the frame again
+  popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
+  if (!popupFrame) return;
+
   nsPresContext* presContext = popupFrame->PresContext();
   nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
+  presShell->FrameNeedsReflow(popupFrame, nsIPresShell::eTreeChange,
+                              NS_FRAME_HAS_DIRTY_CHILDREN);
+
   nsPopupType popupType = popupFrame->PopupType();
 
-  // generate the child frames if they have not already been generated
-  const bool generateFrames = popupFrame->IsLeaf();
-  MOZ_ASSERT_IF(generateFrames, !popupFrame->HasGeneratedChildren());
-  popupFrame->SetGeneratedChildren();
-  if (generateFrames) {
-    MOZ_ASSERT(popupFrame->PrincipalChildList().IsEmpty());
-    presShell->FrameConstructor()->GenerateChildFrames(popupFrame);
-  }
-
-  // get the frame again
-  nsIFrame* frame = aPopup->GetPrimaryFrame();
-  if (!frame) return;
-
-  presShell->FrameNeedsReflow(frame, nsIPresShell::eTreeChange,
-                              NS_FRAME_HAS_DIRTY_CHILDREN);
-
   // cache the popup so that document.popupNode can retrieve the trigger node
   // during the popupshowing event. It will be cleared below after the event
   // has fired.
   mOpeningPopup = aPopup;
 
   nsEventStatus status = nsEventStatus_eIgnore;
   WidgetMouseEvent event(true, eXULPopupShowing, nullptr,
                          WidgetMouseEvent::eReal);
new file mode 100644
--- /dev/null
+++ b/testing/geckodriver/doc/ARM.md
@@ -0,0 +1,39 @@
+Self-serving an ARM build
+=========================
+
+Mozilla [announced the intent] to deprecate ARMv7 HF builds of
+geckodriver in September 2018.  This does not mean you can no longer
+use geckodriver on ARM systems, and this document explains how you
+can self-service a build for ARMv7 HF.
+
+Assuming you have already checked out [central], the steps to
+cross-compile ARMv7 from a Linux host system is as follows:
+
+  1. If you don’t have Rust installed:
+
+         # curl https://sh.rustup.rs -sSf | sh
+
+  2. Install cross-compiler toolchain:
+
+         # apt install gcc-arm-linux-gnueabihf libc6-armhf-cross libc6-dev-armhf-cross
+
+  3. Createa a new shell, or to reuse the existing shell:
+
+         source $HOME/.cargo/env
+
+  4. Install rustc target toolchain:
+
+         % rustup target install armv7-unknown-linux-gnueabihf
+
+  5. Put this in testing/geckodriver/.cargo/config:
+
+         [target.armv7-unknown-linux-gnueabihf]
+         linker = "arm-linux-gnueabihf-gcc"
+
+  6. Build geckodriver from testing/geckodriver:
+
+         % cd testing/geckodriver
+         % cargo build --release --target armv7-unknown-linux-gnueabihf
+
+[announce the intent]: https://lists.mozilla.org/pipermail/tools-marionette/2018-September/000035.html
+[central]: https://hg.mozilla.org/mozilla-central/
--- a/testing/geckodriver/doc/index.rst
+++ b/testing/geckodriver/doc/index.rst
@@ -37,16 +37,17 @@ For users
 For developers
 ==============
 .. toctree::
    :maxdepth: 1
 
    Building.md
    Testing.md
    Releasing.md
+   ARM.md
 
 
 Communication
 =============
 
 The mailing list for geckodriver discussion is
 tools-marionette@lists.mozilla.org (`subscribe`_, `archive`_).
 
--- a/toolkit/content/tests/chrome/test_menulist_keynav.xul
+++ b/toolkit/content/tests/chrome/test_menulist_keynav.xul
@@ -22,16 +22,24 @@
 <menulist id="list2">
   <menupopup id="popup" onpopupshown="checkCursorNavigation();">
     <menuitem id="b1" label="One"/>
     <menuitem id="b2" label="Two" selected="true"/>
     <menuitem id="b3" label="Three"/>
     <menuitem id="b4" label="Four"/>
   </menupopup>
 </menulist>
+<menulist id="list3" sizetopopup="none">
+  <menupopup>
+    <menuitem id="s1" label="One"/>
+    <menuitem id="s2" label="Two"/>
+    <menuitem id="s3" label="Three"/>
+    <menuitem id="s4" label="Four"/>
+  </menupopup>
+</menulist>
 
 <script class="testbody" type="application/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 
 var gShowPopup = false;
 var gModifiers = 0;
@@ -270,16 +278,26 @@ function checkCursorNavigation()
 
   synthesizeKey("KEY_ArrowUp", {altKey: true});
   is(list.open, ismac, "alt+up closes popup");
 
   if (ismac) {
     list.open = false;
   }
 
+  // Finally, test a menulist with sizetopopup="none" to ensure keyboard navigation
+  // still works when the popup has not been opened.
+  if (!ismac) {
+    let unsizedMenulist = document.getElementById("list3");
+    unsizedMenulist.focus();
+    synthesizeKey("KEY_ArrowDown");
+    is(unsizedMenulist.selectedIndex, 1, "correct menulist index on keydown");
+    is(unsizedMenulist.label, "Two", "correct menulist label on keydown");
+  }
+
   SimpleTest.finish();
 }
 
 SimpleTest.waitForFocus(runTests);
 
 ]]>
 </script>