Bug 1097928 - Convert MozPaymentProvider to WebIDL. r=bholley,fabrice

This commit is contained in:
Fernando Jimenez 2015-01-19 14:50:32 +01:00
parent 8be7f35fa5
commit 6af4bfbd53
30 changed files with 1099 additions and 563 deletions

View File

@ -1,484 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* 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 JS shim contains the callbacks to fire DOMRequest events for
// navigator.pay API within the payment processor's scope.
"use strict";
let { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
const PREF_DEBUG = "dom.payment.debug";
let _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("== Payment flow == " + s + "\n");
}
function LOGE(s) {
dump("== Payment flow ERROR == " + s + "\n");
}
if (_debug) {
LOG("Frame script injected");
}
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
#ifdef MOZ_B2G_RIL
XPCOMUtils.defineLazyServiceGetter(this, "gRil",
"@mozilla.org/ril;1",
"nsIRadioInterfaceLayer");
XPCOMUtils.defineLazyServiceGetter(this, "iccProvider",
"@mozilla.org/ril/content-helper;1",
"nsIIccProvider");
XPCOMUtils.defineLazyServiceGetter(this, "smsService",
"@mozilla.org/sms/smsservice;1",
"nsISmsService");
XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
"@mozilla.org/settingsService;1",
"nsISettingsService");
const kSilentSmsReceivedTopic = "silent-sms-received";
const kMozSettingsChangedObserverTopic = "mozsettings-changed";
const kRilDefaultDataServiceId = "ril.data.defaultServiceId";
const kRilDefaultPaymentServiceId = "ril.payment.defaultServiceId";
const MOBILEMESSAGECALLBACK_CID =
Components.ID("{b484d8c9-6be4-4f94-ab60-c9c7ebcc853d}");
// In order to send messages through nsISmsService, we need to implement
// nsIMobileMessageCallback, as the WebSMS API implementation is not usable
// from JS.
function SilentSmsRequest() {
}
SilentSmsRequest.prototype = {
__exposedProps__: {
onsuccess: "rw",
onerror: "rw"
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileMessageCallback]),
classID: MOBILEMESSAGECALLBACK_CID,
set onsuccess(aSuccessCallback) {
this._onsuccess = aSuccessCallback;
},
set onerror(aErrorCallback) {
this._onerror = aErrorCallback;
},
notifyMessageSent: function notifyMessageSent(aMessage) {
if (_debug) {
LOG("Silent message successfully sent");
}
this._onsuccess(aMessage);
},
notifySendMessageFailed: function notifySendMessageFailed(aError) {
LOGE("Error sending silent message " + aError);
this._onerror(aError);
}
};
function PaymentSettings() {
Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
[kRilDefaultDataServiceId, kRilDefaultPaymentServiceId].forEach(setting => {
gSettingsService.createLock().get(setting, this);
});
}
PaymentSettings.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsISettingsServiceCallback,
Ci.nsIObserver]),
dataServiceId: 0,
_paymentServiceId: 0,
get paymentServiceId() {
return this._paymentServiceId;
},
set paymentServiceId(serviceId) {
// We allow the payment provider to set the service ID that will be used
// for the payment process.
// This service ID will be the one used by the silent SMS flow.
// If the payment is done with an external SIM, the service ID must be set
// to null.
if (serviceId != null && serviceId >= gRil.numRadioInterfaces) {
LOGE("Invalid service ID " + serviceId);
return;
}
gSettingsService.createLock().set(kRilDefaultPaymentServiceId,
serviceId, null);
this._paymentServiceId = serviceId;
},
setServiceId: function(aName, aValue) {
switch (aName) {
case kRilDefaultDataServiceId:
this.dataServiceId = aValue;
if (_debug) {
LOG("dataServiceId " + this.dataServiceId);
}
break;
case kRilDefaultPaymentServiceId:
this._paymentServiceId = aValue;
if (_debug) {
LOG("paymentServiceId " + this._paymentServiceId);
}
break;
}
},
handle: function(aName, aValue) {
if (aName != kRilDefaultDataServiceId) {
return;
}
this.setServiceId(aName, aValue);
},
observe: function(aSubject, aTopic, aData) {
if (aTopic != kMozSettingsChangedObserverTopic) {
return;
}
try {
if ('wrappedJSObject' in aSubject) {
aSubject = aSubject.wrappedJSObject;
}
if (!aSubject.key ||
(aSubject.key !== kRilDefaultDataServiceId &&
aSubject.key !== kRilDefaultPaymentServiceId)) {
return;
}
this.setServiceId(aSubject.key, aSubject.value);
} catch (e) {
LOGE(e);
}
},
cleanup: function() {
Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
}
};
#endif
const kClosePaymentFlowEvent = "close-payment-flow-dialog";
let gRequestId;
let PaymentProvider = {
#ifdef MOZ_B2G_RIL
__exposedProps__: {
paymentSuccess: "r",
paymentFailed: "r",
paymentServiceId: "rw",
iccInfo: "r",
sendSilentSms: "r",
observeSilentSms: "r",
removeSilentSmsObserver: "r"
},
#else
__exposedProps__: {
paymentSuccess: "r",
paymentFailed: "r"
},
#endif
_init: function _init() {
#ifdef MOZ_B2G_RIL
this._settings = new PaymentSettings();
#endif
},
_closePaymentFlowDialog: function _closePaymentFlowDialog(aCallback) {
// After receiving the payment provider confirmation about the
// successful or failed payment flow, we notify the UI to close the
// payment flow dialog and return to the caller application.
let id = kClosePaymentFlowEvent + "-" + uuidgen.generateUUID().toString();
let detail = {
type: kClosePaymentFlowEvent,
id: id,
requestId: gRequestId
};
// In order to avoid race conditions, we wait for the UI to notify that
// it has successfully closed the payment flow and has recovered the
// caller app, before notifying the parent process to fire the success
// or error event over the DOMRequest.
SystemAppProxy.addEventListener("mozContentEvent",
function closePaymentFlowReturn(evt) {
if (evt.detail.id == id && aCallback) {
aCallback();
}
SystemAppProxy.removeEventListener("mozContentEvent",
closePaymentFlowReturn);
let glue = Cc["@mozilla.org/payment/ui-glue;1"]
.createInstance(Ci.nsIPaymentUIGlue);
glue.cleanup();
});
SystemAppProxy.dispatchEvent(detail);
#ifdef MOZ_B2G_RIL
this._cleanUp();
#endif
},
paymentSuccess: function paymentSuccess(aResult) {
if (_debug) {
LOG("paymentSuccess " + aResult);
}
PaymentProvider._closePaymentFlowDialog(function notifySuccess() {
if (!gRequestId) {
return;
}
cpmm.sendAsyncMessage("Payment:Success", { result: aResult,
requestId: gRequestId });
});
},
paymentFailed: function paymentFailed(aErrorMsg) {
LOGE("paymentFailed " + aErrorMsg);
PaymentProvider._closePaymentFlowDialog(function notifyError() {
if (!gRequestId) {
return;
}
cpmm.sendAsyncMessage("Payment:Failed", { errorMsg: aErrorMsg,
requestId: gRequestId });
});
},
#ifdef MOZ_B2G_RIL
get paymentServiceId() {
return this._settings.paymentServiceId;
},
set paymentServiceId(serviceId) {
this._settings.paymentServiceId = serviceId;
},
// We expose to the payment provider the information of all the SIMs
// available in the device. iccInfo is an object of this form:
// {
// "serviceId1": {
// mcc: <string>,
// mnc: <string>,
// iccId: <string>,
// dataPrimary: <boolean>
// },
// "serviceIdN": {...}
// }
get iccInfo() {
if (!this._iccInfo) {
this._iccInfo = {};
for (let i = 0; i < gRil.numRadioInterfaces; i++) {
let info = iccProvider.getIccInfo(i);
if (!info) {
LOGE("Tried to get the ICC info for an invalid service ID " + i);
continue;
}
this._iccInfo[i] = {
iccId: info.iccid,
mcc: info.mcc,
mnc: info.mnc,
dataPrimary: i == this._settings.dataServiceId
};
}
}
return Cu.cloneInto(this._iccInfo, content);
},
_silentNumbers: null,
_silentSmsObservers: null,
sendSilentSms: function sendSilentSms(aNumber, aMessage) {
if (_debug) {
LOG("Sending silent message " + aNumber + " - " + aMessage);
}
let request = new SilentSmsRequest();
if (this._settings.paymentServiceId === null) {
LOGE("No payment service ID set. Cannot send silent SMS");
let runnable = {
run: function run() {
request.notifySendMessageFailed("NO_PAYMENT_SERVICE_ID");
}
};
Services.tm.currentThread.dispatch(runnable,
Ci.nsIThread.DISPATCH_NORMAL);
return request;
}
smsService.send(this._settings.paymentServiceId, aNumber, aMessage, true,
request);
return request;
},
observeSilentSms: function observeSilentSms(aNumber, aCallback) {
if (_debug) {
LOG("observeSilentSms " + aNumber);
}
if (!this._silentSmsObservers) {
this._silentSmsObservers = {};
this._silentNumbers = [];
Services.obs.addObserver(this._onSilentSms.bind(this),
kSilentSmsReceivedTopic,
false);
}
if (!this._silentSmsObservers[aNumber]) {
this._silentSmsObservers[aNumber] = [];
this._silentNumbers.push(aNumber);
smsService.addSilentNumber(aNumber);
}
if (this._silentSmsObservers[aNumber].indexOf(aCallback) == -1) {
this._silentSmsObservers[aNumber].push(aCallback);
}
},
removeSilentSmsObserver: function removeSilentSmsObserver(aNumber, aCallback) {
if (_debug) {
LOG("removeSilentSmsObserver " + aNumber);
}
if (!this._silentSmsObservers || !this._silentSmsObservers[aNumber]) {
if (_debug) {
LOG("No observers for " + aNumber);
}
return;
}
let index = this._silentSmsObservers[aNumber].indexOf(aCallback);
if (index != -1) {
this._silentSmsObservers[aNumber].splice(index, 1);
if (this._silentSmsObservers[aNumber].length == 0) {
this._silentSmsObservers[aNumber] = null;
this._silentNumbers.splice(this._silentNumbers.indexOf(aNumber), 1);
smsService.removeSilentNumber(aNumber);
}
} else if (_debug) {
LOG("No callback found for " + aNumber);
}
},
_onSilentSms: function _onSilentSms(aSubject, aTopic, aData) {
if (_debug) {
LOG("Got silent message! " + aSubject.sender + " - " + aSubject.body);
}
let number = aSubject.sender;
if (!number || this._silentNumbers.indexOf(number) == -1) {
if (_debug) {
LOG("No observers for " + number);
}
return;
}
// If the service ID is null it means that the payment provider asked the
// user for her MSISDN, so we are in a MT only SMS auth flow. In this case
// we manually set the service ID to the one corresponding with the SIM
// that received the SMS.
if (this._settings.paymentServiceId === null) {
let i = 0;
while(i < gRil.numRadioInterfaces) {
if (this.iccInfo[i].iccId === aSubject.iccId) {
this._settings.paymentServiceId = i;
break;
}
i++;
}
}
this._silentSmsObservers[number].forEach(function(callback) {
callback(aSubject);
});
},
_cleanUp: function _cleanUp() {
if (_debug) {
LOG("Cleaning up!");
}
if (!this._silentNumbers) {
return;
}
while (this._silentNumbers.length) {
let number = this._silentNumbers.pop();
smsService.removeSilentNumber(number);
}
this._silentNumbers = null;
this._silentSmsObservers = null;
this._settings.cleanup();
Services.obs.removeObserver(this._onSilentSms, kSilentSmsReceivedTopic);
}
#endif
};
// We save the identifier of the DOM request, so we can dispatch the results
// of the payment flow to the appropriate content process.
addMessageListener("Payment:LoadShim", function receiveMessage(aMessage) {
gRequestId = aMessage.json.requestId;
PaymentProvider._init();
});
addEventListener("DOMWindowCreated", function(e) {
content.wrappedJSObject.mozPaymentProvider = PaymentProvider;
});
#ifdef MOZ_B2G_RIL
// If the trusted dialog is not closed via paymentSuccess or paymentFailed
// a mozContentEvent with type 'cancel' is sent from the UI. We need to listen
// for this event to clean up the silent sms observers if any exists.
SystemAppProxy.addEventListener("mozContentEvent", function(e) {
if (e.detail.type === "cancel") {
PaymentProvider._cleanUp();
}
});
#endif

View File

@ -32,7 +32,6 @@ chrome.jar:
* content/content.css (content/content.css)
content/touchcontrols.css (content/touchcontrols.css)
* content/payment.js (content/payment.js)
content/identity.js (content/identity.js)
% override chrome://global/skin/media/videocontrols.css chrome://b2g/content/touchcontrols.css

View File

@ -42,9 +42,11 @@ component {a6b2ab13-9037-423a-9897-dde1081be323} OMAContentHandler.js
contract @mozilla.org/uriloader/content-handler;1?type=application/vnd.oma.drm.message {a6b2ab13-9037-423a-9897-dde1081be323}
contract @mozilla.org/uriloader/content-handler;1?type=application/vnd.oma.dd+xml {a6b2ab13-9037-423a-9897-dde1081be323}
# PaymentGlue.js
# Payments
component {8b83eabc-7929-47f4-8b48-4dea8d887e4b} PaymentGlue.js
contract @mozilla.org/payment/ui-glue;1 {8b83eabc-7929-47f4-8b48-4dea8d887e4b}
component {4834b2e1-2c91-44ea-b020-e2581ed279a4} PaymentProviderStrategy.js
contract @mozilla.org/payment/provider-strategy;1 {4834b2e1-2c91-44ea-b020-e2581ed279a4}
# TelProtocolHandler.js
component {782775dd-7351-45ea-aff1-0ffa872cfdd2} TelProtocolHandler.js

View File

@ -8,14 +8,15 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
// JS shim that contains the callback functions to be triggered from the
// payment provider's code in order to fire DOMRequest events.
const kPaymentShimFile = "chrome://b2g/content/payment.js";
Cu.import("resource://gre/modules/Promise.jsm");
// Type of MozChromEvents to handle payment dialogs.
const kOpenPaymentConfirmationEvent = "open-payment-confirmation-dialog";
const kOpenPaymentFlowEvent = "open-payment-flow-dialog";
const kClosePaymentFlowEvent = "close-payment-flow-dialog";
// Observer notification topic for payment flow cancelation.
const kPaymentFlowCancelled = "payment-flow-cancelled";
const PREF_DEBUG = "dom.payment.debug";
@ -85,7 +86,7 @@ PaymentUI.prototype = {
showPaymentFlow: function showPaymentFlow(aRequestId,
aPaymentFlowInfo,
aErrorCb) {
let _error = function _error(errorMsg) {
let _error = (errorMsg) => {
if (aErrorCb) {
aErrorCb.onresult(aRequestId, errorMsg);
}
@ -96,60 +97,31 @@ PaymentUI.prototype = {
let detail = {
type: kOpenPaymentFlowEvent,
id: id,
requestId: aRequestId,
uri: aPaymentFlowInfo.uri,
method: aPaymentFlowInfo.requestMethod,
jwt: aPaymentFlowInfo.jwt
requestId: aRequestId
};
// At some point the UI would send the created iframe back so the
// callbacks for firing DOMRequest events can be loaded on its
// content.
this._loadPaymentShim = (function _loadPaymentShim(evt) {
let msg = evt.detail;
if (msg.id != id) {
this._setPaymentRequest = (event) => {
let message = event.detail;
if (message.id != id) {
return;
}
if (msg.errorMsg) {
SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
this._loadPaymentShim = null;
_error("ERROR_LOADING_PAYMENT_SHIM: " + msg.errorMsg);
return;
}
let frame = message.frame;
let docshell = frame.contentWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
docshell.paymentRequestId = aRequestId;
frame.src = aPaymentFlowInfo.uri + aPaymentFlowInfo.jwt;
SystemAppProxy.removeEventListener("mozContentEvent",
this._setPaymentRequest);
};
SystemAppProxy.addEventListener("mozContentEvent",
this._setPaymentRequest);
if (!msg.frame) {
SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
this._loadPaymentShim = null;
_error("ERROR_LOADING_PAYMENT_SHIM");
return;
}
// Try to load the payment shim file containing the payment callbacks
// in the content script.
let frame = msg.frame;
let frameLoader = frame.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader;
let mm = frameLoader.messageManager;
try {
mm.loadFrameScript(kPaymentShimFile, true, true);
mm.sendAsyncMessage("Payment:LoadShim", { requestId: aRequestId });
} catch (e) {
if (this._debug) {
this.LOG("Error loading " + kPaymentShimFile + " as a frame script: "
+ e);
}
_error("ERROR_LOADING_PAYMENT_SHIM");
} finally {
SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
this._loadPaymentShim = null;
}
}).bind(this);
SystemAppProxy.addEventListener("mozContentEvent", this._loadPaymentShim);
// We also listen for UI notifications about a closed payment flow. The UI
// We listen for UI notifications about a closed payment flow. The UI
// should provide the reason of the closure within the 'errorMsg' parameter
this._notifyPayFlowClosed = (function _notifyPayFlowClosed(evt) {
this._notifyPayFlowClosed = (evt) => {
let msg = evt.detail;
if (msg.id != id) {
return;
@ -162,44 +134,66 @@ PaymentUI.prototype = {
if (msg.errorMsg) {
_error(msg.errorMsg);
}
SystemAppProxy.removeEventListener("mozContentEvent",
this._notifyPayFlowClosed);
this._notifyPayFlowClosed = null;
}).bind(this);
Services.obs.notifyObservers(null, kPaymentFlowCancelled, null);
};
SystemAppProxy.addEventListener("mozContentEvent",
this._notifyPayFlowClosed);
this._notifyPayFlowClosed);
SystemAppProxy.dispatchEvent(detail);
},
closePaymentFlow: function(aRequestId) {
return new Promise((aResolve) => {
// After receiving the payment provider confirmation about the
// successful or failed payment flow, we notify the UI to close the
// payment flow dialog and return to the caller application.
let id = kClosePaymentFlowEvent + "-" + uuidgen.generateUUID().toString();
let detail = {
type: kClosePaymentFlowEvent,
id: id,
requestId: aRequestId
};
// In order to avoid race conditions, we wait for the UI to notify that
// it has successfully closed the payment flow and has recovered the
// caller app, before notifying the parent process to fire the success
// or error event over the DOMRequest.
SystemAppProxy.addEventListener("mozContentEvent",
(function closePaymentFlowReturn() {
SystemAppProxy.removeEventListener("mozContentEvent",
closePaymentFlowReturn);
this.cleanup();
aResolve();
}).bind(this));
SystemAppProxy.dispatchEvent(detail);
});
},
cleanup: function cleanup() {
if (this._handleSelection) {
SystemAppProxy.removeEventListener("mozContentEvent", this._handleSelection);
SystemAppProxy.removeEventListener("mozContentEvent",
this._handleSelection);
this._handleSelection = null;
}
if (this._notifyPayFlowClosed) {
SystemAppProxy.removeEventListener("mozContentEvent", this._notifyPayFlowClosed);
SystemAppProxy.removeEventListener("mozContentEvent",
this._notifyPayFlowClosed);
this._notifyPayFlowClosed = null;
}
if (this._loadPaymentShim) {
SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
this._loadPaymentShim = null;
}
},
getRandomId: function getRandomId() {
return uuidgen.generateUUID().toString();
},
LOG: function LOG(s) {
if (!this._debug) {
return;
}
dump("-*- PaymentGlue: " + s + "\n");
},
classID: Components.ID("{8b83eabc-7929-47f4-8b48-4dea8d887e4b}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentUIGlue])

View File

@ -0,0 +1,177 @@
/* 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/. */
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const PREF_DEBUG = "dom.payment.debug";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "iccProvider",
"@mozilla.org/ril/content-helper;1",
"nsIIccProvider");
XPCOMUtils.defineLazyServiceGetter(this, "gRil",
"@mozilla.org/ril;1",
"nsIRadioInterfaceLayer");
XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
"@mozilla.org/settingsService;1",
"nsISettingsService");
const kMozSettingsChangedObserverTopic = "mozsettings-changed";
const kRilDefaultDataServiceId = "ril.data.defaultServiceId";
const kRilDefaultPaymentServiceId = "ril.payment.defaultServiceId";
let _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("== Payment Provider == " + s + "\n");
}
function LOGE(s) {
dump("== Payment Provider ERROR == " + s + "\n");
}
function PaymentSettings() {
Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
[kRilDefaultDataServiceId, kRilDefaultPaymentServiceId].forEach(setting => {
gSettingsService.createLock().get(setting, this);
});
}
PaymentSettings.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsISettingsServiceCallback,
Ci.nsIObserver]),
dataServiceId: 0,
_paymentServiceId: 0,
get paymentServiceId() {
return this._paymentServiceId;
},
set paymentServiceId(serviceId) {
// We allow the payment provider to set the service ID that will be used
// for the payment process.
// This service ID will be the one used by the silent SMS flow.
// If the payment is done with an external SIM, the service ID must be set
// to null.
if (serviceId != null && serviceId >= gRil.numRadioInterfaces) {
LOGE("Invalid service ID " + serviceId);
return;
}
gSettingsService.createLock().set(kRilDefaultPaymentServiceId,
serviceId, 0);
this._paymentServiceId = serviceId;
},
setServiceId: function(aName, aValue) {
switch (aName) {
case kRilDefaultDataServiceId:
this.dataServiceId = aValue;
if (_debug) {
LOG("dataServiceId " + this.dataServiceId);
}
break;
case kRilDefaultPaymentServiceId:
this._paymentServiceId = aValue;
if (_debug) {
LOG("paymentServiceId " + this._paymentServiceId);
}
break;
}
},
handle: function(aName, aValue) {
if (aName != kRilDefaultDataServiceId) {
return;
}
this.setServiceId(aName, aValue);
},
observe: function(aSubject, aTopic, aData) {
if (aTopic != kMozSettingsChangedObserverTopic) {
return;
}
try {
if ("wrappedJSObject" in aSubject) {
aSubject = aSubject.wrappedJSObject;
}
if (!aSubject.key ||
(aSubject.key !== kRilDefaultDataServiceId &&
aSubject.key !== kRilDefaultPaymentServiceId)) {
return;
}
this.setServiceId(aSubject.key, aSubject.value);
} catch (e) {
LOGE(e);
}
},
cleanup: function() {
Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
}
};
function PaymentProviderStrategy() {
this._settings = new PaymentSettings();
}
PaymentProviderStrategy.prototype = {
get paymentServiceId() {
return this._settings.paymentServiceId;
},
set paymentServiceId(aServiceId) {
this._settings.paymentServiceId = aServiceId;
},
get iccInfo() {
if (!this._iccInfo) {
this._iccInfo = [];
for (let i = 0; i < gRil.numRadioInterfaces; i++) {
let info = iccProvider.getIccInfo(i);
if (!info) {
LOGE("Tried to get the ICC info for an invalid service ID " + i);
continue;
}
this._iccInfo.push({
iccId: info.iccid,
mcc: info.mcc,
mnc: info.mnc,
dataPrimary: i == this._settings.dataServiceId
});
}
}
return this._iccInfo;
},
cleanup: function() {
this._settings.cleanup();
},
classID: Components.ID("{4834b2e1-2c91-44ea-b020-e2581ed279a4}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentProviderStrategy])
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentProviderStrategy]);

View File

@ -21,6 +21,7 @@ EXTRA_COMPONENTS += [
'MobileIdentityUIGlue.js',
'OMAContentHandler.js',
'PaymentGlue.js',
'PaymentProviderStrategy.js',
'ProcessGlobal.js',
'SmsProtocolHandler.js',
'SystemMessageGlue.js',

View File

@ -604,6 +604,7 @@
@BINPATH@/components/Payment.js
@BINPATH@/components/PaymentFlowInfo.js
@BINPATH@/components/PaymentProvider.js
@BINPATH@/components/Payment.manifest
@BINPATH@/components/DownloadsAPI.js
@ -854,6 +855,7 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DLL_SUFFIX@
@BINPATH@/components/ProcessGlobal.js
@BINPATH@/components/OMAContentHandler.js
@BINPATH@/components/PaymentGlue.js
@BINPATH@/components/PaymentProviderStrategy.js
@BINPATH@/components/RecoveryService.js
@BINPATH@/components/MailtoProtocolHandler.js
@BINPATH@/components/SmsProtocolHandler.js

View File

@ -13767,3 +13767,35 @@ nsDocShell::MaybeNotifyKeywordSearchLoading(const nsString &aProvider,
}
#endif
}
NS_IMETHODIMP
nsDocShell::SetPaymentRequestId(const nsAString& aPaymentRequestId)
{
mPaymentRequestId = aPaymentRequestId;
return NS_OK;
}
nsString
nsDocShell::GetInheritedPaymentRequestId()
{
if (!mPaymentRequestId.IsEmpty()) {
return mPaymentRequestId;
}
nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
GetSameTypeParent(getter_AddRefs(parentAsItem));
nsCOMPtr<nsIDocShell> parent = do_QueryInterface(parentAsItem);
if (!parent) {
return mPaymentRequestId;
}
return static_cast<nsDocShell*>(
parent.get())->GetInheritedPaymentRequestId();
}
NS_IMETHODIMP
nsDocShell::GetPaymentRequestId(nsAString& aPaymentRequestId)
{
aPaymentRequestId = GetInheritedPaymentRequestId();
return NS_OK;
}

View File

@ -947,6 +947,9 @@ protected:
// find it by walking up the docshell hierarchy.)
uint32_t mOwnOrContainingAppId;
nsString mPaymentRequestId;
nsString GetInheritedPaymentRequestId();
private:
nsCString mForcedCharset;
nsCString mParentCharset;

View File

@ -54,7 +54,7 @@ interface nsITabParent;
typedef unsigned long nsLoadFlags;
[scriptable, builtinclass, uuid(fef3bae1-6673-4c49-9f5a-fcc075926730)]
[scriptable, builtinclass, uuid(e0e833fe-3a5b-48b0-8684-a097e09c0723)]
interface nsIDocShell : nsIDocShellTreeItem
{
/**
@ -773,7 +773,7 @@ interface nsIDocShell : nsIDocShellTreeItem
/**
* Returns true if this docshell corresponds to an <iframe mozbrowser> or
* <iframe mozap>, or if this docshell is contained in an <iframe mozbrowser>
* <iframe mozapp>, or if this docshell is contained in an <iframe mozbrowser>
* or <iframe mozapp>.
*
* To compute this value, we walk up the docshell hierarchy. If we encounter
@ -840,7 +840,7 @@ interface nsIDocShell : nsIDocShellTreeItem
*/
nsIDocShell getSameTypeParentIgnoreBrowserAndAppBoundaries();
/**
/**
* True iff asynchronous panning and zooming is enabled for this
* docshell.
*/
@ -1049,4 +1049,8 @@ interface nsIDocShell : nsIDocShellTreeItem
*/
[infallible] readonly attribute boolean hasLoadedNonBlankURI;
/**
* Holds the id of the payment request associated with this docshell if any.
*/
attribute DOMString paymentRequestId;
};

View File

@ -4,3 +4,6 @@ category JavaScript-navigator-property mozPay @mozilla.org/payment/content-helpe
component {b8bce4e7-fbf0-4719-a634-b1bf9018657c} PaymentFlowInfo.js
contract @mozilla.org/payment/flow-info;1 {b8bce4e7-fbf0-4719-a634-b1bf9018657c}
component {82144756-72ab-45b7-8621-f3dad431dd2f} PaymentProvider.js
contract @mozilla.org/payment/provider;1 {82144756-72ab-45b7-8621-f3dad431dd2f}

View File

@ -0,0 +1,293 @@
/* 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/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
const PREF_DEBUG = "dom.payment.debug";
let _debug;
try {
_debug = Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
&& Services.prefs.getBoolPref(PREF_DEBUG);
} catch(e) {
_debug = false;
}
function DEBUG(s) {
if (!_debug) {
return;
}
dump("== Payment Provider == " + s + "\n");
}
function DEBUG_E(s) {
dump("== Payment Provider ERROR == " + s + "\n");
}
const kPaymentFlowCancelled = "payment-flow-cancelled";
function PaymentProvider() {
}
PaymentProvider.prototype = {
init: function(aWindow) {
let docshell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
this._requestId = docshell.paymentRequestId;
this._oncancelObserver = this.oncancel.bind(this);
Services.obs.addObserver(this._oncancelObserver,
kPaymentFlowCancelled,
false);
this._strategy = Cc["@mozilla.org/payment/provider-strategy;1"]
.createInstance(Ci.nsIPaymentProviderStrategy);
this._window = aWindow;
},
paymentSuccess: function(aResult) {
_debug && DEBUG("paymentSuccess " + aResult);
let glue = Cc["@mozilla.org/payment/ui-glue;1"]
.createInstance(Ci.nsIPaymentUIGlue);
glue.closePaymentFlow(this._requestId).then(() => {
if (!this._requestId) {
return;
}
cpmm.sendAsyncMessage("Payment:Success", { result: aResult,
requestId: this._requestId });
});
},
paymentFailed: function(aError) {
_debug && DEBUG("paymentFailed " + aError);
let glue = Cc["@mozilla.org/payment/ui-glue;1"]
.createInstance(Ci.nsIPaymentUIGlue);
glue.closePaymentFlow(this._requestId).then(() => {
if (!this._requestId) {
return;
}
cpmm.sendAsyncMessage("Payment:Failed", { errorMsg: aError,
requestId: this._requestId });
});
},
get paymentServiceId() {
return this._strategy.paymentServiceId;
},
set paymentServiceId(aServiceId) {
this._strategy.paymentServiceId = aServiceId;
},
/**
* We expose to the payment provider the information of all the SIMs
* available in the device. iccInfo is an object of this form:
* {
* "serviceId1": {
* mcc: <string>,
* mnc: <string>,
* iccId: <string>,
* dataPrimary: <boolean>
* },
* "serviceIdN": {...}
* }
*/
get iccInfo() {
return this._strategy.iccInfo;
},
oncancel: function() {
_debug && DEBUG("Cleaning up!");
this._strategy.cleanup();
Services.obs.removeObserver(this._oncancelObserver, kPaymentFlowCancelled);
if (this._cleanup) {
this._cleanup();
}
},
classID: Components.ID("{82144756-72ab-45b7-8621-f3dad431dd2f}"),
contractID: "@mozilla.org/payment/provider;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
Ci.nsIObserver,
Ci.nsIDOMGlobalPropertyInitializer])
};
#if defined(MOZ_B2G_RIL) || defined(MOZ_WIDGET_ANDROID)
XPCOMUtils.defineLazyServiceGetter(this, "smsService",
"@mozilla.org/sms/smsservice;1",
"nsISmsService");
const kSilentSmsReceivedTopic = "silent-sms-received";
// In order to send messages through nsISmsService, we need to implement
// nsIMobileMessageCallback, as the WebSMS API implementation is not usable
// from JS.
function SilentSmsRequest(aDOMRequest) {
this.request = aDOMRequest;
}
SilentSmsRequest.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileMessageCallback]),
classID: Components.ID("{1d58889c-5660-4cca-a8fd-97ef63e5d3b2}"),
notifyMessageSent: function notifyMessageSent(aMessage) {
_debug && DEBUG("Silent message successfully sent");
Services.DOMRequest.fireSuccessAsync(this.request, aMessage);
},
notifySendMessageFailed: function notifySendMessageFailed(aError) {
DEBUG_E("Error sending silent message " + aError);
Services.DOMRequest.fireErrorAsync(this.request, aError);
}
};
PaymentProvider.prototype._silentNumbers = null;
PaymentProvider.prototype._silentSmsObservers = null;
PaymentProvider.prototype.sendSilentSms = function(aNumber, aMessage) {
_debug && DEBUG("Sending silent message " + aNumber + " - " + aMessage);
let request = Services.DOMRequest.createRequest(this._window);
if (this._strategy.paymentServiceId === null) {
DEBUG_E("No payment service ID set. Cannot send silent SMS");
Services.DOMRequest.fireErrorAsync(request,
"NO_PAYMENT_SERVICE_ID");
return request;
}
let smsRequest = new SilentSmsRequest(request);
smsService.send(this._strategy.paymentServiceId, aNumber, aMessage, true,
smsRequest);
return request;
};
PaymentProvider.prototype.observeSilentSms = function(aNumber, aCallback) {
_debug && DEBUG("observeSilentSms " + aNumber);
if (!this._silentSmsObservers) {
this._silentSmsObservers = {};
this._silentNumbers = [];
this._onSilentSmsObserver = this._onSilentSms.bind(this);
Services.obs.addObserver(this._onSilentSmsObserver,
kSilentSmsReceivedTopic,
false);
}
if (!this._silentSmsObservers[aNumber]) {
this._silentSmsObservers[aNumber] = [];
this._silentNumbers.push(aNumber);
smsService.addSilentNumber(aNumber);
}
if (this._silentSmsObservers[aNumber].indexOf(aCallback) == -1) {
this._silentSmsObservers[aNumber].push(aCallback);
}
return;
};
PaymentProvider.prototype.removeSilentSmsObserver = function(aNumber, aCallback) {
_debug && DEBUG("removeSilentSmsObserver " + aNumber);
if (!this._silentSmsObservers || !this._silentSmsObservers[aNumber]) {
_debug && DEBUG("No observers for " + aNumber);
return;
}
let index = this._silentSmsObservers[aNumber].indexOf(aCallback);
if (index != -1) {
this._silentSmsObservers[aNumber].splice(index, 1);
if (this._silentSmsObservers[aNumber].length == 0) {
this._silentSmsObservers[aNumber] = null;
this._silentNumbers.splice(this._silentNumbers.indexOf(aNumber), 1);
smsService.removeSilentNumber(aNumber);
}
} else if (_debug) {
DEBUG("No callback found for " + aNumber);
}
return;
};
PaymentProvider.prototype._onSilentSms = function(aSubject, aTopic, aData) {
_debug && DEBUG("Got silent message! " + aSubject.sender + " - " + aSubject.body);
let number = aSubject.sender;
if (!number || this._silentNumbers.indexOf(number) == -1) {
_debug && DEBUG("No observers for " + number);
return;
}
// If the service ID is null it means that the payment provider asked the
// user for her MSISDN, so we are in a MT only SMS auth flow. In this case
// we manually set the service ID to the one corresponding with the SIM
// that received the SMS.
if (this._strategy.paymentServiceId === null) {
let i = 0;
while(i < gRil.numRadioInterfaces) {
if (this.iccInfo[i].iccId === aSubject.iccId) {
this._strategy.paymentServiceId = i;
break;
}
i++;
}
}
this._silentSmsObservers[number].forEach(function(callback) {
callback(aSubject);
});
};
PaymentProvider.prototype._cleanup = function() {
if (!this._silentNumbers) {
return;
}
while (this._silentNumbers.length) {
let number = this._silentNumbers.pop();
smsService.removeSilentNumber(number);
}
this._silentNumbers = null;
this._silentSmsObservers = null;
Services.obs.removeObserver(this._onSilentSmsObserver,
kSilentSmsReceivedTopic);
};
#else
PaymentProvider.prototype.sendSilentSms = function(aNumber, aMessage) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
};
PaymentProvider.prototype.observeSilentSms = function(aNumber, aCallback) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
};
PaymentProvider.prototype.removeSilentSmsObserver = function(aNumber, aCallback) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
};
#endif
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentProvider]);

View File

@ -0,0 +1,28 @@
/* 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/. */
#include "mozilla/dom/NavigatorBinding.h"
#include "PaymentProviderUtils.h"
#include "nsGlobalWindow.h"
#include "nsJSUtils.h"
#include "nsIDocShell.h"
using namespace mozilla::dom;
/* static */ bool
PaymentProviderUtils::EnabledForScope(JSContext* aCx,
JSObject* aGlobal)
{
nsCOMPtr<nsPIDOMWindow> win =
do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(aGlobal));
NS_ENSURE_TRUE(win, false);
nsIDocShell *docShell = win->GetDocShell();
NS_ENSURE_TRUE(docShell, false);
nsString paymentRequestId;
docShell->GetPaymentRequestId(paymentRequestId);
return !paymentRequestId.IsEmpty();
}

View File

@ -0,0 +1,25 @@
/* 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/. */
#ifndef mozilla_dom_payment_PaymentProviderEnabler_h
#define mozilla_dom_payment_PaymentProviderEnabler_h
#include "jsapi.h"
struct JSContext;
class JSObject;
namespace mozilla {
namespace dom {
class PaymentProviderUtils
{
public:
static bool EnabledForScope(JSContext*, JSObject*);
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_payment_PaymentProviderEnabler_h

View File

@ -7,6 +7,7 @@
XPIDL_SOURCES += [
'nsINavigatorPayment.idl',
'nsIPaymentFlowInfo.idl',
'nsIPaymentProviderStrategy.idl',
'nsIPaymentUIGlue.idl',
]

View File

@ -0,0 +1,14 @@
/* 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/. */
#include "nsISupports.idl"
[scriptable, uuid(c3971bd9-0fbf-48d3-9498-0ac340d0d216)]
interface nsIPaymentProviderStrategy : nsISupports
{
attribute DOMString paymentServiceId;
readonly attribute jsval iccInfo;
void cleanup();
};

View File

@ -12,7 +12,7 @@ interface nsIPaymentUIGlueCallback : nsISupports
void onresult(in DOMString requestId, in DOMString result);
};
[scriptable, uuid(4dda9aa0-df88-4dcd-a583-199e516fa438)]
[scriptable, uuid(4dc09e33-d395-4e1d-acb4-e85415181270)]
interface nsIPaymentUIGlue : nsISupports
{
// The 'paymentRequestsInfo' contains the payment request information
@ -26,5 +26,7 @@ interface nsIPaymentUIGlue : nsISupports
in nsIPaymentFlowInfo paymentFlowInfo,
in nsIPaymentUIGlueCallback errorCb);
void cleanup();
// The promise resolves with no value as soon as the payment window is
// closed.
jsval /*Promise*/ closePaymentFlow(in DOMString requestId);
};

View File

@ -6,6 +6,14 @@
DIRS += ['interfaces']
EXPORTS.mozilla.dom += [
'PaymentProviderUtils.h',
]
SOURCES += [
'PaymentProviderUtils.cpp',
]
EXTRA_PP_JS_MODULES += [
'Payment.jsm',
]
@ -13,7 +21,19 @@ EXTRA_PP_JS_MODULES += [
EXTRA_COMPONENTS += [
'Payment.js',
'Payment.manifest',
'PaymentFlowInfo.js',
'PaymentFlowInfo.js'
]
EXTRA_PP_COMPONENTS += [
'PaymentProvider.js'
]
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/dom/base'
]
XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini']

View File

@ -0,0 +1,28 @@
<!--
* 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>
<head>
<meta charset="utf-8">
<title>Test app for bug 1097928 - Check if MozPaymentProvider API is exposed</title>
</head>
<body>
<div id='test'>
<script type="application/javascript;version=1.8">
function receiveMessage(event) {
let message = JSON.parse(event.data);
let exposed = (navigator.mozPaymentProvider != undefined);
window.parent.postMessage(JSON.stringify({
iframeType: message.iframeType,
exposed: exposed
}), "*");
}
window.addEventListener("message", receiveMessage, false, true);
</script>
</div>
</body>
</html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
navigator.mozPaymentProvider.paymentFailed();
</script>
</body>
</html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
navigator.mozPaymentProvider.paymentSuccess("aResult");
</script>
</body>
</html>

View File

@ -0,0 +1,12 @@
[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.
support-files=
file_mozpayproviderchecker.html
file_payprovidersuccess.html
file_payproviderfailure.html
[test_mozpaymentprovider.html]
[test_mozpay_callbacks.html]

View File

@ -0,0 +1,108 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1097928
-->
<html>
<head>
<title>Test for navigator.mozPay. Bug 1097928</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript;version=1.8">
SimpleTest.waitForExplicitFinish();
let tests = [() => {
/*
{
"iss": "323d34dc-b5cf-4822-8e47-6a4515dc74db",
"typ": "mozilla/payments/test/success",
"request": {
"name": "Virtual Kiwi",
"id": "af1f960a-3f90-4e2d-a20f-d5170aee49f2",
"postbackURL": "https://inapp-pay-test.paas.allizom.org/mozpay",
"productData": "localTransID=14546cd1-db9b-4759-986f-2a6a295fdcc1",
"chargebackURL": "https://inapp-pay-test.paas.allizom.org/mozpay",
"description": "The forbidden fruit"
}
}
*/
let jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIzMjNkMzRkYy1iNWNmLTQ4MjItOGU0Ny02YTQ1MTVkYzc0ZGIiLCJyZXF1ZXN0Ijp7ImRlc2NyaXB0aW9uIjoiVGhlIGZvcmJpZGRlbiBmcnVpdCIsImlkIjoiYWYxZjk2MGEtM2Y5MC00ZTJkLWEyMGYtZDUxNzBhZWU0OWYyIiwicG9zdGJhY2tVUkwiOiJodHRwczovL2luYXBwLXBheS10ZXN0LnBhYXMuYWxsaXpvbS5vcmcvbW96cGF5L3Bvc3RiYWNrIiwicHJvZHVjdERhdGEiOiJsb2NhbFRyYW5zSUQ9MTQ1NDZjZDEtZGI5Yi00NzU5LTk4NmYtMmE2YTI5NWZkY2MxIiwiY2hhcmdlYmFja1VSTCI6Imh0dHBzOi8vaW5hcHAtcGF5LXRlc3QucGFhcy5hbGxpem9tLm9yZy9tb3pwYXkvY2hhcmdlYmFjayIsIm5hbWUiOiJWaXJ0dWFsIEtpd2kifSwidHlwIjoibW96aWxsYS9wYXltZW50cy90ZXN0L3N1Y2Nlc3MifQ.8zaeYFUCwKkZWk2TFf2wEJWrmiSYQGNbpKc2ADkvL9s";
let req = window.navigator.mozPay(jwt);
req.onsuccess = (result) => {
ok(true, "Expected mozPay success");
runTest();
};
req.onerror = (error) => {
ok(false, "Unexpected mozPay error " + error);
SimpleTest.finish();
};
}, () => {
/*
{
"iss": "323d34dc-b5cf-4822-8e47-6a4515dc74db",
"typ": "mozilla/payments/test/failure",
"request": {
"name": "Virtual Kiwi",
"id": "af1f960a-3f90-4e2d-a20f-d5170aee49f2",
"postbackURL": "https://inapp-pay-test.paas.allizom.org/mozpay",
"productData": "localTransID=cc3c0994-33e8-4a21-aa2c-75ee44f5fe75",
"chargebackURL": "https://inapp-pay-test.paas.allizom.org/mozpay",
"description": "The forbidden fruit"
}
}
*/
let jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIzMjNkMzRkYy1iNWNmLTQ4MjItOGU0Ny02YTQ1MTVkYzc0ZGIiLCJyZXF1ZXN0Ijp7ImRlc2NyaXB0aW9uIjoiVGhlIGZvcmJpZGRlbiBmcnVpdCIsImlkIjoiYWYxZjk2MGEtM2Y5MC00ZTJkLWEyMGYtZDUxNzBhZWU0OWYyIiwicG9zdGJhY2tVUkwiOiJodHRwczovL2luYXBwLXBheS10ZXN0LnBhYXMuYWxsaXpvbS5vcmcvbW96cGF5L3Bvc3RiYWNrIiwicHJvZHVjdERhdGEiOiJsb2NhbFRyYW5zSUQ9Y2MzYzA5OTQtMzNlOC00YTIxLWFhMmMtNzVlZTQ0ZjVmZTc1IiwiY2hhcmdlYmFja1VSTCI6Imh0dHBzOi8vaW5hcHAtcGF5LXRlc3QucGFhcy5hbGxpem9tLm9yZy9tb3pwYXkvY2hhcmdlYmFjayIsIm5hbWUiOiJWaXJ0dWFsIEtpd2kifSwidHlwIjoibW96aWxsYS9wYXltZW50cy90ZXN0L2ZhaWx1cmUifQ.1uV4-HkmwO0oDv50wi1Ma4tNpnxoFGaw5zaPj8xkcAc";
let req = window.navigator.mozPay(jwt);
req.onsuccess = (result) => {
ok(false, "Unexpected mozPay success " + result);
SimpleTest.finish();
};
req.onerror = (error) => {
ok(true, "Expected mozPay error");
runTest();
};
}];
function runTest() {
if (!tests.length) {
ok(true, "Done!");
SimpleTest.finish();
return;
}
tests.shift()();
}
SpecialPowers.MockPaymentsUIGlue.init(window);
SpecialPowers.pushPrefEnv({
"set": [
["dom.payment.skipHTTPSCheck", "true"],
["dom.payment.debug", "true"],
["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="],
["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="],
["dom.payment.provider.2.type", "mozilla/payments/test/failure"],
["dom.payment.provider.2.requestMethod", "GET"],
]
}, () => {
runTest();
});
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,93 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1097928
-->
<html>
<head>
<title>Test for navigator.mozPaymentProvider exposure. Bug 1097928</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript;version=1.8">
SimpleTest.waitForExplicitFinish();
// We create two iframes. The first one is a regular iframe with no payment
// information and so it should not have access to the MozPaymentProvider
// API. For the second iframe we set a dummy payment request ID which should
// expose the MozPaymentProvider API.
let tests = [function() {
// Iframe with no payment information.
let iframe = document.createElement("iframe");
iframe.setAttribute("mozbrowser", "true");
iframe.src = "file_mozpayproviderchecker.html";
document.getElementById("content").appendChild(iframe);
iframe.addEventListener("load", function onLoad() {
iframe.removeEventListener("load", onLoad);
iframe.contentWindow.postMessage(JSON.stringify({
iframeType: "regular"
}), "*");
}, false);
}, function() {
// Payment iframe.
let paymentIframe = document.createElement("iframe");
paymentIframe.setAttribute("mozbrowser", "true");
paymentIframe.src = "file_mozpayproviderchecker.html";
document.getElementById("content").appendChild(paymentIframe);
let Ci = SpecialPowers.Ci;
let docshell = SpecialPowers.wrap(paymentIframe.contentWindow)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
docshell.paymentRequestId = "dummyid";
paymentIframe.addEventListener("load", function onLoad() {
paymentIframe.removeEventListener("load", onLoad);
paymentIframe.contentWindow.postMessage(JSON.stringify({
iframeType: "payment"
}), "*");
}, false);
}];
function receiveMessage(event) {
let message = JSON.parse(event.data);
switch (message.iframeType) {
case "regular":
ok(!message.exposed, "MozPaymentProvider is not exposed in regular iframe");
break;
case "payment":
ok(message.exposed, "MozPaymentProvider is exposed in payment iframe");
break;
default:
ok(false, "Unexpected iframe type");
SimpleTest.finish();
}
runTest();
}
function runTest() {
if (!tests.length) {
ok(true, "Done!");
SimpleTest.finish();
return;
}
tests.shift()();
}
window.addEventListener("message", receiveMessage, false, true);
runTest();
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,33 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/.
*/
callback SilentSmsCallback = void (optional MozSmsMessage message);
dictionary PaymentIccInfo {
DOMString mcc;
DOMString mnc;
DOMString iccId;
boolean dataPrimary;
};
[NavigatorProperty="mozPaymentProvider",
NoInterfaceObject,
HeaderFile="mozilla/dom/PaymentProviderUtils.h",
Func="mozilla::dom::PaymentProviderUtils::EnabledForScope",
JSImplementation="@mozilla.org/payment/provider;1"]
interface PaymentProvider {
readonly attribute DOMString? paymentServiceId;
// We expose to the payment provider the information of all the SIMs
// available in the device.
[Cached, Pure] readonly attribute sequence<PaymentIccInfo>? iccInfo;
void paymentSuccess(optional DOMString result);
void paymentFailed(optional DOMString error);
DOMRequest sendSilentSms(DOMString number, DOMString message);
void observeSilentSms(DOMString number, SilentSmsCallback callback);
void removeSilentSmsObserver(DOMString number, SilentSmsCallback callback);
};

View File

@ -809,3 +809,8 @@ if CONFIG['MOZ_EME']:
'MediaKeySession.webidl',
'MediaKeySystemAccess.webidl',
]
if CONFIG['MOZ_PAY']:
WEBIDL_FILES += [
'MozPaymentProvider.webidl'
]

View File

@ -32,4 +32,5 @@ marionette.jar:
modules/MockFilePicker.jsm (../specialpowers/content/MockFilePicker.jsm)
modules/MockColorPicker.jsm (../specialpowers/content/MockColorPicker.jsm)
modules/MockPermissionPrompt.jsm (../specialpowers/content/MockPermissionPrompt.jsm)
modules/MockPaymentsUIGlue.jsm (../specialpowers/content/MockPaymentsUIGlue.jsm)
modules/Assert.jsm (../modules/Assert.jsm)

View File

@ -0,0 +1,110 @@
/* 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");
let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
let classID;
let oldFactory;
let 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);

View File

@ -14,6 +14,7 @@ var Cu = Components.utils;
Cu.import("resource://specialpowers/MockFilePicker.jsm");
Cu.import("resource://specialpowers/MockColorPicker.jsm");
Cu.import("resource://specialpowers/MockPermissionPrompt.jsm");
Cu.import("resource://specialpowers/MockPaymentsUIGlue.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -521,15 +522,19 @@ SpecialPowersAPI.prototype = {
},
get MockFilePicker() {
return MockFilePicker
return MockFilePicker;
},
get MockColorPicker() {
return MockColorPicker
return MockColorPicker;
},
get MockPermissionPrompt() {
return MockPermissionPrompt
return MockPermissionPrompt;
},
get MockPaymentsUIGlue() {
return MockPaymentsUIGlue;
},
loadChromeScript: function (url) {

View File

@ -9,6 +9,7 @@ specialpowers.jar:
modules/MockFilePicker.jsm (content/MockFilePicker.jsm)
modules/MockColorPicker.jsm (content/MockColorPicker.jsm)
modules/MockPermissionPrompt.jsm (content/MockPermissionPrompt.jsm)
modules/MockPaymentsUIGlue.jsm (content/MockPaymentsUIGlue.jsm)
modules/Assert.jsm (../modules/Assert.jsm)
% component {59a52458-13e0-4d93-9d85-a637344f29a1} components/SpecialPowersObserver.js