Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
2 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 // This JS shim contains the callbacks to fire DOMRequest events for
8 // navigator.pay API within the payment processor's scope.
10 "use strict";
12 let { classes: Cc, interfaces: Ci, utils: Cu } = Components;
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
14 Cu.import("resource://gre/modules/Services.jsm");
16 const PREF_DEBUG = "dom.payment.debug";
18 let _debug;
19 try {
20 _debug = Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
21 && Services.prefs.getBoolPref(PREF_DEBUG);
22 } catch(e){
23 _debug = false;
24 }
26 function LOG(s) {
27 if (!_debug) {
28 return;
29 }
30 dump("== Payment flow == " + s + "\n");
31 }
33 function LOGE(s) {
34 dump("== Payment flow ERROR == " + s + "\n");
35 }
37 if (_debug) {
38 LOG("Frame script injected");
39 }
41 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
42 "@mozilla.org/childprocessmessagemanager;1",
43 "nsIMessageSender");
45 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
46 "@mozilla.org/uuid-generator;1",
47 "nsIUUIDGenerator");
49 XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
50 "resource://gre/modules/SystemAppProxy.jsm");
52 #ifdef MOZ_B2G_RIL
53 XPCOMUtils.defineLazyServiceGetter(this, "gRil",
54 "@mozilla.org/ril;1",
55 "nsIRadioInterfaceLayer");
57 XPCOMUtils.defineLazyServiceGetter(this, "iccProvider",
58 "@mozilla.org/ril/content-helper;1",
59 "nsIIccProvider");
61 XPCOMUtils.defineLazyServiceGetter(this, "smsService",
62 "@mozilla.org/sms/smsservice;1",
63 "nsISmsService");
65 XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
66 "@mozilla.org/settingsService;1",
67 "nsISettingsService");
69 const kSilentSmsReceivedTopic = "silent-sms-received";
70 const kMozSettingsChangedObserverTopic = "mozsettings-changed";
72 const kRilDefaultDataServiceId = "ril.data.defaultServiceId";
73 const kRilDefaultPaymentServiceId = "ril.payment.defaultServiceId";
75 const MOBILEMESSAGECALLBACK_CID =
76 Components.ID("{b484d8c9-6be4-4f94-ab60-c9c7ebcc853d}");
78 // In order to send messages through nsISmsService, we need to implement
79 // nsIMobileMessageCallback, as the WebSMS API implementation is not usable
80 // from JS.
81 function SilentSmsRequest() {
82 }
84 SilentSmsRequest.prototype = {
85 __exposedProps__: {
86 onsuccess: "rw",
87 onerror: "rw"
88 },
90 QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileMessageCallback]),
92 classID: MOBILEMESSAGECALLBACK_CID,
94 set onsuccess(aSuccessCallback) {
95 this._onsuccess = aSuccessCallback;
96 },
98 set onerror(aErrorCallback) {
99 this._onerror = aErrorCallback;
100 },
102 notifyMessageSent: function notifyMessageSent(aMessage) {
103 if (_debug) {
104 LOG("Silent message successfully sent");
105 }
106 this._onsuccess(aMessage);
107 },
109 notifySendMessageFailed: function notifySendMessageFailed(aError) {
110 LOGE("Error sending silent message " + aError);
111 this._onerror(aError);
112 }
113 };
115 function PaymentSettings() {
116 Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
118 [kRilDefaultDataServiceId, kRilDefaultPaymentServiceId].forEach(setting => {
119 gSettingsService.createLock().get(setting, this);
120 });
121 }
123 PaymentSettings.prototype = {
124 QueryInterface: XPCOMUtils.generateQI([Ci.nsISettingsServiceCallback,
125 Ci.nsIObserver]),
127 dataServiceId: 0,
128 _paymentServiceId: 0,
130 get paymentServiceId() {
131 return this._paymentServiceId;
132 },
134 set paymentServiceId(serviceId) {
135 // We allow the payment provider to set the service ID that will be used
136 // for the payment process.
137 // This service ID will be the one used by the silent SMS flow.
138 // If the payment is done with an external SIM, the service ID must be set
139 // to null.
140 if (serviceId != null && serviceId >= gRil.numRadioInterfaces) {
141 LOGE("Invalid service ID " + serviceId);
142 return;
143 }
145 gSettingsService.createLock().set(kRilDefaultPaymentServiceId,
146 serviceId, null);
147 this._paymentServiceId = serviceId;
148 },
150 setServiceId: function(aName, aValue) {
151 switch (aName) {
152 case kRilDefaultDataServiceId:
153 this.dataServiceId = aValue;
154 if (_debug) {
155 LOG("dataServiceId " + this.dataServiceId);
156 }
157 break;
158 case kRilDefaultPaymentServiceId:
159 this._paymentServiceId = aValue;
160 if (_debug) {
161 LOG("paymentServiceId " + this._paymentServiceId);
162 }
163 break;
164 }
165 },
167 handle: function(aName, aValue) {
168 if (aName != kRilDefaultDataServiceId) {
169 return;
170 }
172 this.setServiceId(aName, aValue);
173 },
175 observe: function(aSubject, aTopic, aData) {
176 if (aTopic != kMozSettingsChangedObserverTopic) {
177 return;
178 }
180 try {
181 let setting = JSON.parse(aData);
182 if (!setting.key ||
183 (setting.key !== kRilDefaultDataServiceId &&
184 setting.key !== kRilDefaultPaymentServiceId)) {
185 return;
186 }
187 this.setServiceId(setting.key, setting.value);
188 } catch (e) {
189 LOGE(e);
190 }
191 },
193 cleanup: function() {
194 Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
195 }
196 };
197 #endif
199 const kClosePaymentFlowEvent = "close-payment-flow-dialog";
201 let gRequestId;
203 let PaymentProvider = {
204 #ifdef MOZ_B2G_RIL
205 __exposedProps__: {
206 paymentSuccess: "r",
207 paymentFailed: "r",
208 paymentServiceId: "rw",
209 iccInfo: "r",
210 sendSilentSms: "r",
211 observeSilentSms: "r",
212 removeSilentSmsObserver: "r"
213 },
214 #else
215 __exposedProps__: {
216 paymentSuccess: "r",
217 paymentFailed: "r"
218 },
219 #endif
221 _init: function _init() {
222 #ifdef MOZ_B2G_RIL
223 this._settings = new PaymentSettings();
224 #endif
225 },
227 _closePaymentFlowDialog: function _closePaymentFlowDialog(aCallback) {
228 // After receiving the payment provider confirmation about the
229 // successful or failed payment flow, we notify the UI to close the
230 // payment flow dialog and return to the caller application.
231 let id = kClosePaymentFlowEvent + "-" + uuidgen.generateUUID().toString();
233 let detail = {
234 type: kClosePaymentFlowEvent,
235 id: id,
236 requestId: gRequestId
237 };
239 // In order to avoid race conditions, we wait for the UI to notify that
240 // it has successfully closed the payment flow and has recovered the
241 // caller app, before notifying the parent process to fire the success
242 // or error event over the DOMRequest.
243 SystemAppProxy.addEventListener("mozContentEvent",
244 function closePaymentFlowReturn(evt) {
245 if (evt.detail.id == id && aCallback) {
246 aCallback();
247 }
249 SystemAppProxy.removeEventListener("mozContentEvent",
250 closePaymentFlowReturn);
252 let glue = Cc["@mozilla.org/payment/ui-glue;1"]
253 .createInstance(Ci.nsIPaymentUIGlue);
254 glue.cleanup();
255 });
257 SystemAppProxy.dispatchEvent(detail);
259 #ifdef MOZ_B2G_RIL
260 this._cleanUp();
261 #endif
262 },
264 paymentSuccess: function paymentSuccess(aResult) {
265 if (_debug) {
266 LOG("paymentSuccess " + aResult);
267 }
269 PaymentProvider._closePaymentFlowDialog(function notifySuccess() {
270 if (!gRequestId) {
271 return;
272 }
273 cpmm.sendAsyncMessage("Payment:Success", { result: aResult,
274 requestId: gRequestId });
275 });
276 },
278 paymentFailed: function paymentFailed(aErrorMsg) {
279 LOGE("paymentFailed " + aErrorMsg);
281 PaymentProvider._closePaymentFlowDialog(function notifyError() {
282 if (!gRequestId) {
283 return;
284 }
285 cpmm.sendAsyncMessage("Payment:Failed", { errorMsg: aErrorMsg,
286 requestId: gRequestId });
287 });
288 },
290 #ifdef MOZ_B2G_RIL
291 get paymentServiceId() {
292 return this._settings.paymentServiceId;
293 },
295 set paymentServiceId(serviceId) {
296 this._settings.paymentServiceId = serviceId;
297 },
299 // We expose to the payment provider the information of all the SIMs
300 // available in the device. iccInfo is an object of this form:
301 // {
302 // "serviceId1": {
303 // mcc: <string>,
304 // mnc: <string>,
305 // iccId: <string>,
306 // dataPrimary: <boolean>
307 // },
308 // "serviceIdN": {...}
309 // }
310 get iccInfo() {
311 if (!this._iccInfo) {
312 this._iccInfo = {};
313 for (let i = 0; i < gRil.numRadioInterfaces; i++) {
314 let info = iccProvider.getIccInfo(i);
315 if (!info) {
316 LOGE("Tried to get the ICC info for an invalid service ID " + i);
317 continue;
318 }
320 this._iccInfo[i] = {
321 iccId: info.iccid,
322 mcc: info.mcc,
323 mnc: info.mnc,
324 dataPrimary: i == this._settings.dataServiceId
325 };
326 }
327 }
329 return Cu.cloneInto(this._iccInfo, content);
330 },
332 _silentNumbers: null,
333 _silentSmsObservers: null,
335 sendSilentSms: function sendSilentSms(aNumber, aMessage) {
336 if (_debug) {
337 LOG("Sending silent message " + aNumber + " - " + aMessage);
338 }
340 let request = new SilentSmsRequest();
342 if (this._settings.paymentServiceId === null) {
343 LOGE("No payment service ID set. Cannot send silent SMS");
344 let runnable = {
345 run: function run() {
346 request.notifySendMessageFailed("NO_PAYMENT_SERVICE_ID");
347 }
348 };
349 Services.tm.currentThread.dispatch(runnable,
350 Ci.nsIThread.DISPATCH_NORMAL);
351 return request;
352 }
354 smsService.send(this._settings.paymentServiceId, aNumber, aMessage, true,
355 request);
356 return request;
357 },
359 observeSilentSms: function observeSilentSms(aNumber, aCallback) {
360 if (_debug) {
361 LOG("observeSilentSms " + aNumber);
362 }
364 if (!this._silentSmsObservers) {
365 this._silentSmsObservers = {};
366 this._silentNumbers = [];
367 Services.obs.addObserver(this._onSilentSms.bind(this),
368 kSilentSmsReceivedTopic,
369 false);
370 }
372 if (!this._silentSmsObservers[aNumber]) {
373 this._silentSmsObservers[aNumber] = [];
374 this._silentNumbers.push(aNumber);
375 smsService.addSilentNumber(aNumber);
376 }
378 if (this._silentSmsObservers[aNumber].indexOf(aCallback) == -1) {
379 this._silentSmsObservers[aNumber].push(aCallback);
380 }
381 },
383 removeSilentSmsObserver: function removeSilentSmsObserver(aNumber, aCallback) {
384 if (_debug) {
385 LOG("removeSilentSmsObserver " + aNumber);
386 }
388 if (!this._silentSmsObservers || !this._silentSmsObservers[aNumber]) {
389 if (_debug) {
390 LOG("No observers for " + aNumber);
391 }
392 return;
393 }
395 let index = this._silentSmsObservers[aNumber].indexOf(aCallback);
396 if (index != -1) {
397 this._silentSmsObservers[aNumber].splice(index, 1);
398 if (this._silentSmsObservers[aNumber].length == 0) {
399 this._silentSmsObservers[aNumber] = null;
400 this._silentNumbers.splice(this._silentNumbers.indexOf(aNumber), 1);
401 smsService.removeSilentNumber(aNumber);
402 }
403 } else if (_debug) {
404 LOG("No callback found for " + aNumber);
405 }
406 },
408 _onSilentSms: function _onSilentSms(aSubject, aTopic, aData) {
409 if (_debug) {
410 LOG("Got silent message! " + aSubject.sender + " - " + aSubject.body);
411 }
413 let number = aSubject.sender;
414 if (!number || this._silentNumbers.indexOf(number) == -1) {
415 if (_debug) {
416 LOG("No observers for " + number);
417 }
418 return;
419 }
421 // If the service ID is null it means that the payment provider asked the
422 // user for her MSISDN, so we are in a MT only SMS auth flow. In this case
423 // we manually set the service ID to the one corresponding with the SIM
424 // that received the SMS.
425 if (this._settings.paymentServiceId === null) {
426 let i = 0;
427 while(i < gRil.numRadioInterfaces) {
428 if (this.iccInfo[i].iccId === aSubject.iccId) {
429 this._settings.paymentServiceId = i;
430 break;
431 }
432 i++;
433 }
434 }
436 this._silentSmsObservers[number].forEach(function(callback) {
437 callback(aSubject);
438 });
439 },
441 _cleanUp: function _cleanUp() {
442 if (_debug) {
443 LOG("Cleaning up!");
444 }
446 if (!this._silentNumbers) {
447 return;
448 }
450 while (this._silentNumbers.length) {
451 let number = this._silentNumbers.pop();
452 smsService.removeSilentNumber(number);
453 }
454 this._silentNumbers = null;
455 this._silentSmsObservers = null;
456 this._settings.cleanup();
457 Services.obs.removeObserver(this._onSilentSms, kSilentSmsReceivedTopic);
458 }
459 #endif
460 };
462 // We save the identifier of the DOM request, so we can dispatch the results
463 // of the payment flow to the appropriate content process.
464 addMessageListener("Payment:LoadShim", function receiveMessage(aMessage) {
465 gRequestId = aMessage.json.requestId;
466 PaymentProvider._init();
467 });
469 addEventListener("DOMWindowCreated", function(e) {
470 content.wrappedJSObject.mozPaymentProvider = PaymentProvider;
471 });
473 #ifdef MOZ_B2G_RIL
474 // If the trusted dialog is not closed via paymentSuccess or paymentFailed
475 // a mozContentEvent with type 'cancel' is sent from the UI. We need to listen
476 // for this event to clean up the silent sms observers if any exists.
477 SystemAppProxy.addEventListener("mozContentEvent", function(e) {
478 if (e.detail.type === "cancel") {
479 PaymentProvider._cleanUp();
480 }
481 });
482 #endif