diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index d63f8efe654..3065657a38c 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -128,6 +128,11 @@ #include #endif +#ifdef MOZ_PAY +#include "nsIPaymentContentHelperService.h" +#include "mozilla/dom/DOMRequest.h" +#endif + namespace mozilla { namespace dom { @@ -2672,6 +2677,36 @@ Navigator::MozE10sEnabled() return true; } +#ifdef MOZ_PAY +already_AddRefed +Navigator::MozPay(JSContext* aCx, + JS::Handle aJwts, + ErrorResult& aRv) +{ + if (!mWindow || !mWindow->GetDocShell()) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + nsresult rv; + nsCOMPtr service = + do_GetService("@mozilla.org/payment/content-helper-service;1", &rv); + if (!service) { + aRv.Throw(rv); + return nullptr; + } + + RefPtr request; + rv = service->Pay(mWindow, aJwts, getter_AddRefs(request)); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return nullptr; + } + + return request.forget().downcast(); +} +#endif // MOZ_PAY + /* static */ already_AddRefed Navigator::GetWindowFromGlobal(JSObject* aGlobal) diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h index 4ed32d5038e..17b40535a2a 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -42,6 +42,7 @@ class WakeLock; class ArrayBufferViewOrBlobOrStringOrFormData; struct MobileIdOptions; class ServiceWorkerContainer; +class DOMRequest; } // namespace dom } // namespace mozilla @@ -315,6 +316,12 @@ public: bool MozE10sEnabled(); +#ifdef MOZ_PAY + already_AddRefed MozPay(JSContext* aCx, + JS::Handle aJwts, + ErrorResult& aRv); +#endif // MOZ_PAY + static void GetAcceptLanguages(nsTArray& aLanguages); // WebIDL helper methods diff --git a/dom/payment/Payment.js b/dom/payment/Payment.js index e17565fc44e..95e4dc074e7 100644 --- a/dom/payment/Payment.js +++ b/dom/payment/Payment.js @@ -10,9 +10,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/DOMRequestHelper.jsm"); -const PAYMENTCONTENTHELPER_CID = - Components.ID("{a920adc0-c36e-4fd0-8de0-aac1ac6ebbd0}"); - const PAYMENT_IPC_MSG_NAMES = ["Payment:Success", "Payment:Failed"]; @@ -22,39 +19,82 @@ XPCOMUtils.defineLazyServiceGetter(this, "cpmm", "@mozilla.org/childprocessmessagemanager;1", "nsIMessageSender"); -function PaymentContentHelper() { +var _debug; +try { + _debug = Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL + && Services.prefs.getBoolPref(PREF_DEBUG); +} catch(e) { + _debug = false; +} + +function LOG(s) { + if (!_debug) { + return; + } + dump("-*- PaymentContentHelper: " + s + "\n"); +} + +function PaymentContentHelper(aWindow) { + this.initDOMRequestHelper(aWindow, PAYMENT_IPC_MSG_NAMES); }; PaymentContentHelper.prototype = { __proto__: DOMRequestIpcHelper.prototype, - QueryInterface: XPCOMUtils.generateQI([Ci.nsINavigatorPayment, - Ci.nsIDOMGlobalPropertyInitializer, - Ci.nsISupportsWeakReference, - Ci.nsIObserver]), - classID: PAYMENTCONTENTHELPER_CID, - classInfo: XPCOMUtils.generateCI({ - classID: PAYMENTCONTENTHELPER_CID, - contractID: "@mozilla.org/payment/content-helper;1", - classDescription: "Payment Content Helper", - flags: Ci.nsIClassInfo.DOM_OBJECT, - interfaces: [Ci.nsINavigatorPayment] - }), + receiveMessage: function receiveMessage(aMessage) { + let name = aMessage.name; + let msg = aMessage.json; + if (_debug) { + LOG("Received message '" + name + "': " + JSON.stringify(msg)); + } + let requestId = msg.requestId; + let request = this.takeRequest(requestId); + if (!request) { + return; + } + switch (name) { + case "Payment:Success": + Services.DOMRequest.fireSuccess(request, msg.result); + break; + case "Payment:Failed": + Services.DOMRequest.fireError(request, msg.errorMsg); + break; + } + }, +}; + +function PaymentContentHelperService() { +}; + +PaymentContentHelperService.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentContentHelperService]), + classID: Components.ID("{80035846-6732-4fcc-961b-f336b65218f4}"), + contractID: "@mozilla.org/payment/content-helper-service;1", + + _xpcom_factory: XPCOMUtils.generateSingletonFactory(PaymentContentHelperService), + + // keys are windows and values are PaymentContentHelpers + helpers: new WeakMap(), // nsINavigatorPayment + pay: function pay(aWindow, aJwts) { + let requestHelper = this.helpers.get(aWindow); + if (!requestHelper) { + requestHelper = new PaymentContentHelper(aWindow); + this.helpers.set(aWindow, requestHelper); + } + let request = requestHelper.createRequest(); + let requestId = requestHelper.getRequestId(request); - pay: function pay(aJwts) { - let request = this.createRequest(); - let requestId = this.getRequestId(request); - - let docShell = this._window.QueryInterface(Ci.nsIInterfaceRequestor) + let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShell); if (!docShell.isActive) { - if (this._debug) { - this.LOG("The caller application is a background app. No request " + - "will be sent"); + if (_debug) { + LOG("The caller application is a background app. No request will be " + + "sent"); } + let runnable = { run: function run() { Services.DOMRequest.fireError(request, "BACKGROUND_APP"); @@ -75,61 +115,6 @@ PaymentContentHelper.prototype = { }); return request; }, - - // nsIDOMGlobalPropertyInitializer - - init: function(aWindow) { - try { - if (!Services.prefs.getBoolPref("dom.mozPay.enabled")) { - return null; - } - } catch (e) { - return null; - } - - this._window = aWindow; - this.initDOMRequestHelper(aWindow, PAYMENT_IPC_MSG_NAMES); - - try { - this._debug = - Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL - && Services.prefs.getBoolPref(PREF_DEBUG); - } catch(e) { - this._debug = false; - } - - return Cu.exportFunction(this.pay.bind(this), aWindow); - }, - - // nsIFrameMessageListener - - receiveMessage: function receiveMessage(aMessage) { - let name = aMessage.name; - let msg = aMessage.json; - if (this._debug) { - this.LOG("Received message '" + name + "': " + JSON.stringify(msg)); - } - let requestId = msg.requestId; - let request = this.takeRequest(requestId); - if (!request) { - return; - } - switch (name) { - case "Payment:Success": - Services.DOMRequest.fireSuccess(request, msg.result); - break; - case "Payment:Failed": - Services.DOMRequest.fireError(request, msg.errorMsg); - break; - } - }, - - LOG: function LOG(s) { - if (!this._debug) { - return; - } - dump("-*- PaymentContentHelper: " + s + "\n"); - } }; -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentContentHelper]); +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentContentHelperService]); diff --git a/dom/payment/Payment.manifest b/dom/payment/Payment.manifest index 90fc3409004..42d8ccd5fd3 100644 --- a/dom/payment/Payment.manifest +++ b/dom/payment/Payment.manifest @@ -1,6 +1,5 @@ -component {a920adc0-c36e-4fd0-8de0-aac1ac6ebbd0} Payment.js -contract @mozilla.org/payment/content-helper;1 {a920adc0-c36e-4fd0-8de0-aac1ac6ebbd0} -category JavaScript-navigator-property mozPay @mozilla.org/payment/content-helper;1 +component {80035846-6732-4fcc-961b-f336b65218f4} Payment.js +contract @mozilla.org/payment/content-helper-service;1 {80035846-6732-4fcc-961b-f336b65218f4} component {b8bce4e7-fbf0-4719-a634-b1bf9018657c} PaymentFlowInfo.js contract @mozilla.org/payment/flow-info;1 {b8bce4e7-fbf0-4719-a634-b1bf9018657c} diff --git a/dom/payment/interfaces/moz.build b/dom/payment/interfaces/moz.build index 7046187080e..3c199ed30db 100644 --- a/dom/payment/interfaces/moz.build +++ b/dom/payment/interfaces/moz.build @@ -5,7 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. XPIDL_SOURCES += [ - 'nsINavigatorPayment.idl', + 'nsIPaymentContentHelperService.idl', 'nsIPaymentFlowInfo.idl', 'nsIPaymentProviderStrategy.idl', 'nsIPaymentUIGlue.idl', diff --git a/dom/payment/interfaces/nsINavigatorPayment.idl b/dom/payment/interfaces/nsIPaymentContentHelperService.idl similarity index 72% rename from dom/payment/interfaces/nsINavigatorPayment.idl rename to dom/payment/interfaces/nsIPaymentContentHelperService.idl index 091cec982c1..a02bc55a581 100644 --- a/dom/payment/interfaces/nsINavigatorPayment.idl +++ b/dom/payment/interfaces/nsIPaymentContentHelperService.idl @@ -5,13 +5,14 @@ #include "domstubs.idl" interface nsIDOMDOMRequest; +interface mozIDOMWindow; -[scriptable, uuid(44fb7308-7d7b-4975-8a27-e01fe9623bdb)] -interface nsINavigatorPayment : nsISupports +[scriptable, uuid(80035846-6732-4fcc-961b-f336b65218f4)] +interface nsIPaymentContentHelperService : nsISupports { // The 'jwts' parameter can be either a single DOMString or an array of // DOMStrings. In both cases, it represents the base64url encoded and // digitally signed payment information. Each payment provider should // define its supported JWT format. - nsIDOMDOMRequest pay(in jsval jwts); + nsIDOMDOMRequest pay(in mozIDOMWindow window, in jsval jwts); }; diff --git a/dom/payment/tests/mochitest/MockPaymentsUIChromeScript.js b/dom/payment/tests/mochitest/MockPaymentsUIChromeScript.js new file mode 100644 index 00000000000..7d991784d2a --- /dev/null +++ b/dom/payment/tests/mochitest/MockPaymentsUIChromeScript.js @@ -0,0 +1,91 @@ +/* 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/. */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cm = Components.manager; +const Cu = Components.utils; + +const CONTRACT_ID = "@mozilla.org/payment/ui-glue;1"; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); +var oldClassID, oldFactory; +var newClassID = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID(); +var newFactory = { + createInstance: function(aOuter, aIID) { + if (aOuter) { + throw Components.results.NS_ERROR_NO_AGGREGATION; + } + return new MockPaymentsUIGlueInstance().QueryInterface(aIID); + }, + lockFactory: function(aLock) { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]) +}; + +addMessageListener("MockPaymentsUIGlue.init", function (message) { + try { + oldClassID = registrar.contractIDToCID(CONTRACT_ID); + oldFactory = Cm.getClassObject(oldClassID, Ci.nsIFactory); + } catch (ex) { + oldClassID = ""; + oldFactory = null; + dump("TEST-INFO | can't get payments ui glue registered component, " + + "assuming there is none\n"); + } + if (oldFactory) { + registrar.unregisterFactory(oldClassID, oldFactory); + } + registrar.registerFactory(newClassID, "", CONTRACT_ID, newFactory);}); + +addMessageListener("MockPaymentsUIGlue.cleanup", function (message) { + if (oldClassID) { + registrar.registerFactory(oldClassID, "", CONTRACT_ID, null); + } +}); + +var payments = new Map(); + +function MockPaymentsUIGlueInstance() { +}; + +MockPaymentsUIGlueInstance.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentUIGlue]), + + confirmPaymentRequest: function(aRequestId, + aRequests, + aSuccessCb, + aErrorCb) { + aSuccessCb.onresult(aRequestId, aRequests[0].type); + }, + + showPaymentFlow: function(aRequestId, + aPaymentFlowInfo, + aErrorCb) { + let win = Services.ww.openWindow(null, + null, + "_blank", + "chrome,dialog=no,resizable,scrollbars,centerscreen", + null); + + payments.set(aRequestId, win); + let docshell = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + docshell.paymentRequestId = aRequestId; + + win.document.location = aPaymentFlowInfo.uri + aPaymentFlowInfo.jwt; + }, + + closePaymentFlow: function(aRequestId) { + payments.get(aRequestId).close(); + payments.delete(aRequestId); + + return Promise.resolve(); + }, +}; diff --git a/dom/payment/tests/mochitest/mochitest.ini b/dom/payment/tests/mochitest/mochitest.ini index 3d02e22a172..46c9ebd4003 100644 --- a/dom/payment/tests/mochitest/mochitest.ini +++ b/dom/payment/tests/mochitest/mochitest.ini @@ -1,12 +1,11 @@ [DEFAULT] -skip-if=true # This test uses MockPaymentsUIGlue which uses __exposedProps__ - # we need to move this to a chrome test, but these are not - # available in b2g or android for now. +skip-if=buildapp != 'b2g' && toolkit != 'android' support-files= file_mozpayproviderchecker.html file_payprovidersuccess.html file_payproviderfailure.html + MockPaymentsUIChromeScript.js [test_mozpaymentprovider.html] [test_mozpay_callbacks.html] diff --git a/dom/payment/tests/mochitest/test_mozpay_callbacks.html b/dom/payment/tests/mochitest/test_mozpay_callbacks.html index 7d6439b6143..e373f786085 100644 --- a/dom/payment/tests/mochitest/test_mozpay_callbacks.html +++ b/dom/payment/tests/mochitest/test_mozpay_callbacks.html @@ -79,7 +79,11 @@ function runTest() { tests.shift()(); } -SpecialPowers.MockPaymentsUIGlue.init(window); +const uiGlue = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('MockPaymentsUIChromeScript.js')); +SimpleTest.registerCleanupFunction(() => { + uiGlue.sendAsyncMessage("MockPaymentsUIGlue.cleanup", {}); +}); +uiGlue.sendAsyncMessage("MockPaymentsUIGlue.init", {}); SpecialPowers.pushPrefEnv({ "set": [ @@ -88,13 +92,13 @@ SpecialPowers.pushPrefEnv({ ["dom.payment.provider.1.name", "SuccessProvider"], ["dom.payment.provider.1.description", ""], ["dom.payment.provider.1.uri", - "http://mochi.test:8888/tests/dom/payment/tests/mochitest/file_payprovidersuccess.html?req="], + "https://example.com:443/tests/dom/payment/tests/mochitest/file_payprovidersuccess.html?req="], ["dom.payment.provider.1.type", "mozilla/payments/test/success"], ["dom.payment.provider.1.requestMethod", "GET"], ["dom.payment.provider.2.name", "FailureProvider"], ["dom.payment.provider.2.description", ""], ["dom.payment.provider.2.uri", - "http://mochi.test:8888/tests/dom/payment/tests/mochitest/file_payproviderfailure.html?req="], + "https://example.com:443/tests/dom/payment/tests/mochitest/file_payproviderfailure.html?req="], ["dom.payment.provider.2.type", "mozilla/payments/test/failure"], ["dom.payment.provider.2.requestMethod", "GET"], ] diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl index b24149a46c1..cb08be68149 100644 --- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -465,3 +465,14 @@ partial interface Navigator { readonly attribute boolean mozE10sEnabled; }; #endif + +#ifdef MOZ_PAY +partial interface Navigator { + [Throws, NewObject, Pref="dom.mozPay.enabled"] + // The 'jwts' parameter can be either a single DOMString or an array of + // DOMStrings. In both cases, it represents the base64url encoded and + // digitally signed payment information. Each payment provider should + // define its supported JWT format. + DOMRequest mozPay(any jwts); +}; +#endif diff --git a/testing/marionette/jar.mn b/testing/marionette/jar.mn index 3d3b34358c8..4cbb9585f14 100644 --- a/testing/marionette/jar.mn +++ b/testing/marionette/jar.mn @@ -42,5 +42,4 @@ marionette.jar: content/MockFilePicker.jsm (../specialpowers/content/MockFilePicker.jsm) content/MockColorPicker.jsm (../specialpowers/content/MockColorPicker.jsm) content/MockPermissionPrompt.jsm (../specialpowers/content/MockPermissionPrompt.jsm) - content/MockPaymentsUIGlue.jsm (../specialpowers/content/MockPaymentsUIGlue.jsm) content/Assert.jsm (../modules/Assert.jsm) diff --git a/testing/specialpowers/content/MockPaymentsUIGlue.jsm b/testing/specialpowers/content/MockPaymentsUIGlue.jsm deleted file mode 100644 index c9d7f69ed27..00000000000 --- a/testing/specialpowers/content/MockPaymentsUIGlue.jsm +++ /dev/null @@ -1,110 +0,0 @@ -/* 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/. */ - -this.EXPORTED_SYMBOLS = ["MockPaymentsUIGlue"]; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cm = Components.manager; -const Cu = Components.utils; - -const CONTRACT_ID = "@mozilla.org/payment/ui-glue;1"; - -Cu.import("resource://gre/modules/FileUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); -var classID; -var oldFactory; -var newFactory = function(window) { - return { - createInstance: function(aOuter, aIID) { - if (aOuter) { - throw Components.results.NS_ERROR_NO_AGGREGATION; - } - return new MockPaymentsUIGlueInstance(window).QueryInterface(aIID); - }, - lockFactory: function(aLock) { - throw Components.results.NS_ERROR_NOT_IMPLEMENTED; - }, - QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]) - }; -}; - -this.MockPaymentsUIGlue = { - init: function(aWindow) { - try { - classID = registrar.contractIDToCID(CONTRACT_ID); - oldFactory = Cm.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory); - } catch (ex) { - oldClassID = ""; - oldFactory = null; - dump("TEST-INFO | can't get payments ui glue registered component, " + - "assuming there is none"); - } - if (oldFactory) { - registrar.unregisterFactory(classID, oldFactory); - } - registrar.registerFactory(classID, "", CONTRACT_ID, - new newFactory(aWindow)); - }, - - reset: function() { - }, - - cleanup: function() { - this.reset(); - if (oldFactory) { - registrar.unregisterFactory(classID, newFactory); - registrar.registerFactory(classID, "", CONTRACT_ID, oldFactory); - } - } -}; - -function MockPaymentsUIGlueInstance(aWindow) { - this.window = aWindow; -}; - -MockPaymentsUIGlueInstance.prototype = { - QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentUIGlue]), - - confirmPaymentRequest: function(aRequestId, - aRequests, - aSuccessCb, - aErrorCb) { - aSuccessCb.onresult(aRequestId, aRequests[0].type); - }, - - showPaymentFlow: function(aRequestId, - aPaymentFlowInfo, - aErrorCb) { - let document = this.window.document; - let frame = document.createElement("iframe"); - frame.setAttribute("mozbrowser", true); - frame.setAttribute("remote", true); - document.body.appendChild(frame); - let docshell = frame.contentWindow - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShell); - docshell.paymentRequestId = aRequestId; - frame.src = aPaymentFlowInfo.uri + aPaymentFlowInfo.jwt; - }, - - closePaymentFlow: function(aRequestId) { - return Promise.resolve(); - } -}; -// Expose everything to content. We call reset() here so that all of the relevant -// lazy expandos get added. -MockPaymentsUIGlue.reset(); -function exposeAll(obj) { - var props = {}; - for (var prop in obj) - props[prop] = 'rw'; - obj.__exposedProps__ = props; -} -exposeAll(MockPaymentsUIGlue); -exposeAll(MockPaymentsUIGlueInstance.prototype); diff --git a/testing/specialpowers/content/specialpowersAPI.js b/testing/specialpowers/content/specialpowersAPI.js index d0cb19fb0a4..a8c86ac1637 100644 --- a/testing/specialpowers/content/specialpowersAPI.js +++ b/testing/specialpowers/content/specialpowersAPI.js @@ -14,7 +14,6 @@ var Cu = Components.utils; Cu.import("chrome://specialpowers/content/MockFilePicker.jsm"); Cu.import("chrome://specialpowers/content/MockColorPicker.jsm"); Cu.import("chrome://specialpowers/content/MockPermissionPrompt.jsm"); -Cu.import("chrome://specialpowers/content/MockPaymentsUIGlue.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -461,10 +460,6 @@ SpecialPowersAPI.prototype = { return MockPermissionPrompt; }, - get MockPaymentsUIGlue() { - return MockPaymentsUIGlue; - }, - loadChromeScript: function (url) { // Create a unique id for this chrome script let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"] diff --git a/testing/specialpowers/jar.mn b/testing/specialpowers/jar.mn index 90793bb33de..5e7ad8716b9 100644 --- a/testing/specialpowers/jar.mn +++ b/testing/specialpowers/jar.mn @@ -8,5 +8,4 @@ specialpowers.jar: content/MockFilePicker.jsm (content/MockFilePicker.jsm) content/MockColorPicker.jsm (content/MockColorPicker.jsm) content/MockPermissionPrompt.jsm (content/MockPermissionPrompt.jsm) - content/MockPaymentsUIGlue.jsm (content/MockPaymentsUIGlue.jsm) content/Assert.jsm (../modules/Assert.jsm)