Merge mozilla-central to autoland. a=merge CLOSED TREE
authorshindli <shindli@mozilla.com>
Tue, 05 Mar 2019 23:46:24 +0200
changeset 520321 5c6fc3df47377fcdc94d51861b4f3fa9a70acfdc
parent 520320 10f3bcf2a0ed1a35764e8ac1a48d76042ba1ef78 (current diff)
parent 520279 996a48b306521112d483b3bd8bede1e4fa23d3fa (diff)
child 520322 e2b8ea7e00d9ccb32e57775b343667bf833e50ab
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
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 autoland. a=merge CLOSED TREE
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>