--- a/.eslintignore
+++ b/.eslintignore
@@ -347,16 +347,17 @@ toolkit/components/workerloader/tests/mo
# Tests old non-star function generators
toolkit/modules/tests/xpcshell/test_task.js
# External code:
toolkit/components/microformats/test/**
toolkit/components/microformats/microformat-shiv.js
toolkit/components/reader/Readability.js
toolkit/components/reader/JSDOMParser.js
+toolkit/components/payments/res/vendor/*
# Uses preprocessing
toolkit/content/widgets/wizard.xml
toolkit/components/osfile/osfile.jsm
toolkit/components/urlformatter/nsURLFormatter.js
toolkit/modules/AppConstants.jsm
toolkit/mozapps/downloads/nsHelperAppDlg.js
toolkit/mozapps/update/tests/data/xpcshellConstantsPP.js
--- a/toolkit/components/payments/content/paymentDialog.js
+++ b/toolkit/components/payments/content/paymentDialog.js
@@ -40,34 +40,71 @@ let PaymentDialog = {
maxLogLevelPref: "dom.payments.loglevel",
prefix: `paymentDialog (${requestId})`,
});
});
this.log.debug("init:", this.request);
this.mm.addMessageListener("paymentContentToChrome", this);
this.mm.loadFrameScript("chrome://payments/content/paymentDialogFrameScript.js", true);
- this.frame.src = "resource://payments/paymentRequest.xhtml";
+ this.frame.src = "resource://payments/paymentRequest.html";
},
- createShowResponse({acceptStatus, methodName = "", data = null,
+ createShowResponse({acceptStatus, paymentMethodName = "", paymentMethodData = null,
payerName = "", payerEmail = "", payerPhone = ""}) {
let showResponse = this.createComponentInstance(Ci.nsIPaymentShowActionResponse);
- let methodData = this.createComponentInstance(Ci.nsIGeneralResponseData);
showResponse.init(this.request.requestId,
acceptStatus,
- methodName,
- methodData,
+ paymentMethodName,
+ paymentMethodData,
payerName,
payerEmail,
payerPhone);
return showResponse;
},
+ createBasicCardResponseData({cardNumber,
+ cardholderName,
+ cardSecurityCode,
+ expiryMonth,
+ expiryYear,
+ billingAddress = null}) {
+ const basicCardResponseData = Cc["@mozilla.org/dom/payments/basiccard-response-data;1"]
+ .createInstance(Ci.nsIBasicCardResponseData);
+ basicCardResponseData.initData(cardholderName,
+ cardNumber,
+ expiryMonth,
+ expiryYear,
+ cardSecurityCode,
+ billingAddress);
+ return basicCardResponseData;
+ },
+
+ createPaymentAddress() {
+ const billingAddress = Cc["@mozilla.org/dom/payments/payment-address;1"]
+ .createInstance(Ci.nsIPaymentAddress);
+ const addressLine = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ const address = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+ address.data = "Easton Ave";
+ addressLine.appendElement(address);
+ billingAddress.init("USA", // country
+ addressLine, // address line
+ "CA", // region
+ "San Bruno", // city
+ "", // dependent locality
+ "94066", // postal code
+ "123456", // sorting code
+ "en", // language code
+ "", // organization
+ "Bill A. Pacheco", // recipient
+ "+14344413879"); // phone
+ return billingAddress;
+ },
+
createComponentInstance(componentInterface) {
let componentName;
switch (componentInterface) {
case Ci.nsIPaymentShowActionResponse: {
componentName = "@mozilla.org/dom/payments/payment-show-action-response;1";
break;
}
case Ci.nsIGeneralResponseData: {
@@ -82,22 +119,36 @@ let PaymentDialog = {
this.componentsLoaded.set(componentName, component);
}
return component.createInstance(componentInterface);
},
onPaymentCancel() {
const showResponse = this.createShowResponse({
- acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_REJECTED,
+ acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_REJECTED, // abort?
});
paymentSrv.respondPayment(showResponse);
window.close();
},
+ pay(details) {
+ let {payerName, payerEmail, payerPhone, paymentMethodName, paymentMethodData} = details;
+ let basicCardData = this.createBasicCardResponseData(paymentMethodData);
+ const showResponse = this.createShowResponse({
+ acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED,
+ payerName,
+ payerEmail,
+ payerPhone,
+ paymentMethodName,
+ paymentMethodData: basicCardData,
+ });
+ paymentSrv.respondPayment(showResponse);
+ },
+
receiveMessage({data}) {
let {messageType} = data;
switch (messageType) {
case "initializeRequest": {
let requestSerialized = JSON.parse(JSON.stringify(this.request));
// Manually serialize the nsIPrincipal.
@@ -107,23 +158,56 @@ let PaymentDialog = {
displayHost,
},
};
this.mm.sendAsyncMessage("paymentChromeToContent", {
messageType: "showPaymentRequest",
data: {
request: requestSerialized,
+ savedAddresses: [ // TODO: Object with guid key for lookup?
+ {
+ guid: "89twgn34y4g",
+ name: "Mr. Baz",
+ },
+ {
+ guid: "3feg745sed",
+ name: "Mrs. Smith",
+ },
+ ],
+ savedBasicCards: [ // TODO
+ {
+ "cc-exp-month": 3,
+ "cc-number-encrypted": "_decrypt_me_",
+ "cc-number": "************5678",
+ "guid": "587gdh45",
+ "version": 1,
+ "timeCreated": 1506397876390,
+ "timeLastModified": 1506397909614,
+ "timeLastUsed": 0,
+ "timesUsed": 0,
+ "cc-name": "Jane Doe",
+ "cc-exp-year": 2021,
+ "cc-given-name": "Jane",
+ "cc-additional-name": "",
+ "cc-family-name": "Doe",
+ "cc-exp": "2021-03",
+ },
+ ],
},
});
break;
}
case "paymentCancel": {
this.onPaymentCancel();
break;
}
+ case "pay": {
+ this.pay(data);
+ break;
+ }
}
},
};
let frame = document.getElementById("paymentRequestFrame");
let requestId = (new URLSearchParams(window.location.search)).get("requestId");
PaymentDialog.init(requestId, frame);
--- a/toolkit/components/payments/content/paymentDialog.xhtml
+++ b/toolkit/components/payments/content/paymentDialog.xhtml
@@ -7,14 +7,16 @@
<head>
<title></title>
<link rel="stylesheet" href="chrome://payments/content/paymentDialog.css"/>
</head>
<body>
<iframe type="content"
id="paymentRequestFrame"
+ width="600"
+ height="400"
mozbrowser="true"
remote="true"
name="paymentRequestFrame"></iframe>
<script src="chrome://payments/content/paymentDialog.js"></script>
</body>
</html>
--- a/toolkit/components/payments/jar.mn
+++ b/toolkit/components/payments/jar.mn
@@ -4,9 +4,15 @@
toolkit.jar:
% content payments %content/payments/
content/payments/paymentDialog.css (content/paymentDialog.css)
content/payments/paymentDialog.js (content/paymentDialog.js)
content/payments/paymentDialogFrameScript.js (content/paymentDialogFrameScript.js)
content/payments/paymentDialog.xhtml (content/paymentDialog.xhtml)
% resource payments %res/payments/
- res/payments (res/paymentRequest.*)
+ res/payments (res/paymentRequest.*)
+ res/payments/components.js (res/components.js)
+ res/payments/containers.js (res/containers.js)
+ res/payments/debugging.html (res/debugging.html)
+ res/payments/debugging.js (res/debugging.js)
+ res/payments/paymentsStore.js (res/paymentsStore.js)
+ res/payments/vendor (res/vendor/*.js)
--- a/toolkit/components/payments/paymentUIService.js
+++ b/toolkit/components/payments/paymentUIService.js
@@ -20,16 +20,52 @@ const { classes: Cc, interfaces: Ci, res
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyServiceGetter(this,
"paymentSrv",
"@mozilla.org/dom/payments/payment-request-service;1",
"nsIPaymentRequestService");
+XPCOMUtils.defineLazyGetter(this, "profileStorage", () => {
+ let _profileStorage;
+
+ try {
+ _profileStorage = Cu.import("resource://formautofill/ProfileStorage.jsm", {}).profileStorage;
+ _profileStorage.initialize();
+ } catch (e) {
+ _profileStorage = null;
+ }
+
+ return _profileStorage;
+});
+
+/*
+ // get storage info, and keep only the fields we need
+ let profiles = profileStorage.addresses.getAll().map(profile => {
+ return {
+ address: profile["street-address"],
+ addressLines: [
+ profile["address-line1"],
+ profile["address-line2"],
+ profile["address-line3"],
+ ],
+ city: profile["address-level2"],
+ country: profile["country-name"],
+ email: profile.email,
+ name: profile.name,
+ organization: profile.organization,
+ phone: profile.tel,
+ postalCode: profile["postal-code"],
+ region: profile["address-level1"],
+ // dependentLocality && sortingCode && languageCode ??
+ };
+ });
+*/
+
function PaymentUIService() {
this.wrappedJSObject = this;
XPCOMUtils.defineLazyGetter(this, "log", () => {
let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
return new ConsoleAPI({
maxLogLevelPref: "dom.payments.loglevel",
prefix: "Payment UI Service",
});
@@ -53,50 +89,62 @@ PaymentUIService.prototype = {
"modal,dialog,centerscreen,resizable=no");
},
abortPayment(requestId) {
this.log.debug("abortPayment:", requestId);
let abortResponse = Cc["@mozilla.org/dom/payments/payment-abort-action-response;1"]
.createInstance(Ci.nsIPaymentAbortActionResponse);
- let enu = Services.wm.getEnumerator(null);
- let win;
- while ((win = enu.getNext())) {
- if (win.name == `${this.REQUEST_ID_PREFIX}${requestId}`) {
- this.log.debug(`closing: ${win.name}`);
- win.close();
- break;
- }
- }
+ let found = this.closeDialog(requestId);
// if `win` is falsy, then we haven't found the dialog, so the abort fails
// otherwise, the abort is successful
- let response = win ?
+ let response = found ?
Ci.nsIPaymentActionResponse.ABORT_SUCCEEDED :
Ci.nsIPaymentActionResponse.ABORT_FAILED;
abortResponse.init(requestId, response);
paymentSrv.respondPayment(abortResponse);
},
completePayment(requestId) {
this.log.debug("completePayment:", requestId);
let completeResponse = Cc["@mozilla.org/dom/payments/payment-complete-action-response;1"]
.createInstance(Ci.nsIPaymentCompleteActionResponse);
- completeResponse.init(requestId, Ci.nsIPaymentActionResponse.COMPLTETE_SUCCEEDED);
+ completeResponse.init(requestId, Ci.nsIPaymentActionResponse.COMPLETE_SUCCEEDED);
paymentSrv.respondPayment(completeResponse.QueryInterface(Ci.nsIPaymentActionResponse));
+
+ this.closeDialog(requestId); // TODO
},
updatePayment(requestId) {
this.log.debug("updatePayment:", requestId);
},
// other helper methods
+ /**
+ * @param {string} requestId - Payment Request ID of the dialog to close.
+ * @returns {boolean} whether the specified dialog was closed.
+ */
+ closeDialog(requestId) {
+ let enu = Services.wm.getEnumerator(null);
+ let win;
+ while ((win = enu.getNext())) {
+ if (win.name == `${this.REQUEST_ID_PREFIX}${requestId}`) {
+ this.log.debug(`closing: ${win.name}`);
+ win.close();
+ return true;
+ }
+ }
+
+ return false;
+ },
+
requestIdForWindow(window) {
let windowName = window.name;
return windowName.startsWith(this.REQUEST_ID_PREFIX) ?
windowName.replace(this.REQUEST_ID_PREFIX, "") : // returns suffix, which is the requestId
null;
},
};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/payments/res/components.js
@@ -0,0 +1,85 @@
+/* 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/. */
+
+/**
+ * State-less presentational components.
+ *
+ * Do not connect to state.
+ */
+
+class PaymentElement extends HTMLElement {
+ constructor() {
+ super();
+
+ console.log("constructing:", this.constructor.name);
+
+ // Reflect property changes for `observedAttributes` to attributes.
+ for (let name of (this.constructor.observedAttributes || [])) {
+ if (name in this) {
+ // Don't overwrite existing properties.
+ continue;
+ }
+ Object.defineProperty(this, name.replace(/-([a-z])/g, ($0, $1) => $1.toUpperCase()), {
+ configurable: true,
+ get() {
+ return this.getAttribute(name);
+ },
+ set(value) {
+ if (value === null || value === undefined) {
+ this.removeAttribute(name);
+ } else {
+ this.setAttribute(name, value);
+ }
+ },
+ });
+ }
+ }
+
+ attributeChangedCallback(attr, oldValue, newValue) {
+ this.render();
+ }
+}
+
+class PaymentItem extends PaymentElement {
+ static get observedAttributes() {
+ return ["label", "currency", "value"];
+ }
+
+ render() {
+ if (!this._labelEl && !this._priceEl) {
+ this._labelEl = document.createElement("dt");
+ this._priceEl = document.createElement("dd");
+ this._currencyEl = document.createElement("currency-amount");
+ this._priceEl.appendChild(this._currencyEl);
+ this.appendChild(this._labelEl);
+ this.appendChild(this._priceEl);
+ }
+
+ this._labelEl.textContent = this.getAttribute("label");
+ this._currencyEl.currency = this.getAttribute("currency");
+ this._currencyEl.value = this.getAttribute("value");
+ }
+}
+
+customElements.define("payment-item", PaymentItem);
+
+class CurrencyAmount extends PaymentElement {
+ static get observedAttributes() {
+ return ["currency", "value"];
+ }
+
+ render() {
+ if (this.value && this.currency) {
+ const formatter = new Intl.NumberFormat(navigator.languages, {
+ style: "currency",
+ currency: this.currency,
+ currencyDisplay: "symbol",
+ });
+ this.textContent = formatter.format(this.value);
+ } else {
+ this.textContent = "";
+ }
+ }
+}
+customElements.define("currency-amount", CurrencyAmount);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/payments/res/containers.js
@@ -0,0 +1,298 @@
+/* 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/. */
+
+let requestStore = new PaymentsStore({
+ savedAddresses: [],
+ savedBasicCards: [],
+ request: {
+ tabId: null,
+ topLevelPrincipal: {URI: {displayHost: null}},
+ requestId: null,
+ paymentMethods: [],
+ paymentDetails: {
+ id: null,
+ totalItem: {label: null, amount: {currency: null, value: null}},
+ displayItems: [],
+ shippingOptions: [],
+ modifiers: null,
+ error: "",
+ },
+ paymentOptions: {
+ requestPayerName: false,
+ requestPayerEmail: false,
+ requestPayerPhone: false,
+ requestShipping: false,
+ shippingType: "shipping",
+ },
+ },
+});
+
+/**
+ * Container components.
+ *
+ * Containers connected to state and pass state to inner components.
+ *
+ * They generally don't use attributes as they get all their info from global state.
+ */
+
+// TODO: use mixins instead of inheritance
+
+class PaymentStateSubscriber extends HTMLElement {
+ connectedCallback() {
+ requestStore.subscribe(this);
+ console.log("subscribed from", this);
+ }
+
+ disconnectedCallback() {
+ requestStore.unsubscribe(this);
+ }
+
+ shouldComponentUpdate(nextState) { // TODO
+ // Do a shallow prop comparison to track whether there were any changes.
+ for (let key in nextState) {
+ if (nextState[key] !== this.state[key]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ stateChangeCallback(state) {
+ this.render(state);
+ }
+
+ /**
+ * Forward events dispatched on the element to on* methods.
+ */
+ handleEvent(event) {
+ let handlerName = "on" + event.type;
+ if (typeof(this[handlerName]) != "function") {
+ return;
+ }
+
+ this[handlerName](event);
+ }
+}
+
+class PaymentItems extends PaymentStateSubscriber {
+ constructor() {
+ super();
+ this._details = document.createElement("details");
+ this._summary = document.createElement("summary");
+ this._summary.textContent = "All Items"; // TODO: l10n
+ this._list = document.createElement("dl");
+ }
+
+ render(state) {
+ this._list.innerHTML = "";
+ console.log("rendering", this);
+ let displayItems = state.request.paymentDetails.displayItems;
+ for (let item of displayItems) {
+ let pi = new PaymentItem();
+ pi.label = item.label;
+ pi.value = item.amount.value;
+ pi.currency = item.amount.currency;
+ this._list.appendChild(pi);
+ }
+
+ if (displayItems.length == 0) {
+ let pi = new PaymentItem();
+ pi.label = "Subtotal"; // l10n
+ pi.value = ""; // TODO: what goes here?
+ this._list.appendChild(pi);
+ }
+
+ this._summary.hidden = displayItems.length <= 1;
+ // TODO: could handle already toggle opened or closed by user
+ // Hopefully we'll move this to its own page anyways
+ this._details.open = displayItems.length <= 1;
+
+ if (!this._details.parentNode) {
+ this._details.appendChild(this._summary);
+ this._details.appendChild(this._list);
+ this.appendChild(this._details);
+ }
+ }
+}
+
+customElements.define("payment-items", PaymentItems);
+
+class PaymentDropdown extends PaymentStateSubscriber {
+ constructor() {
+ super();
+ this._selector = document.createElement("select");
+ }
+
+ get selectedStateKey() {
+ return this.getAttribute("selected-state-key");
+ }
+
+ connectedCallback() {
+ this._selector.name = this.selectedStateKey;
+ this._selector.addEventListener("change", this);
+ this.appendChild(this._selector);
+ super.connectedCallback();
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback();
+ this._selector.removeEventListener("change", this);
+ }
+
+ onchange(evt) {
+ requestStore.setState({
+ [this.selectedStateKey]: evt.target.value,
+ });
+ }
+}
+
+class AddressPicker extends PaymentDropdown {
+ render(state) {
+ console.log("rendering", this.id);
+ this._selector.innerHTML = "";
+ let frag = new DocumentFragment();
+ for (let address of state.savedAddresses) {
+ let isSelected = state[this.selectedStateKey] == address.guid;
+ frag.appendChild(new Option(address.name,
+ address.guid,
+ isSelected,
+ isSelected));
+ }
+ this._selector.appendChild(frag);
+ }
+}
+
+customElements.define("address-picker", AddressPicker);
+
+class BasicCardPicker extends PaymentDropdown {
+ constructor() {
+ super();
+ this._securityCodeField = document.createElement("input");
+ this._securityCodeField.placeholder = "CVV"; // TODO: l10n
+ this._securityCodeField.size = 3;
+ this._securityCodeField.autocomplete = "off";
+ // this._securityCodeField.type = "password";
+ this._securityCodeField.name = "cardSecurityCode";
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.appendChild(this._securityCodeField);
+ }
+
+ render(state) {
+ this._selector.innerHTML = "";
+ let frag = new DocumentFragment();
+ for (let card of state.savedBasicCards) {
+ let isSelected = state[this.selectedStateKey] == card.guid;
+ frag.appendChild(new Option(`${card["cc-number"]}, ${card["cc-name"]}`,
+ card.guid,
+ isSelected,
+ isSelected));
+ }
+ this._selector.appendChild(frag);
+ }
+}
+
+customElements.define("basiccard-picker", BasicCardPicker);
+
+class ShippingOptionPicker extends PaymentDropdown {
+ render(state) {
+ this._selector.innerHTML = "";
+ let shippingOptions = state.request.paymentDetails.shippingOptions;
+ let merchantSelected = (shippingOptions.find(opt => opt.selected) || {}).id;
+ let selected = state[this.selectedStateKey] || merchantSelected;
+
+ let frag = new DocumentFragment();
+ for (let opt of shippingOptions) {
+ let isSelected = selected === opt.id;
+ frag.appendChild(new Option(opt.label,
+ opt.id,
+ isSelected,
+ isSelected));
+ }
+ this._selector.appendChild(frag);
+ }
+}
+
+customElements.define("shippingoption-picker", ShippingOptionPicker);
+
+class PaymentDialog extends PaymentStateSubscriber {
+ constructor() {
+ super();
+ this._template = document.getElementById("payment-dialog-template");
+ this._requestStore = requestStore;
+ }
+
+ connectedCallback() {
+ let contents = document.importNode(this._template.content, true);
+ this._hostNameEl = contents.querySelector("#host-name");
+
+ this._cancelButton = contents.querySelector("#cancel");
+ this._cancelButton.addEventListener("click", this);
+
+ this._form = contents.querySelector("form");
+ this._form.addEventListener("submit", this);
+
+ this.appendChild(contents);
+
+ super.connectedCallback();
+ }
+
+ disconnectedCallback() {
+ this._cancelButtonEl.removeEventListener("click", this);
+ this._form.removeEventListener("submit", this);
+ super.disconnectedCallback();
+ }
+
+ setLoadingState(state) {
+ requestStore.setState(state);
+ }
+
+ onclick(event) {
+ PaymentRequest.cancel();
+ }
+
+ // https://w3c.github.io/payment-method-basic-card/#basiccardresponse-dictionary
+ convertSavedCardToBasicCardResponse(savedCard, cardSecurityCode, billingAddress = null) {
+ return {
+ cardNumber: savedCard["cc-number-encrypted"], // TODO: decrypt
+ cardholderName: savedCard["cc-name"],
+ cardSecurityCode,
+ expiryMonth: String(savedCard["cc-exp-month"]).padStart(2, "0"),
+ expiryYear: String(savedCard["cc-exp-year"]),
+ billingAddress,
+ };
+ }
+
+ onsubmit(event) {
+ event.preventDefault();
+ let formData = new FormData(event.target);
+ let state = requestStore.getState();
+ let selectedCardGuid = formData.get("selectedBasicCard");
+ let selectedCard = state.savedBasicCards.find(card => selectedCardGuid == card.guid);
+ let cardSecurityCode = formData.get("cardSecurityCode");
+
+ // TODO: only send info that was requested
+ PaymentRequest.pay({
+ payerName: "Foo",
+ payerEmail: "foo@bar.com",
+ payerPhone: "1234567890",
+ paymentMethodName: "basic-card",
+ paymentMethodData: this.convertSavedCardToBasicCardResponse(selectedCard, cardSecurityCode),
+ });
+ }
+
+ render() {
+ let request = requestStore.getState().request;
+ this._hostNameEl.textContent = request.topLevelPrincipal.URI.displayHost;
+
+ let totalItem = request.paymentDetails.totalItem;
+ let totalAmountEl = this.querySelector("#total > currency-amount");
+ totalAmountEl.value = totalItem.amount.value;
+ totalAmountEl.currency = totalItem.amount.currency;
+ }
+}
+
+customElements.define("payment-dialog", PaymentDialog);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/payments/res/debugging.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'">
+ <script src="debugging.js"></script>
+ </head>
+ <body>
+ <div>
+ <button id="logState">Log state</button>
+ <button id="refresh">Refresh</button>
+ </div>
+ <div>
+ <button id="setRequest1">Set Request 1</button>
+ <button id="setRequest2">Set Request 2</button>
+ </div>
+ <div>
+ <button id="setAddresses1">Set Addreses 1</button>
+ <button id="delete1Address">Delete 1 Address</button>
+ </div>
+ <div>
+ <button id="setBasicCards1">Set Basic Cards 1</button>
+ </div>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/payments/res/debugging.js
@@ -0,0 +1,196 @@
+const requestStore = window.parent.document.querySelector("payment-dialog")._requestStore;
+let REQUEST_1 = {
+ tabId: 9,
+ topLevelPrincipal: {URI: {displayHost: "tschaeff.github.io"}},
+ requestId: "3797081f-a96b-c34b-a58b-1083c6e66e25",
+ paymentMethods: [],
+ paymentDetails: {
+ id: "",
+ totalItem: {label: "Demo total", amount: {currency: "EUR", value: "1.00"}, pending: false},
+ displayItems: [
+ {
+ label: "Square",
+ amount: {
+ currency: "USD",
+ value: "5",
+ },
+ },
+ ],
+ shippingOptions: [
+ {
+ id: "123",
+ label: "Fast",
+ amount: {
+ currency: "USD",
+ value: 10,
+ },
+ selected: false,
+ },
+ {
+ id: "456",
+ label: "Faster (default)",
+ amount: {
+ currency: "USD",
+ value: 20,
+ },
+ selected: true,
+ },
+ ],
+ modifiers: null,
+ error: "",
+ },
+ paymentOptions: {
+ requestPayerName: false,
+ requestPayerEmail: false,
+ requestPayerPhone: false,
+ requestShipping: false,
+ shippingType: "shipping",
+ },
+};
+
+let REQUEST_2 = {
+ tabId: 9,
+ topLevelPrincipal: {URI: {displayHost: "example.com"}},
+ requestId: "3797081f-a96b-c34b-a58b-1083c6e66e25",
+ paymentMethods: [],
+ paymentDetails: {
+ id: "",
+ totalItem: {label: "Demo total", amount: {currency: "CAD", value: "25.75"}, pending: false},
+ displayItems: [
+ {
+ label: "Triangle",
+ amount: {
+ currency: "CAD",
+ value: "3",
+ },
+ },
+ {
+ label: "Circle",
+ amount: {
+ currency: "EUR",
+ value: "10.50",
+ },
+ },
+ ],
+ shippingOptions: [
+ {
+ id: "123",
+ label: "Fast (default)",
+ amount: {
+ currency: "USD",
+ value: 10,
+ },
+ selected: true,
+ },
+ {
+ id: "947",
+ label: "Slow",
+ amount: {
+ currency: "USD",
+ value: 10,
+ },
+ selected: false,
+ },
+ ],
+ modifiers: null,
+ error: "",
+ },
+ paymentOptions: {
+ requestPayerName: false,
+ requestPayerEmail: false,
+ requestPayerPhone: false,
+ requestShipping: false,
+ shippingType: "shipping",
+ },
+};
+
+let ADDRESSES_1 = [
+ {
+ guid: "48bnds6854t",
+ name: "Mr. Foo",
+ },
+ {
+ guid: "65tfdgg34",
+ name: "Mrs. Bar",
+ },
+];
+
+let BASIC_CARDS_1 = [
+ {
+ "cc-exp-month": 5,
+ "cc-number-encrypted": "_decrypt_me_",
+ "cc-number": "************1234",
+ "guid": "5bd7e7337f18",
+ "version": 1,
+ "timeCreated": 1506397876390,
+ "timeLastModified": 1506397909614,
+ "timeLastUsed": 0,
+ "timesUsed": 0,
+ "cc-name": "John Smith",
+ "cc-exp-year": 2020,
+ "cc-given-name": "John",
+ "cc-additional-name": "",
+ "cc-family-name": "Smith",
+ "cc-exp": "2020-05",
+ },
+ {
+ "cc-exp-month": 6,
+ "cc-number-encrypted": "_decrypt_me_",
+ "cc-number": "************8473",
+ "guid": "87gh3hf6g4",
+ "version": 1,
+ "timeCreated": 1506397876390,
+ "timeLastModified": 1506397909614,
+ "timeLastUsed": 0,
+ "timesUsed": 0,
+ "cc-name": "Mr. J. Magoo",
+ "cc-exp-year": 2019,
+ "cc-given-name": "J.",
+ "cc-additional-name": "",
+ "cc-family-name": "Magoo",
+ "cc-exp": "2019-06",
+ },
+];
+
+let buttonActions = {
+ delete1Address() {
+ requestStore.setState({
+ savedAddresses: requestStore.getState().savedAddresses.slice(1),
+ });
+ },
+
+ logState() {
+ let state = requestStore.getState();
+ console.log(state);
+ dump(`${JSON.stringify(state, null, 2)}\n`);
+ },
+
+ refresh() {
+ window.parent.location.reload(true);
+ },
+
+ setAddresses1() {
+ requestStore.setState({savedAddresses: ADDRESSES_1});
+ },
+
+ setBasicCards1() {
+ requestStore.setState({savedBasicCards: BASIC_CARDS_1});
+ },
+
+ setRequest1() {
+ requestStore.setState({request: REQUEST_1});
+ },
+
+ setRequest2() {
+ requestStore.setState({request: REQUEST_2});
+ },
+};
+
+window.addEventListener("click", function onButtonClick(evt) {
+ let id = evt.target.id;
+ if (!id || typeof(buttonActions[id]) != "function") {
+ return;
+ }
+
+ buttonActions[id]();
+});
--- a/toolkit/components/payments/res/paymentRequest.css
+++ b/toolkit/components/payments/res/paymentRequest.css
@@ -1,24 +1,32 @@
/* 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 {
- background: -moz-dialog;
+body {
+ background: white;
+}
+
+dl {
+ max-height: 8em;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+summary {
+ cursor: pointer;
+}
+
+#debugging {
+ float: right;
}
#total {
border: 1px solid black;
margin: 5px;
text-align: center;
}
#total .label {
font-size: 15px;
font-weight: bold;
}
-
-#cancel {
- position: absolute;
- bottom: 10px;
- left: 10px;
-}
rename from toolkit/components/payments/res/paymentRequest.xhtml
rename to toolkit/components/payments/res/paymentRequest.html
--- a/toolkit/components/payments/res/paymentRequest.xhtml
+++ b/toolkit/components/payments/res/paymentRequest.html
@@ -1,24 +1,55 @@
-<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<!DOCTYPE html>
-<html xmlns="http://www.w3.org/1999/xhtml">
+<html>
<head>
+ <meta charset="utf-8">
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'">
<title></title>
- <link rel="stylesheet" href="resource://payments/paymentRequest.css" />
- <script src="resource://payments/paymentRequest.js"></script>
+ <link rel="stylesheet" href="paymentRequest.css"/>
+ <script src="vendor/custom-elements.min.js"></script>
+ <script src="paymentsStore.js"></script>
+ <script src="components.js"></script>
+ <script src="containers.js"></script>
+ <script src="paymentRequest.js"></script>
+
+ <template id="payment-dialog-template">
+ <form>
+ <h1>Your Order</h1>
+ <div id="host-name">placeholder.example.com</div>
+
+ <payment-items></payment-items>
+
+ <div id="total">
+ <h2 class="label">Total</h2>
+ <currency-amount currency="USD" value="100.00"></currency-amount>
+ </div>
+
+ <fieldset>
+ <legend>Ship to</legend>
+ <shippingoption-picker selected-state-key="selectedShippingOption"></shippingoption-picker>
+ <address-picker id="shipping-address" selected-state-key="selectedShippingAddress"></address-picker>
+ <add-address></add-address>
+ </fieldset>
+
+ <fieldset>
+ <legend>Pay by</legend>
+ <basiccard-picker selected-state-key="selectedBasicCard"></basiccard-picker>
+ <add-address></add-address>
+ <address-picker id="billing-address" selected-state-key="selectedBillingAddress"></address-picker>
+ </fieldset>
+
+ <div id="controls-container">
+ <button id="cancel">Cancel payment</button>
+ <button id="pay">Pay</button>
+ </div>
+ </form>
+ </template>
</head>
<body>
- <div id="host-name"></div>
+ <iframe id="debugging" src="debugging.html"></iframe>
- <div id="total">
- <h2 class="label"></h2>
- <span class="value"></span>
- <span class="currency"></span>
- </div>
- <div id="controls-container">
- <button id="cancel">Cancel payment</button>
- </div>
+ <payment-dialog></payment-dialog>
</body>
</html>
--- a/toolkit/components/payments/res/paymentRequest.js
+++ b/toolkit/components/payments/res/paymentRequest.js
@@ -5,19 +5,17 @@
/**
* Loaded in the unprivileged frame of each payment dialog.
*
* Communicates with privileged code via DOM Events.
*/
"use strict";
-let PaymentRequest = {
- request: null,
-
+var PaymentRequest = {
init() {
// listen to content
window.addEventListener("paymentChromeToContent", this);
// listen to user events
window.addEventListener("DOMContentLoaded", this, {once: true});
// This scope is now ready to listen to the initialization data
@@ -26,22 +24,16 @@ let PaymentRequest = {
handleEvent(event) {
switch (event.type) {
case "DOMContentLoaded": {
this.onPaymentRequestLoad();
break;
}
case "click": {
- switch (event.target.id) {
- case "cancel": {
- this.onCancel();
- break;
- }
- }
break;
}
case "unload": {
this.onPaymentRequestUnload();
break;
}
case "paymentChromeToContent": {
this.onChromeToContent(event);
@@ -63,42 +55,38 @@ let PaymentRequest = {
document.dispatchEvent(event);
},
onChromeToContent({detail}) {
let {messageType} = detail;
switch (messageType) {
case "showPaymentRequest": {
- this.request = detail.request;
-
- let hostNameEl = document.getElementById("host-name");
- hostNameEl.textContent = this.request.topLevelPrincipal.URI.displayHost;
-
- let totalItem = this.request.paymentDetails.totalItem;
- let totalEl = document.getElementById("total");
- totalEl.querySelector(".value").textContent = totalItem.amount.value;
- totalEl.querySelector(".currency").textContent = totalItem.amount.currency;
- totalEl.querySelector(".label").textContent = totalItem.label;
+ document.querySelector("payment-dialog").setLoadingState({
+ request: detail.request,
+ savedAddresses: detail.savedAddresses,
+ savedBasicCards: detail.savedBasicCards,
+ });
break;
}
}
},
onPaymentRequestLoad(requestId) {
- let cancelBtn = document.getElementById("cancel");
- cancelBtn.addEventListener("click", this, {once: true});
-
window.addEventListener("unload", this, {once: true});
this.sendMessageToChrome("paymentDialogReady");
},
- onCancel() {
+ cancel() {
this.sendMessageToChrome("paymentCancel");
},
+ pay(data) {
+ this.sendMessageToChrome("pay", data);
+ },
+
onPaymentRequestUnload() {
// remove listeners that may be used multiple times here
window.removeEventListener("paymentChromeToContent", this);
},
};
PaymentRequest.init();
new file mode 100644
--- /dev/null
+++ b/toolkit/components/payments/res/paymentsStore.js
@@ -0,0 +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/. */
+
+/* exported PaymentsStore */
+
+class PaymentsStore {
+ constructor(defaultState = {}) {
+ console.debug("constructing store");
+ this._state = defaultState;
+ this._nextNotifification = 0;
+ this.subscribers = new Set();
+ }
+
+ getState() {
+ return Object.assign({}, this._state);
+ }
+
+ async setState(obj) {
+ console.debug("Setting state:", obj);
+ Object.assign(this._state, obj);
+ let thisChangeNum = ++this._nextNotifification;
+
+ // This lets any *synchronous* setState
+ // calls that happen after the current setState call complete first.
+ // Their effects on the state will be batched up before the render
+ // call below actually happens.
+ await Promise.resolve();
+
+ // https://github.com/elix/elix/blob/frp/mixins/ReactiveMixin.js
+
+ if (thisChangeNum !== this._nextNotifification) {
+ console.log("not notifying for stale state change", obj);
+ return;
+ }
+
+ console.log("notifying subscribers of state change", this._state);
+ for (let subscriber of this.subscribers) {
+ try {
+ subscriber.stateChangeCallback(this.getState());
+ } catch (ex) {
+ console.error(ex);
+ }
+ }
+ }
+
+ subscribe(component) {
+ if (this.subscribers.has(component)) {
+ return;
+ }
+
+ this.subscribers.add(component);
+ component.stateChangeCallback(this.getState());
+ }
+
+ unsubscribe(component) {
+ this.subscribers.delete(component);
+ }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/payments/res/vendor/custom-elements.min.js
@@ -0,0 +1,37 @@
+(function(){
+'use strict';var h=new function(){};var aa=new Set("annotation-xml color-profile font-face font-face-src font-face-uri font-face-format font-face-name missing-glyph".split(" "));function m(b){var a=aa.has(b);b=/^[a-z][.0-9_a-z]*-[\-.0-9_a-z]*$/.test(b);return!a&&b}function n(b){var a=b.isConnected;if(void 0!==a)return a;for(;b&&!(b.__CE_isImportDocument||b instanceof Document);)b=b.parentNode||(window.ShadowRoot&&b instanceof ShadowRoot?b.host:void 0);return!(!b||!(b.__CE_isImportDocument||b instanceof Document))}
+function p(b,a){for(;a&&a!==b&&!a.nextSibling;)a=a.parentNode;return a&&a!==b?a.nextSibling:null}
+function t(b,a,d){d=d?d:new Set;for(var c=b;c;){if(c.nodeType===Node.ELEMENT_NODE){var e=c;a(e);var f=e.localName;if("link"===f&&"import"===e.getAttribute("rel")){c=e.import;if(c instanceof Node&&!d.has(c))for(d.add(c),c=c.firstChild;c;c=c.nextSibling)t(c,a,d);c=p(b,e);continue}else if("template"===f){c=p(b,e);continue}if(e=e.__CE_shadowRoot)for(e=e.firstChild;e;e=e.nextSibling)t(e,a,d)}c=c.firstChild?c.firstChild:p(b,c)}}function u(b,a,d){b[a]=d};function v(){this.a=new Map;this.o=new Map;this.f=[];this.b=!1}function ba(b,a,d){b.a.set(a,d);b.o.set(d.constructor,d)}function w(b,a){b.b=!0;b.f.push(a)}function x(b,a){b.b&&t(a,function(a){return y(b,a)})}function y(b,a){if(b.b&&!a.__CE_patched){a.__CE_patched=!0;for(var d=0;d<b.f.length;d++)b.f[d](a)}}function z(b,a){var d=[];t(a,function(b){return d.push(b)});for(a=0;a<d.length;a++){var c=d[a];1===c.__CE_state?b.connectedCallback(c):A(b,c)}}
+function B(b,a){var d=[];t(a,function(b){return d.push(b)});for(a=0;a<d.length;a++){var c=d[a];1===c.__CE_state&&b.disconnectedCallback(c)}}
+function C(b,a,d){d=d?d:{};var c=d.w||new Set,e=d.s||function(a){return A(b,a)},f=[];t(a,function(a){if("link"===a.localName&&"import"===a.getAttribute("rel")){var d=a.import;d instanceof Node&&"complete"===d.readyState?(d.__CE_isImportDocument=!0,d.__CE_hasRegistry=!0):a.addEventListener("load",function(){var d=a.import;if(!d.__CE_documentLoadHandled){d.__CE_documentLoadHandled=!0;d.__CE_isImportDocument=!0;d.__CE_hasRegistry=!0;var f=new Set(c);f.delete(d);C(b,d,{w:f,s:e})}})}else f.push(a)},c);
+if(b.b)for(a=0;a<f.length;a++)y(b,f[a]);for(a=0;a<f.length;a++)e(f[a])}
+function A(b,a){if(void 0===a.__CE_state){var d=b.a.get(a.localName);if(d){d.constructionStack.push(a);var c=d.constructor;try{try{if(new c!==a)throw Error("The custom element constructor did not produce the element being upgraded.");}finally{d.constructionStack.pop()}}catch(r){throw a.__CE_state=2,r;}a.__CE_state=1;a.__CE_definition=d;if(d.attributeChangedCallback)for(d=d.observedAttributes,c=0;c<d.length;c++){var e=d[c],f=a.getAttribute(e);null!==f&&b.attributeChangedCallback(a,e,null,f,null)}n(a)&&
+b.connectedCallback(a)}}}v.prototype.connectedCallback=function(b){var a=b.__CE_definition;a.connectedCallback&&a.connectedCallback.call(b)};v.prototype.disconnectedCallback=function(b){var a=b.__CE_definition;a.disconnectedCallback&&a.disconnectedCallback.call(b)};v.prototype.attributeChangedCallback=function(b,a,d,c,e){var f=b.__CE_definition;f.attributeChangedCallback&&-1<f.observedAttributes.indexOf(a)&&f.attributeChangedCallback.call(b,a,d,c,e)};function D(b,a){this.c=b;this.a=a;this.b=void 0;C(this.c,this.a);"loading"===this.a.readyState&&(this.b=new MutationObserver(this.f.bind(this)),this.b.observe(this.a,{childList:!0,subtree:!0}))}function E(b){b.b&&b.b.disconnect()}D.prototype.f=function(b){var a=this.a.readyState;"interactive"!==a&&"complete"!==a||E(this);for(a=0;a<b.length;a++)for(var d=b[a].addedNodes,c=0;c<d.length;c++)C(this.c,d[c])};function ca(){var b=this;this.b=this.a=void 0;this.f=new Promise(function(a){b.b=a;b.a&&a(b.a)})}function F(b){if(b.a)throw Error("Already resolved.");b.a=void 0;b.b&&b.b(void 0)};function G(b){this.i=!1;this.c=b;this.m=new Map;this.j=function(b){return b()};this.g=!1;this.l=[];this.u=new D(b,document)}
+G.prototype.define=function(b,a){var d=this;if(!(a instanceof Function))throw new TypeError("Custom element constructors must be functions.");if(!m(b))throw new SyntaxError("The element name '"+b+"' is not valid.");if(this.c.a.get(b))throw Error("A custom element with name '"+b+"' has already been defined.");if(this.i)throw Error("A custom element is already being defined.");this.i=!0;var c,e,f,r,k;try{var g=function(b){var a=l[b];if(void 0!==a&&!(a instanceof Function))throw Error("The '"+b+"' callback must be a function.");
+return a},l=a.prototype;if(!(l instanceof Object))throw new TypeError("The custom element constructor's prototype is not an object.");c=g("connectedCallback");e=g("disconnectedCallback");f=g("adoptedCallback");r=g("attributeChangedCallback");k=a.observedAttributes||[]}catch(q){return}finally{this.i=!1}a={localName:b,constructor:a,connectedCallback:c,disconnectedCallback:e,adoptedCallback:f,attributeChangedCallback:r,observedAttributes:k,constructionStack:[]};ba(this.c,b,a);this.l.push(a);this.g||
+(this.g=!0,this.j(function(){return da(d)}))};function da(b){if(!1!==b.g){b.g=!1;for(var a=b.l,d=[],c=new Map,e=0;e<a.length;e++)c.set(a[e].localName,[]);C(b.c,document,{s:function(a){if(void 0===a.__CE_state){var e=a.localName,f=c.get(e);f?f.push(a):b.c.a.get(e)&&d.push(a)}}});for(e=0;e<d.length;e++)A(b.c,d[e]);for(;0<a.length;){for(var f=a.shift(),e=f.localName,f=c.get(f.localName),r=0;r<f.length;r++)A(b.c,f[r]);(e=b.m.get(e))&&F(e)}}}G.prototype.get=function(b){if(b=this.c.a.get(b))return b.constructor};
+G.prototype.whenDefined=function(b){if(!m(b))return Promise.reject(new SyntaxError("'"+b+"' is not a valid custom element name."));var a=this.m.get(b);if(a)return a.f;a=new ca;this.m.set(b,a);this.c.a.get(b)&&!this.l.some(function(a){return a.localName===b})&&F(a);return a.f};G.prototype.v=function(b){E(this.u);var a=this.j;this.j=function(d){return b(function(){return a(d)})}};window.CustomElementRegistry=G;G.prototype.define=G.prototype.define;G.prototype.get=G.prototype.get;
+G.prototype.whenDefined=G.prototype.whenDefined;G.prototype.polyfillWrapFlushCallback=G.prototype.v;var H=window.Document.prototype.createElement,ea=window.Document.prototype.createElementNS,fa=window.Document.prototype.importNode,ga=window.Document.prototype.prepend,ha=window.Document.prototype.append,ia=window.DocumentFragment.prototype.prepend,ja=window.DocumentFragment.prototype.append,I=window.Node.prototype.cloneNode,J=window.Node.prototype.appendChild,K=window.Node.prototype.insertBefore,L=window.Node.prototype.removeChild,M=window.Node.prototype.replaceChild,N=Object.getOwnPropertyDescriptor(window.Node.prototype,
+"textContent"),O=window.Element.prototype.attachShadow,P=Object.getOwnPropertyDescriptor(window.Element.prototype,"innerHTML"),Q=window.Element.prototype.getAttribute,R=window.Element.prototype.setAttribute,S=window.Element.prototype.removeAttribute,T=window.Element.prototype.getAttributeNS,U=window.Element.prototype.setAttributeNS,ka=window.Element.prototype.removeAttributeNS,la=window.Element.prototype.insertAdjacentElement,ma=window.Element.prototype.prepend,na=window.Element.prototype.append,
+V=window.Element.prototype.before,oa=window.Element.prototype.after,pa=window.Element.prototype.replaceWith,qa=window.Element.prototype.remove,ra=window.HTMLElement,W=Object.getOwnPropertyDescriptor(window.HTMLElement.prototype,"innerHTML"),sa=window.HTMLElement.prototype.insertAdjacentElement;function ta(){var b=X;window.HTMLElement=function(){function a(){var a=this.constructor,c=b.o.get(a);if(!c)throw Error("The custom element being constructed was not registered with `customElements`.");var e=c.constructionStack;if(!e.length)return e=H.call(document,c.localName),Object.setPrototypeOf(e,a.prototype),e.__CE_state=1,e.__CE_definition=c,y(b,e),e;var c=e.length-1,f=e[c];if(f===h)throw Error("The HTMLElement constructor was either called reentrantly for this constructor or called multiple times.");
+e[c]=h;Object.setPrototypeOf(f,a.prototype);y(b,f);return f}a.prototype=ra.prototype;return a}()};function Y(b,a,d){function c(a){return function(d){for(var e=[],c=0;c<arguments.length;++c)e[c-0]=arguments[c];for(var c=[],f=[],l=0;l<e.length;l++){var q=e[l];q instanceof Element&&n(q)&&f.push(q);if(q instanceof DocumentFragment)for(q=q.firstChild;q;q=q.nextSibling)c.push(q);else c.push(q)}a.apply(this,e);for(e=0;e<f.length;e++)B(b,f[e]);if(n(this))for(e=0;e<c.length;e++)f=c[e],f instanceof Element&&z(b,f)}}d.h&&(a.prepend=c(d.h));d.append&&(a.append=c(d.append))};function ua(){var b=X;u(Document.prototype,"createElement",function(a){if(this.__CE_hasRegistry){var d=b.a.get(a);if(d)return new d.constructor}a=H.call(this,a);y(b,a);return a});u(Document.prototype,"importNode",function(a,d){a=fa.call(this,a,d);this.__CE_hasRegistry?C(b,a):x(b,a);return a});u(Document.prototype,"createElementNS",function(a,d){if(this.__CE_hasRegistry&&(null===a||"http://www.w3.org/1999/xhtml"===a)){var c=b.a.get(d);if(c)return new c.constructor}a=ea.call(this,a,d);y(b,a);return a});
+Y(b,Document.prototype,{h:ga,append:ha})};function va(){var b=X;function a(a,c){Object.defineProperty(a,"textContent",{enumerable:c.enumerable,configurable:!0,get:c.get,set:function(a){if(this.nodeType===Node.TEXT_NODE)c.set.call(this,a);else{var e=void 0;if(this.firstChild){var d=this.childNodes,k=d.length;if(0<k&&n(this))for(var e=Array(k),g=0;g<k;g++)e[g]=d[g]}c.set.call(this,a);if(e)for(a=0;a<e.length;a++)B(b,e[a])}}})}u(Node.prototype,"insertBefore",function(a,c){if(a instanceof DocumentFragment){var e=Array.prototype.slice.apply(a.childNodes);
+a=K.call(this,a,c);if(n(this))for(c=0;c<e.length;c++)z(b,e[c]);return a}e=n(a);c=K.call(this,a,c);e&&B(b,a);n(this)&&z(b,a);return c});u(Node.prototype,"appendChild",function(a){if(a instanceof DocumentFragment){var c=Array.prototype.slice.apply(a.childNodes);a=J.call(this,a);if(n(this))for(var e=0;e<c.length;e++)z(b,c[e]);return a}c=n(a);e=J.call(this,a);c&&B(b,a);n(this)&&z(b,a);return e});u(Node.prototype,"cloneNode",function(a){a=I.call(this,a);this.ownerDocument.__CE_hasRegistry?C(b,a):x(b,a);
+return a});u(Node.prototype,"removeChild",function(a){var c=n(a),e=L.call(this,a);c&&B(b,a);return e});u(Node.prototype,"replaceChild",function(a,c){if(a instanceof DocumentFragment){var e=Array.prototype.slice.apply(a.childNodes);a=M.call(this,a,c);if(n(this))for(B(b,c),c=0;c<e.length;c++)z(b,e[c]);return a}var e=n(a),f=M.call(this,a,c),d=n(this);d&&B(b,c);e&&B(b,a);d&&z(b,a);return f});N&&N.get?a(Node.prototype,N):w(b,function(b){a(b,{enumerable:!0,configurable:!0,get:function(){for(var a=[],b=
+0;b<this.childNodes.length;b++)a.push(this.childNodes[b].textContent);return a.join("")},set:function(a){for(;this.firstChild;)L.call(this,this.firstChild);J.call(this,document.createTextNode(a))}})})};function wa(b){var a=Element.prototype;function d(a){return function(e){for(var c=[],d=0;d<arguments.length;++d)c[d-0]=arguments[d];for(var d=[],k=[],g=0;g<c.length;g++){var l=c[g];l instanceof Element&&n(l)&&k.push(l);if(l instanceof DocumentFragment)for(l=l.firstChild;l;l=l.nextSibling)d.push(l);else d.push(l)}a.apply(this,c);for(c=0;c<k.length;c++)B(b,k[c]);if(n(this))for(c=0;c<d.length;c++)k=d[c],k instanceof Element&&z(b,k)}}V&&(a.before=d(V));V&&(a.after=d(oa));pa&&u(a,"replaceWith",function(a){for(var e=
+[],c=0;c<arguments.length;++c)e[c-0]=arguments[c];for(var c=[],d=[],k=0;k<e.length;k++){var g=e[k];g instanceof Element&&n(g)&&d.push(g);if(g instanceof DocumentFragment)for(g=g.firstChild;g;g=g.nextSibling)c.push(g);else c.push(g)}k=n(this);pa.apply(this,e);for(e=0;e<d.length;e++)B(b,d[e]);if(k)for(B(b,this),e=0;e<c.length;e++)d=c[e],d instanceof Element&&z(b,d)});qa&&u(a,"remove",function(){var a=n(this);qa.call(this);a&&B(b,this)})};function xa(){var b=X;function a(a,c){Object.defineProperty(a,"innerHTML",{enumerable:c.enumerable,configurable:!0,get:c.get,set:function(a){var e=this,d=void 0;n(this)&&(d=[],t(this,function(a){a!==e&&d.push(a)}));c.set.call(this,a);if(d)for(var f=0;f<d.length;f++){var r=d[f];1===r.__CE_state&&b.disconnectedCallback(r)}this.ownerDocument.__CE_hasRegistry?C(b,this):x(b,this);return a}})}function d(a,c){u(a,"insertAdjacentElement",function(a,e){var d=n(e);a=c.call(this,a,e);d&&B(b,e);n(a)&&z(b,e);
+return a})}O&&u(Element.prototype,"attachShadow",function(a){return this.__CE_shadowRoot=a=O.call(this,a)});if(P&&P.get)a(Element.prototype,P);else if(W&&W.get)a(HTMLElement.prototype,W);else{var c=H.call(document,"div");w(b,function(b){a(b,{enumerable:!0,configurable:!0,get:function(){return I.call(this,!0).innerHTML},set:function(a){var b="template"===this.localName?this.content:this;for(c.innerHTML=a;0<b.childNodes.length;)L.call(b,b.childNodes[0]);for(;0<c.childNodes.length;)J.call(b,c.childNodes[0])}})})}u(Element.prototype,
+"setAttribute",function(a,c){if(1!==this.__CE_state)return R.call(this,a,c);var e=Q.call(this,a);R.call(this,a,c);c=Q.call(this,a);b.attributeChangedCallback(this,a,e,c,null)});u(Element.prototype,"setAttributeNS",function(a,c,d){if(1!==this.__CE_state)return U.call(this,a,c,d);var e=T.call(this,a,c);U.call(this,a,c,d);d=T.call(this,a,c);b.attributeChangedCallback(this,c,e,d,a)});u(Element.prototype,"removeAttribute",function(a){if(1!==this.__CE_state)return S.call(this,a);var c=Q.call(this,a);S.call(this,
+a);null!==c&&b.attributeChangedCallback(this,a,c,null,null)});u(Element.prototype,"removeAttributeNS",function(a,c){if(1!==this.__CE_state)return ka.call(this,a,c);var d=T.call(this,a,c);ka.call(this,a,c);var e=T.call(this,a,c);d!==e&&b.attributeChangedCallback(this,c,d,e,a)});sa?d(HTMLElement.prototype,sa):la?d(Element.prototype,la):console.warn("Custom Elements: `Element#insertAdjacentElement` was not patched.");Y(b,Element.prototype,{h:ma,append:na});wa(b)};/*
+
+ Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
+ This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
+ The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
+ The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
+ Code distributed by Google as part of the polymer project is also
+ subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
+*/
+var Z=window.customElements;if(!Z||Z.forcePolyfill||"function"!=typeof Z.define||"function"!=typeof Z.get){var X=new v;ta();ua();Y(X,DocumentFragment.prototype,{h:ia,append:ja});va();xa();document.__CE_hasRegistry=!0;var customElements=new G(X);Object.defineProperty(window,"customElements",{configurable:!0,enumerable:!0,value:customElements})};
+}).call(self);
+
+//# sourceMappingURL=custom-elements.min.js.map