--- a/toolkit/components/payments/content/paymentDialog.js
+++ b/toolkit/components/payments/content/paymentDialog.js
@@ -8,24 +8,43 @@
*/
"use strict";
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
.getService(Ci.nsIPaymentRequestService);
+Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyGetter(this, "profileStorage", () => {
+ let profileStorage;
+ try {
+ profileStorage = Cu.import("resource://formautofill/ProfileStorage.jsm", {}).profileStorage;
+ profileStorage.initialize();
+ } catch (ex) {
+ profileStorage = null;
+ Cu.reportError(ex);
+ }
+
+ return profileStorage;
+});
+
var PaymentDialog = {
componentsLoaded: new Map(),
frame: null,
mm: null,
request: null,
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ ]),
+
init(requestId, frame) {
if (!requestId || typeof(requestId) != "string") {
throw new Error("Invalid PaymentRequest ID");
}
this.request = paymentSrv.getPaymentRequestById(requestId);
if (!this.request) {
throw new Error(`PaymentRequest not found: ${requestId}`);
@@ -129,16 +148,68 @@ var PaymentDialog = {
if (!component) {
component = Cc[componentName];
this.componentsLoaded.set(componentName, component);
}
return component.createInstance(componentInterface);
},
+ fetchSavedAddresses() {
+ let savedAddresses = {};
+ for (let address of profileStorage.addresses.getAll()) {
+ savedAddresses[address.guid] = address;
+ }
+ return savedAddresses;
+ },
+
+ fetchSavedPaymentCards() {
+ let savedBasicCards = {};
+ for (let card of profileStorage.creditCards.getAll()) {
+ savedBasicCards[card.guid] = card;
+ // Filter out the encrypted card number since the dialog content is
+ // considered untrusted and runs in a content process.
+ delete card["cc-number-encrypted"];
+ }
+ return savedBasicCards;
+ },
+
+ onAutofillStorageChange() {
+ this.mm.sendAsyncMessage("paymentChromeToContent", {
+ messageType: "updateState",
+ data: {
+ savedAddresses: this.fetchSavedAddresses(),
+ savedBasicCards: this.fetchSavedPaymentCards(),
+ },
+ });
+ },
+
+ initializeFrame() {
+ let requestSerialized = JSON.parse(JSON.stringify(this.request));
+
+ // Manually serialize the nsIPrincipal.
+ let displayHost = this.request.topLevelPrincipal.URI.displayHost;
+ requestSerialized.topLevelPrincipal = {
+ URI: {
+ displayHost,
+ },
+ };
+
+ this.mm.sendAsyncMessage("paymentChromeToContent", {
+ messageType: "showPaymentRequest",
+ data: {
+ request: requestSerialized,
+ savedAddresses: this.fetchSavedAddresses(),
+ savedBasicCards: this.fetchSavedPaymentCards(),
+ },
+ });
+
+ Services.obs.addObserver(this, "formautofill-storage-changed", true);
+ },
+
onPaymentCancel() {
const showResponse = this.createShowResponse({
acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_REJECTED,
});
paymentSrv.respondPayment(showResponse);
window.close();
},
@@ -156,37 +227,40 @@ var PaymentDialog = {
payerEmail,
payerPhone,
methodName,
methodData: basicCardData,
});
paymentSrv.respondPayment(showResponse);
},
+ /**
+ * @implements {nsIObserver}
+ * @param {nsISupports} subject
+ * @param {string} topic
+ * @param {string} data
+ */
+ observe(subject, topic, data) {
+ switch (topic) {
+ case "formautofill-storage-changed": {
+ if (data == "notifyUsed") {
+ break;
+ }
+ this.onAutofillStorageChange();
+ break;
+ }
+ }
+ },
+
receiveMessage({data}) {
let {messageType} = data;
switch (messageType) {
case "initializeRequest": {
- let requestSerialized = JSON.parse(JSON.stringify(this.request));
-
- // Manually serialize the nsIPrincipal.
- let displayHost = this.request.topLevelPrincipal.URI.displayHost;
- requestSerialized.topLevelPrincipal = {
- URI: {
- displayHost,
- },
- };
-
- this.mm.sendAsyncMessage("paymentChromeToContent", {
- messageType: "showPaymentRequest",
- data: {
- request: requestSerialized,
- },
- });
+ this.initializeFrame();
break;
}
case "paymentCancel": {
this.onPaymentCancel();
break;
}
case "pay": {
this.pay(data);
--- a/toolkit/components/payments/content/paymentDialog.xhtml
+++ b/toolkit/components/payments/content/paymentDialog.xhtml
@@ -9,12 +9,13 @@
<link rel="stylesheet" href="chrome://payments/content/paymentDialog.css"/>
</head>
<body>
<iframe type="content"
id="paymentRequestFrame"
mozbrowser="true"
remote="true"
- name="paymentRequestFrame"></iframe>
+ height="400"
+ width="700"></iframe>
<script src="chrome://payments/content/paymentDialog.js"></script>
</body>
</html>
--- a/toolkit/components/payments/res/components/address-option.css
+++ b/toolkit/components/payments/res/components/address-option.css
@@ -2,57 +2,57 @@
* 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/. */
address-option {
display: grid;
grid-row-gap: 5px;
grid-column-gap: 10px;
grid-template-areas:
- "recipient "
- "addressLine";
+ "name "
+ "street-address";
border-bottom: 1px solid #ddd;
background: #fff;
padding: 5px;
padding-inline-start: 20px;
width: 400px;
font-size: .8em;
}
rich-select[open] > .rich-select-popup-box > address-option {
grid-template-areas:
- "recipient recipient"
- "addressLine addressLine"
- "email phone ";
+ "name name "
+ "street-address street-address"
+ "email tel ";
}
-address-option > .recipient {
- grid-area: recipient;
+address-option > .name {
+ grid-area: name;
}
-address-option > .addressLine {
- grid-area: addressLine;
+address-option > .street-address {
+ grid-area: street-address;
}
address-option > .email {
grid-area: email;
}
-address-option > .phone {
- grid-area: phone;
+address-option > .tel {
+ grid-area: tel;
}
-address-option > .recipient,
-address-option > .addressLine,
+address-option > .name,
+address-option > .street-address,
address-option > .email,
-address-option > .phone {
+address-option > .tel {
white-space: nowrap;
}
.rich-select-popup-box > address-option[selected] {
background-color: #ffa;
}
rich-select > .rich-select-selected-clone > .email,
-rich-select > .rich-select-selected-clone > .phone {
+rich-select > .rich-select-selected-clone > .tel {
display: none;
}
--- a/toolkit/components/payments/res/components/address-option.js
+++ b/toolkit/components/payments/res/components/address-option.js
@@ -1,68 +1,66 @@
/* 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/. */
/**
* <rich-select>
- * <address-option addressLine="1234 Anywhere St"
- * city="Some City"
+ * <address-option guid="98hgvnbmytfc"
+ * address-level1="MI"
+ * address-level2="Some City"
+ * email="foo@example.com"
* country="USA"
- * dependentLocality=""
- * languageCode="en-US"
- * phone=""
- * postalCode="90210"
- * recipient="Jared Wein"
- * region="MI"></address-option>
+ * name="Jared Wein"
+ * postal-code="90210"
+ * street-address="1234 Anywhere St"
+ * tel="+1 650 555-5555"></address-option>
* </rich-select>
+ *
+ * Attribute names follow ProfileStorage.jsm.
*/
/* global ObservedPropertiesMixin, RichOption */
class AddressOption extends ObservedPropertiesMixin(RichOption) {
static get observedAttributes() {
return RichOption.observedAttributes.concat([
- "addressLine",
- "city",
+ "address-level1",
+ "address-level2",
"country",
- "dependentLocality",
"email",
- "languageCode",
- "organization",
- "phone",
- "postalCode",
- "recipient",
- "region",
- "sortingCode",
+ "guid",
+ "name",
+ "postal-code",
+ "street-address",
+ "tel",
]);
}
connectedCallback() {
for (let child of this.children) {
child.remove();
}
let fragment = document.createDocumentFragment();
- RichOption._createElement(fragment, "recipient");
- RichOption._createElement(fragment, "addressLine");
+ RichOption._createElement(fragment, "name");
+ RichOption._createElement(fragment, "street-address");
RichOption._createElement(fragment, "email");
- RichOption._createElement(fragment, "phone");
+ RichOption._createElement(fragment, "tel");
this.appendChild(fragment);
super.connectedCallback();
}
render() {
if (!this.parentNode) {
return;
}
- this.querySelector(".recipient").textContent = this.recipient;
- this.querySelector(".addressLine").textContent =
- `${this.addressLine} ${this.city} ${this.region} ${this.postalCode} ${this.country}`;
+ this.querySelector(".name").textContent = this.name;
+ this.querySelector(".street-address").textContent = `${this.streetAddress} ` +
+ `${this.addressLevel2} ${this.addressLevel1} ${this.postalCode} ${this.country}`;
this.querySelector(".email").textContent = this.email;
- this.querySelector(".phone").textContent = this.phone;
+ this.querySelector(".tel").textContent = this.tel;
}
}
customElements.define("address-option", AddressOption);
-
--- a/toolkit/components/payments/res/components/basic-card-option.js
+++ b/toolkit/components/payments/res/components/basic-card-option.js
@@ -9,16 +9,17 @@
*/
/* global ObservedPropertiesMixin, RichOption */
class BasicCardOption extends ObservedPropertiesMixin(RichOption) {
static get observedAttributes() {
return RichOption.observedAttributes.concat([
"expiration",
+ "guid",
"number",
"owner",
"type",
]);
}
connectedCallback() {
for (let child of this.children) {
--- a/toolkit/components/payments/res/components/rich-select.js
+++ b/toolkit/components/payments/res/components/rich-select.js
@@ -35,16 +35,20 @@ class RichSelect extends ObservedPropert
get popupBox() {
return this.querySelector(":scope > .rich-select-popup-box");
}
get selectedOption() {
return this.popupBox.querySelector(":scope > [selected]");
}
+ namedItem(name) {
+ return this.popupBox.querySelector(`:scope > [name="${CSS.escape(name)}"]`);
+ }
+
handleEvent(event) {
switch (event.type) {
case "blur": {
this.onBlur(event);
break;
}
case "click": {
this.onClick(event);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/payments/res/containers/address-picker.js
@@ -0,0 +1,49 @@
+/* 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/. */
+
+/* global PaymentStateSubscriberMixin, PaymentRequest */
+
+"use strict";
+
+/**
+ * <address-picker></address-picker>
+ * Container around <rich-select> (eventually providing add/edit links) with
+ * <address-option> listening to savedAddresses.
+ */
+
+class AddressPicker extends PaymentStateSubscriberMixin(HTMLElement) {
+ constructor() {
+ super();
+ this.dropdown = document.createElement("rich-select");
+ }
+
+ connectedCallback() {
+ this.appendChild(this.dropdown);
+ super.connectedCallback();
+ }
+
+ render(state) {
+ let {savedAddresses} = state;
+ let desiredOptions = [];
+ for (let [guid, address] of Object.entries(savedAddresses)) {
+ let optionEl = this.dropdown.namedItem(guid);
+ if (!optionEl) {
+ optionEl = document.createElement("address-option");
+ optionEl.name = guid;
+ optionEl.guid = guid;
+ }
+ for (let [key, val] of Object.entries(address)) {
+ optionEl.setAttribute(key, val);
+ }
+ desiredOptions.push(optionEl);
+ }
+ let el = null;
+ while ((el = this.dropdown.popupBox.querySelector(":scope > address-option"))) {
+ el.remove();
+ }
+ this.dropdown.popupBox.append(...desiredOptions);
+ }
+}
+
+customElements.define("address-picker", AddressPicker);
--- a/toolkit/components/payments/res/containers/payment-dialog.js
+++ b/toolkit/components/payments/res/containers/payment-dialog.js
@@ -49,17 +49,24 @@ class PaymentDialog extends PaymentState
cardNumber: "9999999999",
expiryMonth: "01",
expiryYear: "9999",
cardSecurityCode: "999",
},
});
}
- setLoadingState(state) {
+ /**
+ * Set some state from the privileged parent process.
+ * Other elements that need to set state should use their own `this.requestStore.setState`
+ * method provided by the `PaymentStateSubscriberMixin`.
+ *
+ * @param {object} state - See `PaymentsStore.setState`
+ */
+ setStateFromParent(state) {
this.requestStore.setState(state);
}
render(state) {
let request = state.request;
this._hostNameEl.textContent = request.topLevelPrincipal.URI.displayHost;
let totalItem = request.paymentDetails.totalItem;
--- a/toolkit/components/payments/res/debugging.html
+++ b/toolkit/components/payments/res/debugging.html
@@ -9,11 +9,13 @@
<script src="debugging.js"></script>
</head>
<body>
<div>
<button id="refresh">Refresh</button>
<button id="logState">Log state</button>
<button id="setRequest1">Set Request 1</button>
<button id="setRequest2">Set Request 2</button>
+ <button id="setAddresses1">Set Addreses 1</button>
+ <button id="delete1Address">Delete 1 Address</button>
</div>
</body>
</html>
--- a/toolkit/components/payments/res/debugging.js
+++ b/toolkit/components/payments/res/debugging.js
@@ -104,29 +104,63 @@ let REQUEST_2 = {
requestPayerName: false,
requestPayerEmail: false,
requestPayerPhone: false,
requestShipping: false,
shippingType: "shipping",
},
};
+let ADDRESSES_1 = {
+ "48bnds6854t": {
+ "address-level1": "MI",
+ "address-level2": "Some City",
+ "country": "US",
+ "guid": "48bnds6854t",
+ "name": "Mr. Foo",
+ "postal-code": "90210",
+ "street-address": "123 Sesame Street,\nApt 40",
+ "tel": "+1 519 555-5555",
+ },
+ "68gjdh354j": {
+ "address-level1": "CA",
+ "address-level2": "Mountain View",
+ "country": "US",
+ "guid": "68gjdh354j",
+ "name": "Mrs. Bar",
+ "postal-code": "94041",
+ "street-address": "P.O. Box 123",
+ "tel": "+1 650 555-5555",
+ },
+};
let buttonActions = {
+ delete1Address() {
+ let savedAddresses = Object.assign({}, requestStore.getState().savedAddresses);
+ delete savedAddresses[Object.keys(savedAddresses)[0]];
+ requestStore.setState({
+ savedAddresses,
+ });
+ },
+
logState() {
let state = requestStore.getState();
// eslint-disable-next-line no-console
console.log(state);
dump(`${JSON.stringify(state, null, 2)}\n`);
},
refresh() {
window.parent.location.reload(true);
},
+ setAddresses1() {
+ requestStore.setState({savedAddresses: ADDRESSES_1});
+ },
+
setRequest1() {
requestStore.setState({request: REQUEST_1});
},
setRequest2() {
requestStore.setState({request: REQUEST_2});
},
};
--- a/toolkit/components/payments/res/mixins/PaymentStateSubscriberMixin.js
+++ b/toolkit/components/payments/res/mixins/PaymentStateSubscriberMixin.js
@@ -30,18 +30,18 @@ let requestStore = new PaymentsStore({
paymentOptions: {
requestPayerName: false,
requestPayerEmail: false,
requestPayerPhone: false,
requestShipping: false,
shippingType: "shipping",
},
},
- savedAddresses: [],
- savedBasicCards: [],
+ savedAddresses: {},
+ savedBasicCards: {},
});
/* exported PaymentStateSubscriberMixin */
/**
* A mixin to render UI based upon the requestStore and get updated when that store changes.
*
--- a/toolkit/components/payments/res/paymentRequest.js
+++ b/toolkit/components/payments/res/paymentRequest.js
@@ -68,29 +68,33 @@ let PaymentRequest = {
onChromeToContent({detail}) {
let {messageType} = detail;
switch (messageType) {
case "showPaymentRequest": {
this.onShowPaymentRequest(detail);
break;
}
+ case "updateState": {
+ document.querySelector("payment-dialog").setStateFromParent(detail);
+ break;
+ }
}
},
onPaymentRequestLoad(requestId) {
window.addEventListener("unload", this, {once: true});
this.sendMessageToChrome("paymentDialogReady");
},
async onShowPaymentRequest(detail) {
// Handle getting called before the DOM is ready.
await this.domReadyPromise;
- document.querySelector("payment-dialog").setLoadingState({
+ document.querySelector("payment-dialog").setStateFromParent({
request: detail.request,
savedAddresses: detail.savedAddresses,
savedBasicCards: detail.savedBasicCards,
});
},
cancel() {
this.sendMessageToChrome("paymentCancel");
--- a/toolkit/components/payments/res/paymentRequest.xhtml
+++ b/toolkit/components/payments/res/paymentRequest.xhtml
@@ -17,75 +17,36 @@
<script src="mixins/ObservedPropertiesMixin.js"></script>
<script src="mixins/PaymentStateSubscriberMixin.js"></script>
<script src="components/currency-amount.js"></script>
<script src="components/rich-select.js"></script>
<script src="components/rich-option.js"></script>
<script src="components/address-option.js"></script>
+ <script src="containers/address-picker.js"></script>
<script src="containers/payment-dialog.js"></script>
<script src="paymentRequest.js"></script>
<template id="payment-dialog-template">
<div id="host-name"></div>
<div id="total">
<h2 class="label"></h2>
<currency-amount></currency-amount>
</div>
+
+ <div><label>Shipping Address</label></div>
+ <address-picker>
+ </address-picker>
+
<div id="controls-container">
<button id="cancel">Cancel</button>
<button id="pay">Pay</button>
</div>
</template>
</head>
<body>
<iframe id="debugging-console" hidden="hidden" src="debugging.html"></iframe>
-
- <rich-select>
- <address-option email="emzembrano92@example.com"
- recipient="Emily Zembrano"
- addressLine="717 Hyde Street #6"
- city="San Francisco"
- region="CA"
- phone="415 203 0845"
- postalCode="94109"
- country="USA"></address-option>
- <address-option email="jenz9382@example.com"
- recipient="Jennifer Zembrano"
- addressLine="42 Fairydust Lane"
- city="Lala Land"
- region="HI"
- phone="415 439 2827"
- postalCode="98765"
- country="USA"></address-option>
- <address-option email="johnz9382@example.com"
- recipient="John Zembrano"
- addressLine="42 Fairydust Lane"
- city="Lala Land"
- missinginformation="true"
- region="HI"
- phone="415 439 2827"
- postalCode="98765"
- country="USA"></address-option>
- <address-option email="adbrwodne@example.com"
- recipient="Andrew Browne"
- addressLine="42 Fairydust Lane"
- city="Lala Land"
- region="HI"
- phone="517 410 0845"
- postalCode="98765"
- country="USA"></address-option>
- <address-option email="johnz9382@example.com"
- recipient="Jacob Humphrey"
- addressLine="1855 Pinecrest Rd"
- city="East Lansing"
- region="MI"
- phone="517 439 2827"
- postalCode="48823"
- country="USA"></address-option>
- </rich-select>
-
<payment-dialog></payment-dialog>
</body>
</html>
--- a/toolkit/components/payments/test/browser/browser.ini
+++ b/toolkit/components/payments/test/browser/browser.ini
@@ -2,12 +2,13 @@
head = head.js
prefs =
dom.payments.request.enabled=true
skip-if = !e10s # Bug 1365964 - Payment Request isn't implemented for non-e10s
support-files =
blank_page.html
[browser_host_name.js]
+[browser_profile_storage.js]
[browser_request_summary.js]
[browser_show_dialog.js]
skip-if = os == 'win' && debug # bug 1418385
[browser_total.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/payments/test/browser/browser_profile_storage.js
@@ -0,0 +1,192 @@
+"use strict";
+
+// Disable CPOW checks because they have false-positives from use of ContentTask in a helper.
+/* eslint-disable mozilla/no-cpows-in-tests */
+
+const methodData = [PTU.MethodData.basicCard];
+const details = PTU.Details.total60USD;
+
+add_task(async function test_initial_state() {
+ let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+ (subject, data) => data == "add");
+ let address1GUID = profileStorage.addresses.add({
+ "given-name": "Timothy",
+ "additional-name": "John",
+ "family-name": "Berners-Lee",
+ organization: "World Wide Web Consortium",
+ "street-address": "32 Vassar Street\nMIT Room 32-G524",
+ "address-level2": "Cambridge",
+ "address-level1": "MA",
+ "postal-code": "02139",
+ country: "US",
+ tel: "+16172535702",
+ email: "timbl@w3.org",
+ });
+ await onChanged;
+
+ onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+ (subject, data) => data == "add");
+ let card1GUID = profileStorage.creditCards.add({
+ "cc-name": "John Doe",
+ "cc-number": "1234567812345678",
+ "cc-exp-month": 4,
+ "cc-exp-year": 2028,
+ });
+ await onChanged;
+
+ await BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: BLANK_PAGE_URL,
+ }, async browser => {
+ let dialogReadyPromise = waitForWidgetReady();
+ // start by creating a PaymentRequest, and show it
+ await ContentTask.spawn(browser, {methodData, details}, PTU.ContentTasks.createAndShowRequest);
+
+ // get a reference to the UI dialog and the requestId
+ let win = await getPaymentWidget();
+ let requestId = paymentUISrv.requestIdForWindow(win);
+ ok(requestId, "requestId should be defined");
+ is(win.closed, false, "dialog should not be closed");
+
+ let frame = await getPaymentFrame(win);
+ ok(frame, "Got payment frame");
+ await dialogReadyPromise;
+ info("dialog ready");
+
+ await spawnPaymentDialogTask(frame, async function checkInitialStore({
+ address1GUID,
+ card1GUID,
+ }) {
+ info("checkInitialStore");
+ let contentWin = Components.utils.waiveXrays(content);
+ let {
+ savedAddresses,
+ savedBasicCards,
+ } = contentWin.document.querySelector("payment-dialog").requestStore.getState();
+
+ is(Object.keys(savedAddresses).length, 1, "Initially one savedAddresses");
+ is(savedAddresses[address1GUID].name, "Timothy John Berners-Lee", "Check full name");
+ is(savedAddresses[address1GUID].guid, address1GUID, "Check address guid matches key");
+
+ is(Object.keys(savedBasicCards).length, 1, "Initially one savedBasicCards");
+ is(savedBasicCards[card1GUID]["cc-number"], "************5678", "Check cc-number");
+ is(savedBasicCards[card1GUID].guid, card1GUID, "Check card guid matches key");
+ }, {
+ address1GUID,
+ card1GUID,
+ });
+
+ let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+ (subject, data) => data == "add");
+ info("adding an address");
+ let address2GUID = profileStorage.addresses.add({
+ "given-name": "John",
+ "additional-name": "",
+ "family-name": "Smith",
+ "street-address": "331 E. Evelyn Ave.",
+ "address-level2": "Mountain View",
+ "address-level1": "CA",
+ "postal-code": "94041",
+ country: "US",
+ });
+ await onChanged;
+
+ await spawnPaymentDialogTask(frame, async function checkAdd({
+ address1GUID,
+ address2GUID,
+ card1GUID,
+ }) {
+ info("checkAdd");
+ let contentWin = Components.utils.waiveXrays(content);
+ let {
+ savedAddresses,
+ savedBasicCards,
+ } = contentWin.document.querySelector("payment-dialog").requestStore.getState();
+
+ let addressGUIDs = Object.keys(savedAddresses);
+ is(addressGUIDs.length, 2, "Now two savedAddresses");
+ is(addressGUIDs[0], address1GUID, "Check first address GUID");
+ is(savedAddresses[address1GUID].guid, address1GUID, "Check address 1 guid matches key");
+ is(addressGUIDs[1], address2GUID, "Check second address GUID");
+ is(savedAddresses[address2GUID].guid, address2GUID, "Check address 2 guid matches key");
+
+ is(Object.keys(savedBasicCards).length, 1, "Still one savedBasicCards");
+ is(savedBasicCards[card1GUID].guid, card1GUID, "Check card guid matches key");
+ }, {
+ address1GUID,
+ address2GUID,
+ card1GUID,
+ });
+
+ onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+ (subject, data) => data == "update");
+ info("updating the credit expiration");
+ profileStorage.creditCards.update(card1GUID, {
+ "cc-exp-month": 6,
+ "cc-exp-year": 2029,
+ }, true);
+ await onChanged;
+
+ await spawnPaymentDialogTask(frame, async function checkUpdate({
+ address1GUID,
+ address2GUID,
+ card1GUID,
+ }) {
+ info("checkUpdate");
+ let contentWin = Components.utils.waiveXrays(content);
+ let {
+ savedAddresses,
+ savedBasicCards,
+ } = contentWin.document.querySelector("payment-dialog").requestStore.getState();
+
+ let addressGUIDs = Object.keys(savedAddresses);
+ is(addressGUIDs.length, 2, "Still two savedAddresses");
+ is(addressGUIDs[0], address1GUID, "Check first address GUID");
+ is(savedAddresses[address1GUID].guid, address1GUID, "Check address 1 guid matches key");
+ is(addressGUIDs[1], address2GUID, "Check second address GUID");
+ is(savedAddresses[address2GUID].guid, address2GUID, "Check address 2 guid matches key");
+
+ is(Object.keys(savedBasicCards).length, 1, "Still one savedBasicCards");
+ is(savedBasicCards[card1GUID].guid, card1GUID, "Check card guid matches key");
+ is(savedBasicCards[card1GUID]["cc-exp-month"], 6, "Check expiry month");
+ is(savedBasicCards[card1GUID]["cc-exp-year"], 2029, "Check expiry year");
+ }, {
+ address1GUID,
+ address2GUID,
+ card1GUID,
+ });
+
+ onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+ (subject, data) => data == "remove");
+ info("removing the first address");
+ profileStorage.addresses.remove(address1GUID);
+ await onChanged;
+
+ await spawnPaymentDialogTask(frame, async function checkRemove({
+ address2GUID,
+ card1GUID,
+ }) {
+ info("checkRemove");
+ let contentWin = Components.utils.waiveXrays(content);
+ let {
+ savedAddresses,
+ savedBasicCards,
+ } = contentWin.document.querySelector("payment-dialog").requestStore.getState();
+
+ is(Object.keys(savedAddresses).length, 1, "Now one savedAddresses");
+ is(savedAddresses[address2GUID].name, "John Smith", "Check full name");
+ is(savedAddresses[address2GUID].guid, address2GUID, "Check address guid matches key");
+
+ is(Object.keys(savedBasicCards).length, 1, "Still one savedBasicCards");
+ is(savedBasicCards[card1GUID]["cc-number"], "************5678", "Check cc-number");
+ is(savedBasicCards[card1GUID].guid, card1GUID, "Check card guid matches key");
+ }, {
+ address2GUID,
+ card1GUID,
+ });
+
+ spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
+
+ await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
+ });
+});
--- a/toolkit/components/payments/test/browser/browser_show_dialog.js
+++ b/toolkit/components/payments/test/browser/browser_show_dialog.js
@@ -37,18 +37,16 @@ add_task(async function test_show_manual
ok(win, "Got payment widget");
let requestId = paymentUISrv.requestIdForWindow(win);
ok(requestId, "requestId should be defined");
is(win.closed, false, "dialog should not be closed");
// abort the payment request manually
let frame = await getPaymentFrame(win);
ok(frame, "Got payment frame");
- await dialogReadyPromise;
- info("dialog ready");
spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
});
});
add_task(async function test_show_completePayment() {
await BrowserTestUtils.withNewTab({
gBrowser,
@@ -62,18 +60,17 @@ add_task(async function test_show_comple
let [win] = await Promise.all([getPaymentWidget(), dialogReadyPromise]);
ok(win, "Got payment widget");
let requestId = paymentUISrv.requestIdForWindow(win);
ok(requestId, "requestId should be defined");
is(win.closed, false, "dialog should not be closed");
let frame = await getPaymentFrame(win);
ok(frame, "Got payment frame");
- await dialogReadyPromise;
- info("dialog ready, clicking pay");
+ info("clicking pay");
spawnPaymentDialogTask(frame, PTU.DialogContentTasks.completePayment);
// Add a handler to complete the payment above.
info("acknowledging the completion from the merchant page");
let result = await ContentTask.spawn(browser, {}, PTU.ContentTasks.addCompletionHandler);
is(result.response.methodName, "basic-card", "Check methodName");
let methodDetails = result.methodDetails;
--- a/toolkit/components/payments/test/browser/head.js
+++ b/toolkit/components/payments/test/browser/head.js
@@ -11,16 +11,17 @@
const BLANK_PAGE_PATH = "/browser/toolkit/components/payments/test/browser/blank_page.html";
const BLANK_PAGE_URL = "https://example.com" + BLANK_PAGE_PATH;
const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
.getService(Ci.nsIPaymentRequestService);
const paymentUISrv = Cc["@mozilla.org/dom/payments/payment-ui-service;1"]
.getService().wrappedJSObject;
+const {profileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
const {PaymentTestUtils: PTU} = Cu.import("resource://testing-common/PaymentTestUtils.jsm", {});
function getPaymentRequests() {
let requestsEnum = paymentSrv.enumerate();
let requests = [];
while (requestsEnum.hasMoreElements()) {
requests.push(requestsEnum.getNext().QueryInterface(Ci.nsIPaymentRequest));
}
@@ -155,12 +156,16 @@ async function spawnInDialogForMerchantT
let request = requests[0];
ok(!!request.requestId, "Got a payment request with an ID");
await spawnTaskInNewDialog(request.requestId, dialogTaskFn, taskArgs);
});
}
add_task(async function setup_head() {
+ await profileStorage.initialize();
+
SimpleTest.registerCleanupFunction(function cleanup() {
paymentSrv.cleanup();
+ profileStorage.addresses._nukeAllRecords();
+ profileStorage.creditCards._nukeAllRecords();
});
});
--- a/toolkit/components/payments/test/mochitest/mochitest.ini
+++ b/toolkit/components/payments/test/mochitest/mochitest.ini
@@ -1,22 +1,26 @@
[DEFAULT]
+prefs =
+ dom.webcomponents.customelements.enabled=false
support-files =
../../../../../testing/modules/sinon-2.3.2.js
../../res/PaymentsStore.js
../../res/components/currency-amount.js
../../res/components/address-option.js
../../res/components/address-option.css
../../res/components/basic-card-option.js
../../res/components/basic-card-option.css
../../res/components/rich-option.js
../../res/components/rich-select.css
../../res/components/rich-select.js
+ ../../res/containers/address-picker.js
../../res/mixins/ObservedPropertiesMixin.js
../../res/mixins/PaymentStateSubscriberMixin.js
../../res/vendor/custom-elements.min.js
../../res/vendor/custom-elements.min.js.map
payments_common.js
+[test_address_picker.html]
[test_currency_amount.html]
[test_rich_select.html]
[test_ObservedPropertiesMixin.html]
[test_PaymentStateSubscriberMixin.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/payments/test/mochitest/test_address_picker.html
@@ -0,0 +1,141 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test the address-picker component
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test the address-picker component</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="payments_common.js"></script>
+ <script src="custom-elements.min.js"></script>
+ <script src="PaymentsStore.js"></script>
+ <script src="ObservedPropertiesMixin.js"></script>
+ <script src="PaymentStateSubscriberMixin.js"></script>
+ <script src="rich-select.js"></script>
+ <script src="address-picker.js"></script>
+ <script src="rich-option.js"></script>
+ <script src="address-option.js"></script>
+ <link rel="stylesheet" type="text/css" href="rich-select.css"/>
+ <link rel="stylesheet" type="text/css" href="address-option.css"/>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <p id="display">
+ <address-picker id="picker1"></address-picker>
+ </p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+/** Test the address-picker component **/
+
+/* import-globals-from payments_common.js */
+/* import-globals-from ../../res/components/address-option.js */
+
+let picker1 = document.getElementById("picker1");
+
+add_task(async function test_empty() {
+ ok(picker1, "Check picker1 exists");
+ let {savedAddresses} = picker1.requestStore.getState();
+ is(Object.keys(savedAddresses).length, 0, "Check empty initial state");
+ is(picker1.dropdown.popupBox.children.length, 0, "Check dropdown is empty");
+});
+
+add_task(async function test_initialSet() {
+ picker1.requestStore.setState({
+ savedAddresses: {
+ "48bnds6854t": {
+ "address-level1": "MI",
+ "address-level2": "Some City",
+ "country": "US",
+ "guid": "48bnds6854t",
+ "name": "Mr. Foo",
+ "postal-code": "90210",
+ "street-address": "123 Sesame Street,\nApt 40",
+ "tel": "+1 519 555-5555",
+ },
+ "68gjdh354j": {
+ "address-level1": "CA",
+ "address-level2": "Mountain View",
+ "country": "US",
+ "guid": "68gjdh354j",
+ "name": "Mrs. Bar",
+ "postal-code": "94041",
+ "street-address": "P.O. Box 123",
+ "tel": "+1 650 555-5555",
+ },
+ },
+ });
+ await asyncElementRendered();
+ let options = picker1.dropdown.popupBox.children;
+ is(options.length, 2, "Check dropdown has both addresses");
+ ok(options[0].textContent.includes("123 Sesame Street"), "Check first address");
+ ok(options[1].textContent.includes("P.O. Box 123"), "Check second address");
+});
+
+add_task(async function test_update() {
+ picker1.requestStore.setState({
+ savedAddresses: {
+ "48bnds6854t": {
+ // Same GUID, different values to trigger an update
+ "address-level1": "MI-edit",
+ "address-level2": "Some City-edit",
+ "country": "CA",
+ "guid": "48bnds6854t",
+ "name": "Mr. Foo-edit",
+ "postal-code": "90210-1234",
+ "street-address": "new-edit",
+ "tel": "+1 650 555-5555",
+ },
+ "68gjdh354j": {
+ "address-level1": "CA",
+ "address-level2": "Mountain View",
+ "country": "US",
+ "guid": "68gjdh354j",
+ "name": "Mrs. Bar",
+ "postal-code": "94041",
+ "street-address": "P.O. Box 123",
+ "tel": "+1 650 555-5555",
+ },
+ },
+ });
+ await asyncElementRendered();
+ let options = picker1.dropdown.popupBox.children;
+ is(options.length, 2, "Check dropdown still has both addresses");
+ ok(options[0].textContent.includes("MI-edit"), "Check updated first address-level1");
+ ok(options[0].textContent.includes("Some City-edit"), "Check updated first address-level2");
+ ok(options[0].textContent.includes("new-edit"), "Check updated first address");
+
+ ok(options[1].textContent.includes("P.O. Box 123"), "Check second address is the same");
+});
+
+add_task(async function test_delete() {
+ picker1.requestStore.setState({
+ savedAddresses: {
+ // 48bnds6854t was deleted
+ "68gjdh354j": {
+ "address-level1": "CA",
+ "address-level2": "Mountain View",
+ "country": "US",
+ "guid": "68gjdh354j",
+ "name": "Mrs. Bar",
+ "postal-code": "94041",
+ "street-address": "P.O. Box 123",
+ "tel": "+1 650 555-5555",
+ },
+ },
+ });
+ await asyncElementRendered();
+ let options = picker1.dropdown.popupBox.children;
+ is(options.length, 1, "Check dropdown has one remaining address");
+ ok(options[0].textContent.includes("P.O. Box 123"), "Check remaining address");
+});
+</script>
+
+</body>
+</html>
--- a/toolkit/components/payments/test/mochitest/test_rich_select.html
+++ b/toolkit/components/payments/test/mochitest/test_rich_select.html
@@ -21,41 +21,41 @@ Test the rich-select component
<link rel="stylesheet" type="text/css" href="basic-card-option.css"/>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display">
<rich-select id="select1">
<address-option id="option1"
email="emzembrano92@email.com"
- recipient="Emily Zembrano"
- addressLine="717 Hyde Street #6"
- city="San Francisco"
- region="CA"
- phone="415 203 0845"
- postalCode="94109"
+ name="Emily Zembrano"
+ street-address="717 Hyde Street #6"
+ address-level2="San Francisco"
+ address-level1="CA"
+ tel="415 203 0845"
+ postal-code="94109"
country="USA"></address-option>
<address-option id="option2"
email="jenz9382@email.com"
- recipient="Jennifer Zembrano"
- addressLine="42 Fairydust Lane"
- city="Lala Land"
- region="HI"
- phone="415 439 2827"
- postalCode="98765"
+ name="Jennifer Zembrano"
+ street-address="42 Fairydust Lane"
+ address-level2="Lala Land"
+ address-level1="HI"
+ tel="415 439 2827"
+ postal-code="98765"
country="USA"></address-option>
<address-option id="option3"
email="johnz9382@email.com"
- recipient="John Zembrano"
- addressLine="42 Fairydust Lane"
- city="Lala Land"
+ name="John Zembrano"
+ street-address="42 Fairydust Lane"
+ address-level2="Lala Land"
missinginformation="true"
- region="HI"
- phone="415 439 2827"
- postalCode="98765"
+ address-level1="HI"
+ tel="415 439 2827"
+ postal-code="98765"
country="USA"></address-option>
</rich-select>
<rich-select id="select2">
<basic-card-option owner="Jared Wein"
expiration="01/1970"
number="4024007197293599"
type="Visa"></basic-card-option>
@@ -97,22 +97,22 @@ function is_visible(element, message) {
function is_hidden(element, message) {
ok(isHidden(element), message);
}
function dispatchKeyPress(key, keyCode) {
select1.dispatchEvent(new KeyboardEvent("keypress", {key, keyCode}));
}
-add_task(async function test_addressLine_combines_address_city_region_postalCode_country() {
+add_task(async function test_streetAddress_combines_street_level2_level1_postalCode_country() {
ok(option1, "option1 exists");
- let addressLine = option1.querySelector(".addressLine");
+ let streetAddress = option1.querySelector(".street-address");
/* eslint-disable max-len */
- is(addressLine.textContent,
- `${option1.addressLine} ${option1.city} ${option1.region} ${option1.postalCode} ${option1.country}`);
+ is(streetAddress.textContent,
+ `${option1.streetAddress} ${option1.addressLevel2} ${option1.addressLevel1} ${option1.postalCode} ${option1.country}`);
/* eslint-enable max-len */
});
add_task(async function test_no_option_selected_first_displayed() {
ok(select1, "select1 exists");
await asyncElementRendered();
@@ -120,18 +120,18 @@ add_task(async function test_no_option_s
is_hidden(option2, "option 2 should be hidden when popup is not open");
is_hidden(option3, "option 3 should be hidden when popup is not open");
ok(option1.selected, "option 1 should be selected");
ok(option1.hasAttribute("selected"), "option 1 should have selected attribute");
let selectedClone = get_selected_clone();
is_visible(selectedClone, "The selected clone should be visible at all times");
is(selectedClone.getAttribute("email"), option1.getAttribute("email"),
"The selected clone email should be equivalent to the selected option 1");
- is(selectedClone.getAttribute("recipient"), option1.getAttribute("recipient"),
- "The selected clone recipient should be equivalent to the selected option 1");
+ is(selectedClone.getAttribute("name"), option1.getAttribute("name"),
+ "The selected clone name should be equivalent to the selected option 1");
});
add_task(async function test_clicking_on_select_shows_all_options() {
ok(select1, "select1 exists");
ok(!select1.open, "select is not open by default");
ok(option1.selected, "option 1 should be selected by default");
select1.click();
@@ -152,18 +152,18 @@ add_task(async function test_clicking_on
is_hidden(option3, "option 3 is hidden when select is closed");
await asyncElementRendered();
let selectedClone = get_selected_clone();
is_visible(selectedClone, "The selected clone should be visible at all times");
is(selectedClone.getAttribute("email"), option2.getAttribute("email"),
"The selected clone email should be equivalent to the selected option 2");
- is(selectedClone.getAttribute("recipient"), option2.getAttribute("recipient"),
- "The selected clone recipient should be equivalent to the selected option 2");
+ is(selectedClone.getAttribute("name"), option2.getAttribute("name"),
+ "The selected clone name should be equivalent to the selected option 2");
});
add_task(async function test_changing_option_selected_affects_other_options() {
ok(option2.selected, "Option 2 should be selected from prior test");
option1.selected = true;
ok(!option2.selected, "Option 2 should no longer be selected after making option 1 selected");
ok(option1.hasAttribute("selected"), "Option 1 should now have selected attribute");