зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1469464 - Consistent PaymentRequest footer positioning with <payment-request-page>. r=sfoster
MozReview-Commit-ID: Oq06q6xF0e --HG-- extra : rebase_source : 76ee5c4a8e23db6a24e755e09eff05cdf0ae9f52
This commit is contained in:
Родитель
6bab329a7b
Коммит
798c0466a9
|
@ -14,7 +14,7 @@ Debugging/Development
|
|||
=====================
|
||||
|
||||
Must Have Electrolysis
|
||||
-------
|
||||
----------------------
|
||||
|
||||
Web Payments `does not work without e10s <https://bugzilla.mozilla.org/show_bug.cgi?id=1365964>`_!
|
||||
|
||||
|
@ -69,3 +69,19 @@ Instead, all communication across the privileged/unprivileged boundary is done v
|
|||
|
||||
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.
|
||||
|
|
|
@ -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);
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
/* 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 */
|
||||
|
@ -11,15 +12,18 @@ import paymentRequest from "../paymentRequest.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");
|
||||
|
@ -73,8 +77,7 @@ export default class AddressForm extends PaymentStateSubscriberMixin(HTMLElement
|
|||
|
||||
connectedCallback() {
|
||||
this.promiseReady.then(form => {
|
||||
this.appendChild(this.pageTitle);
|
||||
this.appendChild(form);
|
||||
this.body.appendChild(form);
|
||||
|
||||
let record = {};
|
||||
this.formHandler = new EditAddress({
|
||||
|
@ -85,11 +88,12 @@ export default class AddressForm extends PaymentStateSubscriberMixin(HTMLElement
|
|||
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();
|
||||
|
@ -123,7 +127,7 @@ export default class AddressForm extends PaymentStateSubscriberMixin(HTMLElement
|
|||
this.removeAttribute("address-fields");
|
||||
}
|
||||
|
||||
this.pageTitle.textContent = addressPage.title;
|
||||
this.pageTitleHeading.textContent = addressPage.title;
|
||||
this.genericErrorText.textContent = page.error;
|
||||
|
||||
let editing = !!addressPage.guid;
|
||||
|
@ -161,8 +165,8 @@ export default class AddressForm extends PaymentStateSubscriberMixin(HTMLElement
|
|||
|
||||
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);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
/* 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";
|
||||
|
||||
|
@ -16,17 +17,12 @@ import paymentRequest from "../paymentRequest.js";
|
|||
* 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)";
|
||||
|
@ -36,6 +32,14 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(HTMLEleme
|
|||
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);
|
||||
|
@ -44,8 +48,7 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(HTMLEleme
|
|||
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";
|
||||
|
@ -70,8 +73,7 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(HTMLEleme
|
|||
|
||||
connectedCallback() {
|
||||
this.promiseReady.then(form => {
|
||||
this.appendChild(this.pageTitle);
|
||||
this.appendChild(form);
|
||||
this.body.appendChild(form);
|
||||
|
||||
let record = {};
|
||||
let addresses = [];
|
||||
|
@ -89,11 +91,8 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(HTMLEleme
|
|||
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();
|
||||
|
@ -135,7 +134,7 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(HTMLEleme
|
|||
|
||||
// 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);
|
||||
|
@ -143,7 +142,7 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(HTMLEleme
|
|||
// 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) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import PaymentStateSubscriberMixin from "../mixins/PaymentStateSubscriberMixin.j
|
|||
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";
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
* 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 */
|
||||
|
|
|
@ -3,28 +3,14 @@
|
|||
* 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] {
|
||||
|
@ -35,8 +21,7 @@ body {
|
|||
/* 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;
|
||||
|
@ -45,7 +30,9 @@ body {
|
|||
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;
|
||||
|
@ -57,14 +44,28 @@ payment-dialog > header {
|
|||
|
||||
#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 {
|
||||
|
@ -72,6 +73,7 @@ payment-dialog > header {
|
|||
}
|
||||
|
||||
#order-details-overlay {
|
||||
background-color: var(--in-content-page-background);
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -81,11 +83,6 @@ payment-dialog > header {
|
|||
z-index: 1;
|
||||
}
|
||||
|
||||
payment-dialog > footer {
|
||||
align-items: baseline;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#total {
|
||||
flex: 1 1 auto;
|
||||
margin: 5px;
|
||||
|
@ -115,6 +112,7 @@ payment-dialog[changes-prevented][completion-state="success"] #pay {
|
|||
|
||||
#disabled-overlay {
|
||||
background: white;
|
||||
grid-area: disabled-overlay;
|
||||
opacity: 0.6;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
|
|
@ -92,8 +92,8 @@
|
|||
</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"
|
||||
|
@ -122,9 +122,9 @@
|
|||
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"
|
||||
|
@ -134,14 +134,13 @@
|
|||
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;"
|
||||
|
@ -156,7 +155,6 @@
|
|||
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;"
|
||||
|
@ -182,8 +180,8 @@
|
|||
</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;"
|
||||
|
|
|
@ -94,7 +94,7 @@ add_task(async function test_backButton() {
|
|||
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();
|
||||
|
|
|
@ -78,7 +78,7 @@ add_task(async function test_backButton() {
|
|||
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, {});
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ add_task(async function test_viewAllButtonVisibility() {
|
|||
|
||||
let button = el1._viewAllButton;
|
||||
ok(button.hidden, "Button is initially hidden when there are no items to show");
|
||||
ok(isHidden(button), "Button should be visibly hidden since bug 1469464")
|
||||
ok(isHidden(button), "Button should be visibly hidden since bug 1469464");
|
||||
|
||||
// Add a display item.
|
||||
let request = deepClone(el1.requestStore.getState().request);
|
||||
|
@ -182,6 +182,19 @@ add_task(async function test_completionStateChangesPrevented() {
|
|||
}
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
|
|
|
@ -139,7 +139,7 @@ class EditAddress extends EditAutofillForm {
|
|||
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);
|
||||
|
@ -155,7 +155,7 @@ class EditAddress extends EditAutofillForm {
|
|||
}
|
||||
// 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;
|
||||
|
@ -164,7 +164,7 @@ class EditAddress extends EditAutofillForm {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
Загрузка…
Ссылка в новой задаче