Bug 1469464 - Consistent PaymentRequest footer positioning with <payment-request-page>. r=sfoster
☠☠ backed out by 135aaab3fef5 ☠ ☠
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Wed, 11 Jul 2018 23:43:27 -0700
changeset 426273 58c1a79fb661a55e2b4cf0eeed1fb383cc219614
parent 426272 90654d5df81a7cddc14cca127f3e572addb80427
child 426274 59c4189297640ec9c2bdbb2d3bb7b617cb74e6e5
push id34270
push userapavel@mozilla.com
push dateThu, 12 Jul 2018 21:50:22 +0000
treeherdermozilla-central@ccdb64ade35f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfoster
bugs1469464
milestone63.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
Bug 1469464 - Consistent PaymentRequest footer positioning with <payment-request-page>. r=sfoster MozReview-Commit-ID: Oq06q6xF0e
browser/components/payments/docs/index.rst
browser/components/payments/res/components/payment-request-page.js
browser/components/payments/res/containers/address-form.js
browser/components/payments/res/containers/basic-card-form.js
browser/components/payments/res/containers/payment-dialog.js
browser/components/payments/res/debugging.css
browser/components/payments/res/paymentRequest.css
browser/components/payments/res/paymentRequest.xhtml
browser/components/payments/test/mochitest/test_address_form.html
browser/components/payments/test/mochitest/test_basic_card_form.html
browser/components/payments/test/mochitest/test_payment_dialog.html
browser/extensions/formautofill/content/autofillEditForms.js
--- a/browser/components/payments/docs/index.rst
+++ b/browser/components/payments/docs/index.rst
@@ -9,17 +9,17 @@ JSDoc style comments are used within the
 .. toctree::
    :maxdepth: 5
 
 
 Debugging/Development
 =====================
 
 Must Have Electrolysis
--------
+----------------------
 
 Web Payments `does not work without e10s <https://bugzilla.mozilla.org/show_bug.cgi?id=1365964>`_!
 
 Logging
 -------
 
 Set the pref ``dom.payments.loglevel`` to "Debug" to increase the verbosity of console messages.
 
@@ -64,8 +64,24 @@ In order to communicate across the proce
 This is because the unprivileged document cannot access message managers.
 Instead, all communication across the privileged/unprivileged boundary is done via custom DOM events:
 
 * A ``paymentContentToChrome`` event is dispatched when the dialog contents want to communicate with the privileged dialog wrapper.
 * A ``paymentChromeToContent`` event is dispatched on the ``window`` with the ``detail`` property populated when the privileged dialog wrapper communicates with the unprivileged dialog.
 
 These events are converted to/from message manager messages of the same name to communicate to the other process.
 The purpose of `paymentDialogFrameScript.js` is to simply convert unprivileged DOM events to/from messages from the other process.
+
+Custom Elements
+---------------
+
+The Payment Request UI uses Custom Elements for the UI components.
+
+Some guidelines:
+* If you're overriding a lifecycle callback, don't forget to call that method on
+  ``super`` from the implementation to ensure that mixins and ancestor classes
+  work properly.
+* From within a custom element, don't use ``document.getElementById`` or
+  ``document.querySelector*`` because they can return elements that are outside
+  of the component, thus breaking the modularization. It can also cause problems
+  if the elements you're looking for aren't attached to the document yet. Use
+  ``querySelector*`` on ``this`` (the custom element) or one of its descendants
+  instead.
new file mode 100644
--- /dev/null
+++ b/browser/components/payments/res/components/payment-request-page.js
@@ -0,0 +1,33 @@
+/* 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/. */
+
+/**
+ * <payment-request-page></payment-request-page>
+ */
+
+export default class PaymentRequestPage extends HTMLElement {
+  constructor() {
+    super();
+
+    this.classList.add("page");
+
+    this.pageTitleHeading = document.createElement("h2");
+
+    // The body and footer may be pre-defined in the template so re-use them if they exist.
+    this.body = this.querySelector(":scope > .page-body") || document.createElement("div");
+    this.body.classList.add("page-body");
+
+    this.footer = this.querySelector(":scope > footer") || document.createElement("footer");
+  }
+
+  connectedCallback() {
+    // The heading goes inside the body so it scrolls.
+    this.body.prepend(this.pageTitleHeading);
+    this.appendChild(this.body);
+
+    this.appendChild(this.footer);
+  }
+}
+
+customElements.define("payment-request-page", PaymentRequestPage);
--- a/browser/components/payments/res/containers/address-form.js
+++ b/browser/components/payments/res/containers/address-form.js
@@ -1,30 +1,34 @@
 /* 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/. */
 
 /* import-globals-from ../../../../../browser/extensions/formautofill/content/autofillEditForms.js*/
 import LabelledCheckbox from "../components/labelled-checkbox.js";
+import PaymentRequestPage from "../components/payment-request-page.js";
 import PaymentStateSubscriberMixin from "../mixins/PaymentStateSubscriberMixin.js";
 import paymentRequest from "../paymentRequest.js";
 /* import-globals-from ../unprivileged-fallbacks.js */
 
 /**
  * <address-form></address-form>
  *
+ * Don't use document.getElementById or document.querySelector* to access form
+ * elements, use querySelector on `this` or `this.form` instead so that elements
+ * can be found before the element is connected.
+ *
  * XXX: Bug 1446164 - This form isn't localized when used via this custom element
  * as it will be much easier to share the logic once we switch to Fluent.
  */
 
-export default class AddressForm extends PaymentStateSubscriberMixin(HTMLElement) {
+export default class AddressForm extends PaymentStateSubscriberMixin(PaymentRequestPage) {
   constructor() {
     super();
 
-    this.pageTitle = document.createElement("h2");
     this.genericErrorText = document.createElement("div");
 
     this.cancelButton = document.createElement("button");
     this.cancelButton.className = "cancel-button";
     this.cancelButton.addEventListener("click", this);
 
     this.backButton = document.createElement("button");
     this.backButton.className = "back-button";
@@ -68,33 +72,33 @@ export default class AddressForm extends
       });
       xhr.open("GET", url);
       xhr.send();
     });
   }
 
   connectedCallback() {
     this.promiseReady.then(form => {
-      this.appendChild(this.pageTitle);
-      this.appendChild(form);
+      this.body.appendChild(form);
 
       let record = {};
       this.formHandler = new EditAddress({
         form,
       }, record, {
         DEFAULT_REGION: PaymentDialogUtils.DEFAULT_REGION,
         getFormFormat: PaymentDialogUtils.getFormFormat,
         supportedCountries: PaymentDialogUtils.supportedCountries,
       });
 
-      this.appendChild(this.persistCheckbox);
-      this.appendChild(this.genericErrorText);
-      this.appendChild(this.cancelButton);
-      this.appendChild(this.backButton);
-      this.appendChild(this.saveButton);
+      this.body.appendChild(this.persistCheckbox);
+      this.body.appendChild(this.genericErrorText);
+
+      this.footer.appendChild(this.cancelButton);
+      this.footer.appendChild(this.backButton);
+      this.footer.appendChild(this.saveButton);
       // Only call the connected super callback(s) once our markup is fully
       // connected, including the shared form fetched asynchronously.
       super.connectedCallback();
     });
   }
 
   render(state) {
     let record = {};
@@ -118,17 +122,17 @@ export default class AddressForm extends
     this.cancelButton.hidden = !page.onboardingWizard;
 
     if (addressPage.addressFields) {
       this.setAttribute("address-fields", addressPage.addressFields);
     } else {
       this.removeAttribute("address-fields");
     }
 
-    this.pageTitle.textContent = addressPage.title;
+    this.pageTitleHeading.textContent = addressPage.title;
     this.genericErrorText.textContent = page.error;
 
     let editing = !!addressPage.guid;
     let addresses = paymentRequest.getAddresses(state);
 
     // If an address is selected we want to edit it.
     if (editing) {
       record = addresses[addressPage.guid];
@@ -156,18 +160,18 @@ export default class AddressForm extends
         container.setAttribute("required", "true");
       } else {
         container.removeAttribute("required");
       }
     }
 
     let shippingAddressErrors = request.paymentDetails.shippingAddressErrors;
     for (let [errorName, errorSelector] of Object.entries(this._errorFieldMap)) {
-      let container = document.querySelector(errorSelector + "-container");
-      let field = document.querySelector(errorSelector);
+      let container = this.form.querySelector(errorSelector + "-container");
+      let field = this.form.querySelector(errorSelector);
       let errorText = (shippingAddressErrors && shippingAddressErrors[errorName]) || "";
       container.classList.toggle("error", !!errorText);
       field.setCustomValidity(errorText);
       let span = container.querySelector(".error-text");
       if (!span) {
         span = document.createElement("span");
         span.className = "error-text";
         container.appendChild(span);
--- a/browser/components/payments/res/containers/basic-card-form.js
+++ b/browser/components/payments/res/containers/basic-card-form.js
@@ -1,56 +1,59 @@
 /* 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/. */
 
 /* import-globals-from ../../../../../browser/extensions/formautofill/content/autofillEditForms.js*/
 import LabelledCheckbox from "../components/labelled-checkbox.js";
+import PaymentRequestPage from "../components/payment-request-page.js";
 import PaymentStateSubscriberMixin from "../mixins/PaymentStateSubscriberMixin.js";
 import paymentRequest from "../paymentRequest.js";
 
 /* import-globals-from ../unprivileged-fallbacks.js */
 
 /**
  * <basic-card-form></basic-card-form>
  *
  * XXX: Bug 1446164 - This form isn't localized when used via this custom element
  * as it will be much easier to share the logic once we switch to Fluent.
  */
 
-export default class BasicCardForm extends PaymentStateSubscriberMixin(HTMLElement) {
+export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRequestPage) {
   constructor() {
     super();
 
-    this.pageTitle = document.createElement("h2");
     this.genericErrorText = document.createElement("div");
 
-    this.cancelButton = document.createElement("button");
-    this.cancelButton.className = "cancel-button";
-    this.cancelButton.addEventListener("click", this);
-
     this.addressAddLink = document.createElement("a");
     this.addressAddLink.className = "add-link";
     this.addressAddLink.href = "javascript:void(0)";
     this.addressAddLink.addEventListener("click", this);
     this.addressEditLink = document.createElement("a");
     this.addressEditLink.className = "edit-link";
     this.addressEditLink.href = "javascript:void(0)";
     this.addressEditLink.addEventListener("click", this);
 
+    this.persistCheckbox = new LabelledCheckbox();
+    this.persistCheckbox.className = "persist-checkbox";
+
+    // page footer
+    this.cancelButton = document.createElement("button");
+    this.cancelButton.className = "cancel-button";
+    this.cancelButton.addEventListener("click", this);
+
     this.backButton = document.createElement("button");
     this.backButton.className = "back-button";
     this.backButton.addEventListener("click", this);
 
     this.saveButton = document.createElement("button");
     this.saveButton.className = "save-button primary";
     this.saveButton.addEventListener("click", this);
 
-    this.persistCheckbox = new LabelledCheckbox();
-    this.persistCheckbox.className = "persist-checkbox";
+    this.footer.append(this.cancelButton, this.backButton, this.saveButton);
 
     // The markup is shared with form autofill preferences.
     let url = "formautofill/editCreditCard.xhtml";
     this.promiseReady = this._fetchMarkup(url).then(doc => {
       this.form = doc.getElementById("form");
       return this.form;
     });
   }
@@ -65,18 +68,17 @@ export default class BasicCardForm exten
       });
       xhr.open("GET", url);
       xhr.send();
     });
   }
 
   connectedCallback() {
     this.promiseReady.then(form => {
-      this.appendChild(this.pageTitle);
-      this.appendChild(form);
+      this.body.appendChild(form);
 
       let record = {};
       let addresses = [];
       this.formHandler = new EditCreditCard({
         form,
       }, record, addresses, {
         isCCNumber: PaymentDialogUtils.isCCNumber,
         getAddressLabel: PaymentDialogUtils.getAddressLabel,
@@ -84,21 +86,18 @@ export default class BasicCardForm exten
 
       let fragment = document.createDocumentFragment();
       fragment.append(this.addressAddLink);
       fragment.append(" ");
       fragment.append(this.addressEditLink);
       let billingAddressRow = this.form.querySelector(".billingAddressRow");
       billingAddressRow.appendChild(fragment);
 
-      this.appendChild(this.persistCheckbox);
-      this.appendChild(this.genericErrorText);
-      this.appendChild(this.cancelButton);
-      this.appendChild(this.backButton);
-      this.appendChild(this.saveButton);
+      this.body.appendChild(this.persistCheckbox);
+      this.body.appendChild(this.genericErrorText);
       // Only call the connected super callback(s) once our markup is fully
       // connected, including the shared form fetched asynchronously.
       super.connectedCallback();
     });
   }
 
   render(state) {
     let {
@@ -130,25 +129,25 @@ export default class BasicCardForm exten
 
     this.genericErrorText.textContent = page.error;
 
     let editing = !!basicCardPage.guid;
     this.form.querySelector("#cc-number").disabled = editing;
 
     // If a card is selected we want to edit it.
     if (editing) {
-      this.pageTitle.textContent = this.dataset.editBasicCardTitle;
+      this.pageTitleHeading.textContent = this.dataset.editBasicCardTitle;
       record = basicCards[basicCardPage.guid];
       if (!record) {
         throw new Error("Trying to edit a non-existing card: " + basicCardPage.guid);
       }
       // When editing an existing record, prevent changes to persistence
       this.persistCheckbox.hidden = true;
     } else {
-      this.pageTitle.textContent = this.dataset.addBasicCardTitle;
+      this.pageTitleHeading.textContent = this.dataset.addBasicCardTitle;
       // Use a currently selected shipping address as the default billing address
       record.billingAddressGUID = basicCardPage.billingAddressGUID;
       if (!record.billingAddressGUID && selectedShippingAddress) {
         record.billingAddressGUID = selectedShippingAddress;
       }
       // Adding a new record: default persistence to checked when in a not-private session
       this.persistCheckbox.hidden = false;
       this.persistCheckbox.checked = !state.isPrivate;
--- a/browser/components/payments/res/containers/payment-dialog.js
+++ b/browser/components/payments/res/containers/payment-dialog.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import "../vendor/custom-elements.min.js";
 
 import PaymentStateSubscriberMixin from "../mixins/PaymentStateSubscriberMixin.js";
 import paymentRequest from "../paymentRequest.js";
 
 import "../components/currency-amount.js";
+import "../components/payment-request-page.js";
 import "./address-picker.js";
 import "./address-form.js";
 import "./basic-card-form.js";
 import "./order-details.js";
 import "./payment-method-picker.js";
 import "./shipping-option-picker.js";
 
 /* import-globals-from ../unprivileged-fallbacks.js */
--- a/browser/components/payments/res/debugging.css
+++ b/browser/components/payments/res/debugging.css
@@ -1,16 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 html {
-  /* Based on global.css styles for top-level XUL windows */
-  -moz-appearance: dialog;
-  background-color: -moz-Dialog;
   color: -moz-DialogText;
   font: message-box;
   /* Make sure the background ends to the bottom if there is unused space */
   height: 100%;
 }
 
 h1 {
   font-size: 1em;
--- a/browser/components/payments/res/paymentRequest.css
+++ b/browser/components/payments/res/paymentRequest.css
@@ -1,96 +1,93 @@
 /* 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/. */
 
 html {
-  /* Based on global.css styles for top-level XUL windows */
-  color: -moz-DialogText;
-  font: message-box;
   height: 100%;
 }
 
 body {
-  /* Override font-size from in-content/common.css which is too large */
-  font-size: inherit;
-}
-
-#order-details-overlay,
-html {
-  /* Based on global.css styles for top-level XUL windows */
-  -moz-appearance: dialog;
-  background-color: -moz-Dialog;
-}
-
-body {
   height: 100%;
   margin: 0;
-  overflow: hidden;
+  /* Override font-size from in-content/common.css which is too large */
+  font-size: inherit;
 }
 
 [hidden] {
   display: none !important;
 }
 
 #debugging-console {
   /* include the default borders in the max-height */
   box-sizing: border-box;
   float: right;
-  /* avoid causing the body to scroll */
-  max-height: 100vh;
+  height: 100vh;
   /* Float above the other overlays */
   position: relative;
   z-index: 99;
 }
 
 payment-dialog {
   box-sizing: border-box;
   display: grid;
-  grid-template-rows: fit-content(10%) auto;
+  grid-template: "header" auto
+                 "main"   1fr;
+                 "disabled-overlay" auto;
   height: 100%;
   margin: 0 10%;
   padding: 1em;
 }
 
 payment-dialog > header {
   display: flex;
 }
 
 #main-container {
   display: flex;
+  grid-area: main;
   position: relative;
+  max-height: 100%;
 }
 
-#payment-summary {
-  display: grid;
-  flex: 1 1 auto;
-  grid-template-rows: fit-content(10%) auto fit-content(10%);
+.page {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
   position: relative;
+  width: 100%;
+}
+
+.page > .page-body {
+  /* The area above the footer should scroll, if necessary. */
+  overflow: auto;
+}
+
+.page > footer {
+  align-items: end;
+  display: flex;
+  flex-grow: 1;
 }
 
 #error-text {
   text-align: center;
 }
 
 #order-details-overlay {
+  background-color: var(--in-content-page-background);
   overflow: auto;
   position: absolute;
   top: 0;
   right: 0;
   bottom: 0;
   left: 0;
   z-index: 1;
 }
 
-payment-dialog > footer {
-  align-items: baseline;
-  display: flex;
-}
-
 #total {
   flex: 1 1 auto;
   margin: 5px;
 }
 
 #total > currency-amount > .currency-code {
   color: GrayText;
 }
--- a/browser/components/payments/res/paymentRequest.xhtml
+++ b/browser/components/payments/res/paymentRequest.xhtml
@@ -87,18 +87,18 @@
         <div>&header.payTo; <span id="host-name"></span></div>
       </div>
       <div id="top-buttons" hidden="hidden">
         <button id="view-all" class="closed">&viewAllItems;</button>
       </div>
     </header>
 
     <div id="main-container">
-      <section id="payment-summary" class="page">
-        <section>
+      <payment-request-page id="payment-summary">
+        <div class="page-body">
           <div id="error-text"></div>
 
           <div class="shipping-related"
                id="shipping-type-label"
                data-shipping-address-label="&shippingAddressLabel;"
                data-delivery-address-label="&deliveryAddressLabel;"
                data-persist-checkbox-label="&addressPage.persistCheckbox.label;"
                data-pickup-address-label="&pickupAddressLabel;"><label></label></div>
@@ -117,51 +117,49 @@
           </payment-method-picker>
 
           <div class="payer-related"><label>&payerLabel;</label></div>
           <address-picker class="payer-related"
                           data-add-link-label="&payer.addLink.label;"
                           data-edit-link-label="&payer.editLink.label;"
                           selected-state-key="selectedPayerAddress"></address-picker>
           <div id="error-text"></div>
-        </section>
+        </div>
 
-        <footer id="controls-container">
+        <footer>
           <button id="cancel">&cancelPaymentButton.label;</button>
           <button id="pay"
                   class="primary"
                   data-initial-label="&approvePaymentButton.label;"
                   data-processing-label="&processingPaymentButton.label;"
                   data-fail-label="&failPaymentButton.label;"
                   data-unknown-label="&unknownPaymentButton.label;"
                   data-success-label="&successPaymentButton.label;"></button>
         </footer>
-      </section>
+      </payment-request-page>
       <section id="order-details-overlay" hidden="hidden">
         <h2>&orderDetailsLabel;</h2>
         <order-details></order-details>
       </section>
 
       <basic-card-form id="basic-card-page"
-                       class="page"
                        data-add-basic-card-title="&basicCard.addPage.title;"
                        data-edit-basic-card-title="&basicCard.editPage.title;"
                        data-error-generic-save="&basicCardPage.error.genericSave;"
                        data-address-add-link-label="&basicCardPage.addressAddLink.label;"
                        data-address-edit-link-label="&basicCardPage.addressEditLink.label;"
                        data-billing-address-title-add="&billingAddress.addPage.title;"
                        data-billing-address-title-edit="&billingAddress.editPage.title;"
                        data-back-button-label="&basicCardPage.backButton.label;"
                        data-save-button-label="&basicCardPage.saveButton.label;"
                        data-cancel-button-label="&cancelPaymentButton.label;"
                        data-persist-checkbox-label="&basicCardPage.persistCheckbox.label;"
                        hidden="hidden"></basic-card-form>
 
       <address-form id="address-page"
-                    class="page"
                     data-error-generic-save="&addressPage.error.genericSave;"
                     data-cancel-button-label="&addressPage.cancelButton.label;"
                     data-back-button-label="&addressPage.backButton.label;"
                     data-save-button-label="&addressPage.saveButton.label;"
                     data-persist-checkbox-label="&addressPage.persistCheckbox.label;"
                     hidden="hidden"></address-form>
     </div>
 
@@ -177,18 +175,18 @@
     <div class="details-total">
       <h2 class="label">&orderTotalLabel;</h2>
       <currency-amount></currency-amount>
     </div>
   </template>
 </head>
 <body dir="&locale.dir;">
   <iframe id="debugging-console"
-          hidden="hidden"
-          height="400"></iframe>
+          hidden="hidden">
+  </iframe>
   <payment-dialog data-shipping-address-title-add="&shippingAddress.addPage.title;"
                   data-shipping-address-title-edit="&shippingAddress.editPage.title;"
                   data-delivery-address-title-add="&deliveryAddress.addPage.title;"
                   data-delivery-address-title-edit="&deliveryAddress.editPage.title;"
                   data-pickup-address-title-add="&pickupAddress.addPage.title;"
                   data-pickup-address-title-edit="&pickupAddress.editPage.title;"
                   data-billing-address-title-add="&billingAddress.addPage.title;"
                   data-payer-title-add="&payer.addPage.title;"
--- a/browser/components/payments/test/mochitest/test_address_form.html
+++ b/browser/components/payments/test/mochitest/test_address_form.html
@@ -89,17 +89,17 @@ add_task(async function test_backButton(
       title: "Sample page title",
     },
   });
   await form.promiseReady;
   display.appendChild(form);
   await asyncElementRendered();
 
   let stateChangePromise = promiseStateChange(form.requestStore);
-  is(form.pageTitle.textContent, "Sample page title", "Check label");
+  is(form.pageTitleHeading.textContent, "Sample page title", "Check label");
 
   is(form.backButton.textContent, "Back", "Check label");
   form.backButton.scrollIntoView();
   synthesizeMouseAtCenter(form.backButton, {});
 
   let {page} = await stateChangePromise;
   is(page.id, "payment-summary", "Check initial page after appending");
 
--- a/browser/components/payments/test/mochitest/test_basic_card_form.html
+++ b/browser/components/payments/test/mochitest/test_basic_card_form.html
@@ -73,17 +73,17 @@ add_task(async function test_backButton(
     "basic-card-page": {
     },
   });
   await form.promiseReady;
   display.appendChild(form);
   await asyncElementRendered();
 
   let stateChangePromise = promiseStateChange(form.requestStore);
-  is(form.pageTitle.textContent, "Sample page title 2", "Check title");
+  is(form.pageTitleHeading.textContent, "Sample page title 2", "Check title");
   is(form.backButton.textContent, "Back", "Check label");
   synthesizeMouseAtCenter(form.backButton, {});
 
   let {page} = await stateChangePromise;
   is(page.id, "payment-summary", "Check initial page after appending");
 
   form.remove();
 });
--- a/browser/components/payments/test/mochitest/test_payment_dialog.html
+++ b/browser/components/payments/test/mochitest/test_payment_dialog.html
@@ -177,16 +177,29 @@ add_task(async function test_completionS
     ok(payButton.disabled, "Button is disabled");
     let rect = payButton.getBoundingClientRect();
     let visibleElement =
       document.elementFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);
     ok(payButton === visibleElement, "Pay button is on top of the overlay");
   }
 });
 
+add_task(async function test_scrollPaymentRequestPage() {
+  await setup();
+  info("making the payment-dialog container small to require scrolling");
+  el1.parentElement.style.height = "100px";
+  let summaryPageBody = document.querySelector("#payment-summary .page-body");
+  is(summaryPageBody.scrollTop, 0, "Page body not scrolled initially");
+  let securityCodeInput = summaryPageBody.querySelector("payment-method-picker input");
+  securityCodeInput.focus();
+  await new Promise(resolve => SimpleTest.executeSoon(resolve));
+  ok(summaryPageBody.scrollTop > 0, "Page body scrolled after focusing the CVV field");
+  el1.parentElement.style.height = "";
+});
+
 add_task(async function test_disconnect() {
   await setup();
 
   el1.remove();
   await el1.requestStore.setState({orderDetailsShowing: true});
   await asyncElementRendered();
   ok(el1.stateChangeCallback.notCalled, "stateChangeCallback not called");
   ok(el1.render.notCalled, "render not called");
--- a/browser/extensions/formautofill/content/autofillEditForms.js
+++ b/browser/extensions/formautofill/content/autofillEditForms.js
@@ -134,42 +134,42 @@ class EditAddress extends EditAutofillFo
       "street-address",
       "address-level2",
       "address-level1",
       "postal-code",
     ];
     let inputs = [];
     for (let i = 0; i < fieldsOrder.length; i++) {
       let {fieldId, newLine} = fieldsOrder[i];
-      let container = document.getElementById(`${fieldId}-container`);
+      let container = this._elements.form.querySelector(`#${fieldId}-container`);
       let containerInputs = [...container.querySelectorAll("input, textarea, select")];
       containerInputs.forEach(function(input) { input.disabled = false; });
       inputs.push(...containerInputs);
       container.style.display = "flex";
       container.style.order = i;
       container.style.pageBreakAfter = newLine ? "always" : "auto";
       // Remove the field from the list of fields
       fields.splice(fields.indexOf(fieldId), 1);
     }
     for (let i = 0; i < inputs.length; i++) {
       // Assign tabIndex starting from 1
       inputs[i].tabIndex = i + 1;
     }
     // Hide the remaining fields
     for (let field of fields) {
-      let container = document.getElementById(`${field}-container`);
+      let container = this._elements.form.querySelector(`#${field}-container`);
       container.style.display = "none";
       for (let input of [...container.querySelectorAll("input, textarea, select")]) {
         input.disabled = true;
       }
     }
   }
 
   updatePostalCodeValidation(postalCodePattern) {
-    let postalCodeInput = document.getElementById("postal-code");
+    let postalCodeInput = this._elements.form.querySelector("#postal-code");
     if (postalCodePattern && postalCodeInput.style.display != "none") {
       postalCodeInput.setAttribute("pattern", postalCodePattern);
     } else {
       postalCodeInput.removeAttribute("pattern");
     }
   }
 
   populateCountries() {