Merge b2g-inbound to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2014-06-08 21:38:51 -04:00
commit 323fbaf43a
52 changed files with 2969 additions and 155 deletions

View File

@ -993,5 +993,7 @@ pref("services.sync.fxaccounts.enabled", true);
pref("identity.fxaccounts.enabled", true);
#endif
pref("services.mobileid.server.uri", "http://msisdn.dev.mozaws.net");
// Enable mapped array buffer
pref("dom.mapped_arraybuffer.enabled", true);

View File

@ -30,6 +30,7 @@ Cu.import('resource://gre/modules/FxAccountsMgmtService.jsm');
#endif
Cu.import('resource://gre/modules/DownloadsAPI.jsm');
Cu.import('resource://gre/modules/MobileIdentityManager.jsm');
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");

View File

@ -95,3 +95,6 @@ contract @mozilla.org/commandlinehandler/general-startup;1?type=b2goop {e30b0e13
category command-line-handler m-b2goop @mozilla.org/commandlinehandler/general-startup;1?type=b2goop
#endif
# MobileIdentityUIGlue.js
component {83dbe26a-81f3-4a75-9541-3d0b7ca496b5} MobileIdentityUIGlue.js
contract @mozilla.org/services/mobileid-ui-glue;1 {83dbe26a-81f3-4a75-9541-3d0b7ca496b5}

View File

@ -0,0 +1,68 @@
/* 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";
this.EXPORTED_SYMBOLS = ["ContentRequestHelper"];
const { interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
function debug(msg) {
// dump("ContentRequestHelper ** " + msg + "\n");
}
this.ContentRequestHelper = function() {
}
ContentRequestHelper.prototype = {
contentRequest: function(aContentEventName, aChromeEventName,
aInternalEventName, aData) {
let deferred = Promise.defer();
let id = uuidgen.generateUUID().toString();
SystemAppProxy.addEventListener(aContentEventName,
function onContentEvent(result) {
SystemAppProxy.removeEventListener(aContentEventName,
onContentEvent);
let msg = result.detail;
if (!msg || !msg.id || msg.id != id) {
deferred.reject("InternalErrorWrongContentEvent " +
JSON.stringify(msg));
SystemAppProxy.removeEventListener(aContentEventName,
onContentEvent);
return;
}
debug("Got content event " + JSON.stringify(msg));
if (msg.error) {
deferred.reject(msg.error);
} else {
deferred.resolve(msg.result);
}
});
let detail = {
eventName: aInternalEventName,
id: id,
data: aData
};
debug("Send chrome event " + JSON.stringify(detail));
SystemAppProxy._sendCustomEvent(aChromeEventName, detail);
return deferred.promise;
}
};

View File

@ -6,66 +6,26 @@
const { interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
Cu.import("resource://gre/modules/ContentRequestHelper.jsm");
function FxAccountsUIGlue() {
}
FxAccountsUIGlue.prototype = {
_contentRequest: function(aEventName, aData) {
let deferred = Promise.defer();
let id = uuidgen.generateUUID().toString();
SystemAppProxy.addEventListener("mozFxAccountsRPContentEvent",
function onContentEvent(result) {
let msg = result.detail;
if (!msg || !msg.id || msg.id != id) {
deferred.reject("InternalErrorWrongContentEvent");
SystemAppProxy.removeEventListener("mozFxAccountsRPContentEvent",
onContentEvent);
return;
}
log.debug("UIGlue got content event " + JSON.stringify(msg));
if (msg.error) {
deferred.reject(msg);
} else {
deferred.resolve(msg.result);
}
SystemAppProxy.removeEventListener("mozFxAccountsRPContentEvent",
onContentEvent);
});
let detail = {
eventName: aEventName,
id: id,
data: aData
};
log.debug("Send chrome event " + JSON.stringify(detail));
SystemAppProxy._sendCustomEvent("mozFxAccountsUnsolChromeEvent", detail);
return deferred.promise;
},
__proto__: ContentRequestHelper.prototype,
signInFlow: function() {
return this._contentRequest("openFlow");
return this.contentRequest("mozFxAccountsRPContentEvent",
"mozFxAccountsUnsolChromeEvent",
"openFlow");
},
refreshAuthentication: function(aEmail) {
return this._contentRequest("refreshAuthentication", {
return this.contentRequest("mozFxAccountsRPContentEvent",
"mozFxAccountsUnsolChromeEvent",
"refreshAuthentication", {
email: aEmail
});
},

View File

@ -0,0 +1,161 @@
/* 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 { interfaces: Ci, utils: Cu, classes: Cc } = Components;
Cu.import("resource://gre/modules/ContentRequestHelper.jsm");
Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
Cu.import("resource://gre/modules/MobileIdentityUIGlueCommon.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
const CHROME_EVENT = "mozMobileIdChromeEvent";
const CONTENT_EVENT = "mozMobileIdContentEvent";
const UNSOLICITED_CONTENT_EVENT = "mozMobileIdUnsolContentEvent";
function MobileIdentityUIGlue() {
SystemAppProxy.addEventListener(UNSOLICITED_CONTENT_EVENT, this);
}
MobileIdentityUIGlue.prototype = {
__proto__: ContentRequestHelper.prototype,
_sendChromeEvent: function(aEventName, aData) {
SystemAppProxy._sendCustomEvent(CHROME_EVENT, {
eventName: aEventName,
id: uuidgen.generateUUID().toString(),
data: aData
});
},
_oncancel: null,
get oncancel() {
return this._oncancel;
},
set oncancel(aCallback) {
this._oncancel = aCallback;
},
_onresendcode: null,
get onresendcode() {
return this._onresendcode;
},
set onresendcode(aCallback) {
this._onresendcode = aCallback;
},
startFlow: function(aManifestURL, aIccInfo) {
let phoneNumberInfo;
if (aIccInfo) {
phoneNumberInfo = [];
for (var i = 0; i < aIccInfo.length; i++) {
let iccInfo = aIccInfo[i];
phoneNumberInfo.push({
primary: iccInfo.primary,
msisdn: iccInfo.msisdn,
operator: iccInfo.operator,
external: iccInfo.external,
serviceId: iccInfo.serviceId,
mcc: iccInfo.mcc
});
}
}
return this.contentRequest(CONTENT_EVENT,
CHROME_EVENT,
"onpermissionrequest",
{ phoneNumberInfo: phoneNumberInfo || [],
manifestURL: aManifestURL })
.then(
(result) => {
if (!result || !result.phoneNumber && !result.serviceId) {
return Promise.reject(ERROR_INVALID_PROMPT_RESULT);
}
let promptResult = new MobileIdentityUIGluePromptResult(
result.phoneNumber || null,
result.prefix || null,
result.mcc || null,
result.serviceId || null
);
return promptResult;
}
);
},
verificationCodePrompt: function(aRetriesLeft, aTimeout, aTimeLeft) {
return this.contentRequest(CONTENT_EVENT,
CHROME_EVENT,
"onverificationcode",
{ retriesLeft: aRetriesLeft,
verificationTimeout: aTimeout,
verificationTimeoutLeft: aTimeLeft })
.then(
(result) => {
if (!result || !result.verificationCode) {
return Promise.reject(ERROR_INVALID_VERIFICATION_CODE);
}
return result.verificationCode;
}
);
},
error: function(aError) {
log.error("UI error " + aError);
this._sendChromeEvent("onerror", {
error: aError
});
},
verify: function() {
this._sendChromeEvent("verify");
},
verified: function(aVerifiedPhoneNumber) {
this._sendChromeEvent("onverified", {
verifiedPhoneNumber: aVerifiedPhoneNumber
});
},
handleEvent: function(aEvent) {
let msg = aEvent.detail;
if (!msg) {
log.warning("Got invalid event");
return;
}
log.debug("Got content event ${}", msg);
switch(msg.eventName) {
case 'cancel':
this.oncancel();
break;
case 'resendcode':
this.onresendcode();
break;
default:
log.warning("Invalid event name");
break;
}
},
classID: Components.ID("{83dbe26a-81f3-4a75-9541-3d0b7ca496b5}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileIdentityUIGlue])
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MobileIdentityUIGlue]);

View File

@ -15,6 +15,7 @@ EXTRA_COMPONENTS += [
'HelperAppDialog.js',
'InterAppCommUIGlue.js',
'MailtoProtocolHandler.js',
'MobileIdentityUIGlue.js',
'PaymentGlue.js',
'ProcessGlobal.js',
'SmsProtocolHandler.js',
@ -43,6 +44,7 @@ if CONFIG['MOZ_UPDATER']:
EXTRA_JS_MODULES += [
'AlertsHelper.jsm',
'ContentRequestHelper.jsm',
'ErrorPage.jsm',
'SignInToWebsite.jsm',
'SystemAppProxy.jsm',

View File

@ -19,8 +19,8 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2e5636e852a9354a5f8072b179cf16f72647cfd6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="12af93123c5db55212d51fe235d39f21209a1eaa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8e4420c0c5c8e8c8e58a000278a7129403769f96"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="a819a94a572c7b32556435491ed8eaab841a95ff"/>

View File

@ -17,8 +17,8 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="2e5636e852a9354a5f8072b179cf16f72647cfd6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="12af93123c5db55212d51fe235d39f21209a1eaa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="cabebb87fcd32f8596af08e6b5e80764ee0157dd"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>

View File

@ -15,9 +15,9 @@
<project name="platform_build" path="build" remote="b2g" revision="276ce45e78b09c4a4ee643646f691d22804754c1">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="2e5636e852a9354a5f8072b179cf16f72647cfd6"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="12af93123c5db55212d51fe235d39f21209a1eaa"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>

View File

@ -19,8 +19,8 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2e5636e852a9354a5f8072b179cf16f72647cfd6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="12af93123c5db55212d51fe235d39f21209a1eaa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8e4420c0c5c8e8c8e58a000278a7129403769f96"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="a819a94a572c7b32556435491ed8eaab841a95ff"/>

View File

@ -17,8 +17,8 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="2e5636e852a9354a5f8072b179cf16f72647cfd6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="12af93123c5db55212d51fe235d39f21209a1eaa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="cabebb87fcd32f8596af08e6b5e80764ee0157dd"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "0a26eafbba1c1cf459dae8b1c44b3c8b6d08f112",
"revision": "ce7008f6cb1d3dc7e7c2f311dc4afae35d2e55a0",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,8 +17,8 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2e5636e852a9354a5f8072b179cf16f72647cfd6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="12af93123c5db55212d51fe235d39f21209a1eaa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>

View File

@ -15,8 +15,8 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2e5636e852a9354a5f8072b179cf16f72647cfd6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="12af93123c5db55212d51fe235d39f21209a1eaa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>

View File

@ -17,8 +17,8 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="2e5636e852a9354a5f8072b179cf16f72647cfd6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="12af93123c5db55212d51fe235d39f21209a1eaa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="cabebb87fcd32f8596af08e6b5e80764ee0157dd"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>

View File

@ -17,8 +17,8 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2e5636e852a9354a5f8072b179cf16f72647cfd6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="12af93123c5db55212d51fe235d39f21209a1eaa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>

View File

@ -831,6 +831,12 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DLL_SUFFIX@
@BINPATH@/components/DataStoreImpl.js
@BINPATH@/components/dom_datastore.xpt
@BINPATH@/components/MobileIdentity.manifest
@BINPATH@/components/MobileIdentity.js
@BINPATH@/components/dom_mobileidentity.xpt
@BINPATH@/components/MobileIdentityUIGlue.js
@BINPATH@/components/services_mobileidentity.xpt
#ifdef MOZ_WEBSPEECH
@BINPATH@/components/dom_webspeechsynth.xpt
#endif

View File

@ -43,6 +43,9 @@
#include "Connection.h"
#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
#include "nsGlobalWindow.h"
#ifdef MOZ_B2G
#include "nsIMobileIdentityService.h"
#endif
#ifdef MOZ_B2G_RIL
#include "mozilla/dom/IccManager.h"
#include "mozilla/dom/CellBroadcast.h"
@ -1584,6 +1587,30 @@ Navigator::GetMozTelephony(ErrorResult& aRv)
return mTelephony;
}
#ifdef MOZ_B2G
already_AddRefed<Promise>
Navigator::GetMobileIdAssertion(ErrorResult& aRv)
{
if (!mWindow || !mWindow->GetDocShell()) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
nsCOMPtr<nsIMobileIdentityService> service =
do_GetService("@mozilla.org/mobileidentity-service;1");
if (!service) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsCOMPtr<nsISupports> promise;
aRv = service->GetMobileIdAssertion(mWindow, getter_AddRefs(promise));
nsRefPtr<Promise> p = static_cast<Promise*>(promise.get());
return p.forget();
}
#endif // MOZ_B2G
#ifdef MOZ_B2G_RIL
MobileConnectionArray*
@ -1647,7 +1674,6 @@ Navigator::GetMozIccManager(ErrorResult& aRv)
return mIccManager;
}
#endif // MOZ_B2G_RIL
#ifdef MOZ_GAMEPAD

View File

@ -215,12 +215,15 @@ public:
systemMessageCallback* aCallback,
ErrorResult& aRv);
bool MozHasPendingMessage(const nsAString& aType, ErrorResult& aRv);
#ifdef MOZ_B2G
already_AddRefed<Promise> GetMobileIdAssertion(ErrorResult& aRv);
#endif
#ifdef MOZ_B2G_RIL
MobileConnectionArray* GetMozMobileConnections(ErrorResult& aRv);
CellBroadcast* GetMozCellBroadcast(ErrorResult& aRv);
Voicemail* GetMozVoicemail(ErrorResult& aRv);
IccManager* GetMozIccManager(ErrorResult& aRv);
#endif // MOZ_B2G_RIL
#endif // MOZ_B2G_RILi
#ifdef MOZ_GAMEPAD
void GetGamepads(nsTArray<nsRefPtr<Gamepad> >& aGamepads, ErrorResult& aRv);
#endif // MOZ_GAMEPAD

View File

@ -164,7 +164,7 @@ static CINDItem sCINDItems[] = {
#endif
};
class BluetoothHfpManager::GetVolumeTask : public nsISettingsServiceCallback
class BluetoothHfpManager::GetVolumeTask MOZ_FINAL : public nsISettingsServiceCallback
{
public:
NS_DECL_ISUPPORTS

View File

@ -0,0 +1,11 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
XPIDL_SOURCES += [
'nsIMobileIdentityService.idl',
]
XPIDL_MODULE = 'dom_mobileidentity'

View File

@ -0,0 +1,13 @@
/* 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"
interface nsIDOMWindow;
[scriptable, uuid(376d3a43-a7f2-4ac0-b196-d83acb3a68cc)]
interface nsIMobileIdentityService : nsISupports
{
nsISupports getMobileIdAssertion(in nsIDOMWindow window);
};

7
dom/mobileid/moz.build Normal file
View File

@ -0,0 +1,7 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
PARALLEL_DIRS += ['interfaces', 'src']

View File

@ -0,0 +1,108 @@
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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;
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const MOBILEIDSERVICE_CID =
Components.ID("{6ec1806c-d61f-4724-9495-68c26d46dc53}");
const IPC_MSG_NAMES = ["MobileId:GetAssertion:Return:OK",
"MobileId:GetAssertion:Return:KO"];
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
function MobileIdentityService() {
}
MobileIdentityService.prototype = {
__proto__: DOMRequestIpcHelper.prototype,
// TODO: this should be handled by DOMRequestIpcHelper. Bug 1020582
_windows: {},
getMobileIdAssertion: function(aWindow) {
log.debug("getMobileIdAssertion");
if (!this.init) {
this.initDOMRequestHelper(aWindow, IPC_MSG_NAMES);
this.init = true;
}
return new aWindow.Promise(
(resolve, reject) => {
let promiseId = this.getPromiseResolverId({
resolve: resolve,
reject: reject
});
this._windows[promiseId] = aWindow;
cpmm.sendAsyncMessage("MobileId:GetAssertion", {
promiseId: promiseId
}, null, aWindow.document.nodePrincipal);
}
);
},
receiveMessage: function(aMessage) {
let name = aMessage.name;
let msg = aMessage.json;
log.debug("Received message " + name + ": " + JSON.stringify(msg));
let promiseId = msg.promiseId || msg.requestID;
let promise = this.takePromiseResolver(promiseId);
if (!promise) {
log.error("Promise not found");
return;
}
let _window = this._windows[promiseId];
delete this._windows[promiseId];
if (!_window) {
log.error("No window object found");
return;
}
switch (name) {
case "MobileId:GetAssertion:Return:OK":
if (!msg.result) {
promise.reject(new _window.DOMError(ERROR_INTERNAL_UNEXPECTED));
}
// Return the assertion
promise.resolve(msg.result);
break;
case "MobileId:GetAssertion:Return:KO":
promise.reject(new _window.DOMError(msg.error || ERROR_UNKNOWN));
break;
}
},
classID: MOBILEIDSERVICE_CID,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileIdentityService,
Ci.nsISupportsWeakReference,
Ci.nsIObserver]),
classInfo: XPCOMUtils.generateCI({
classID: MOBILEIDSERVICE_CID,
contractID: "@mozilla.org/mobileidentity-service;1",
interfaces: [Ci.nsIMobileIdentityService],
flags: Ci.nsIClassInfo.SINGLETON
})
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MobileIdentityService]);

View File

@ -0,0 +1,2 @@
component {6ec1806c-d61f-4724-9495-68c26d46dc53} MobileIdentity.js
contract @mozilla.org/mobileidentity-service;1 {6ec1806c-d61f-4724-9495-68c26d46dc53}

View File

@ -0,0 +1,10 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXTRA_COMPONENTS += [
'MobileIdentity.js',
'MobileIdentity.manifest',
]

View File

@ -113,7 +113,10 @@ if CONFIG['MOZ_NFC']:
PARALLEL_DIRS += ['nfc']
if CONFIG['MOZ_B2G']:
PARALLEL_DIRS += ['downloads']
PARALLEL_DIRS += [
'downloads',
'mobileid'
]
if CONFIG['MOZ_B2G_BT_API_V2']:
PARALLEL_DIRS += ['bluetooth2']

View File

@ -3,8 +3,21 @@
MARIONETTE_CONTEXT = "chrome";
const SETTINGS_KEY_DATA_ENABLED = "ril.data.enabled";
const SETTINGS_KEY_DATA_APN_SETTINGS = "ril.data.apnSettings";
const TOPIC_CONNECTION_STATE_CHANGED = "network-connection-state-changed";
const TOPIC_INTERFACE_STATE_CHANGED = "network-interface-state-changed";
const TOPIC_NETWORK_ACTIVE_CHANGED = "network-active-changed";
let Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise;
let ril = Cc["@mozilla.org/ril;1"].getService(Ci.nsIRadioInterfaceLayer);
ok(ril, "ril.constructor is " + ril.constructor);
let radioInterface = ril.getRadioInterface(0);
ok(radioInterface, "radioInterface.constructor is " + radioInterface.constrctor);
/**
* Wrap DOMRequest onsuccess/onerror events to Promise resolve/reject.
*
@ -116,6 +129,103 @@ function waitForObserverEvent(aTopic) {
return deferred.promise;
}
let mobileTypeMapping = {
"default": Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
"mms": Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS,
"supl": Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_SUPL,
"ims": Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_IMS,
"dun": Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN
};
/**
* Set the default data connection enabling state, wait for
* "network-connection-state-changed" event and verify state.
*
* Fulfill params: (none)
*
* @param aEnabled
* A boolean state.
*
* @return A deferred promise.
*/
function setDataEnabledAndWait(aEnabled) {
let promises = [];
promises.push(waitForObserverEvent(TOPIC_CONNECTION_STATE_CHANGED)
.then(function(aSubject) {
ok(aSubject instanceof Ci.nsIRilNetworkInterface,
"subject should be an instance of nsIRILNetworkInterface");
is(aSubject.type, Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
"subject.type should be " + Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE);
is(aSubject.state,
aEnabled ? Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED
: Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED,
"subject.state should be " + aEnabled ? "CONNECTED" : "DISCONNECTED");
}));
promises.push(setSettings(SETTINGS_KEY_DATA_ENABLED, aEnabled));
return Promise.all(promises);
}
/**
* Setup a certain type of data connection, wait for
* "network-connection-state-changed" event and verify state.
*
* Fulfill params: (none)
*
* @param aType
* The string of the type of data connection to setup.
*
* @return A deferred promise.
*/
function setupDataCallAndWait(aType) {
log("setupDataCallAndWait: " + aType);
let promises = [];
promises.push(waitForObserverEvent(TOPIC_CONNECTION_STATE_CHANGED)
.then(function(aSubject) {
let networkType = mobileTypeMapping[aType];
ok(aSubject instanceof Ci.nsIRilNetworkInterface,
"subject should be an instance of nsIRILNetworkInterface");
is(aSubject.type, networkType,
"subject.type should be " + networkType);
is(aSubject.state, Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED,
"subject.state should be CONNECTED");
}));
promises.push(radioInterface.setupDataCallByType(aType));
return Promise.all(promises);
}
/**
* Deactivate a certain type of data connection, wait for
* "network-connection-state-changed" event and verify state.
*
* Fulfill params: (none)
*
* @param aType
* The string of the type of data connection to deactivate.
*
* @return A deferred promise.
*/
function deactivateDataCallAndWait(aType) {
log("deactivateDataCallAndWait: " + aType);
let promises = [];
promises.push(waitForObserverEvent(TOPIC_CONNECTION_STATE_CHANGED)
.then(function(aSubject) {
let networkType = mobileTypeMapping[aType];
ok(aSubject instanceof Ci.nsIRilNetworkInterface,
"subject should be an instance of nsIRILNetworkInterface");
is(aSubject.type, networkType,
"subject.type should be " + networkType);
is(aSubject.state, Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED,
"subject.state should be DISCONNECTED");
}));
promises.push(radioInterface.deactivateDataCallByType(aType));
return Promise.all(promises);
}
/**
* Basic test routine helper.
*

View File

@ -11,3 +11,4 @@ disabled = Bug 808783
[test_dsds_numRadioInterfaces.js]
[test_data_connection.js]
[test_network_active_changed.js]
[test_multiple_data_connection.js]

View File

@ -4,18 +4,6 @@
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = "head.js";
Cu.import("resource://gre/modules/Promise.jsm");
const DATA_KEY = "ril.data.enabled";
const APN_KEY = "ril.data.apnSettings";
const TOPIC_CONNECTION_STATE_CHANGED = "network-connection-state-changed";
let ril = Cc["@mozilla.org/ril;1"].getService(Ci.nsIRadioInterfaceLayer);
ok(ril, "ril.constructor is " + ril.constructor);
let radioInterface = ril.getRadioInterface(0);
ok(radioInterface, "radioInterface.constructor is " + radioInterface.constrctor);
function setEmulatorAPN() {
let apn = [
[{"carrier":"T-Mobile US",
@ -24,57 +12,7 @@ function setEmulatorAPN() {
"types":["default","supl","mms","ims","dun"]}]
];
return setSettings(APN_KEY, apn);
}
function setupDataCallAndWait(type, networkType) {
let promises = [];
promises.push(waitForObserverEvent(TOPIC_CONNECTION_STATE_CHANGED));
promises.push(radioInterface.setupDataCallByType(type));
return Promise.all(promises).then(function(results) {
let subject = results[0];
ok(subject instanceof Ci.nsIRilNetworkInterface,
"subject should be an instance of nsIRILNetworkInterface");
is(subject.type, networkType,
"subject.type should be " + networkType);
is(subject.state, Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED,
"subject.state should be CONNECTED");
});
}
function deactivateDataCallAndWait(type, networkType) {
let promises = [];
promises.push(waitForObserverEvent(TOPIC_CONNECTION_STATE_CHANGED));
promises.push(radioInterface.deactivateDataCallByType(type));
return Promise.all(promises).then(function(results) {
let subject = results[0];
ok(subject instanceof Ci.nsIRilNetworkInterface,
"subject should be an instance of nsIRILNetworkInterface");
is(subject.type, networkType,
"subject.type should be " + networkType);
is(subject.state, Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED,
"subject.state should be DISCONNECTED");
});
}
function setDataEnabledAndWait(enabled) {
let promises = [];
promises.push(waitForObserverEvent(TOPIC_CONNECTION_STATE_CHANGED));
promises.push(setSettings(DATA_KEY, enabled));
return Promise.all(promises).then(function(results) {
let subject = results[0];
ok(subject instanceof Ci.nsIRilNetworkInterface,
"subject should be an instance of nsIRILNetworkInterface");
is(subject.type, Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
"subject.type should be " + Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE);
is(subject.state,
enabled ? Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED
: Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED,
"subject.state should be " + enabled ? "CONNECTED" : "DISCONNECTED");
});
return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, apn);
}
// Test initial State
@ -82,7 +20,7 @@ function testInitialState() {
log("= testInitialState =");
// Data should be off before starting any test.
return getSettings(DATA_KEY)
return getSettings(SETTINGS_KEY_DATA_ENABLED)
.then(value => {
is(value, false, "Data must be off");
});
@ -105,20 +43,12 @@ function testNonDefaultDataConnection() {
function doTestNonDefaultDataConnection(type) {
log("doTestNonDefaultDataConnection: " + type);
let typeMapping = {
"mms": Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS,
"supl": Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_SUPL,
"ims": Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_IMS,
"dun": Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN
};
let networkType = typeMapping[type];
return setupDataCallAndWait(type, networkType)
.then(() => deactivateDataCallAndWait(type, networkType));
return setupDataCallAndWait(type)
.then(() => deactivateDataCallAndWait(type));
}
let currentApn;
return getSettings(APN_KEY)
return getSettings(SETTINGS_KEY_DATA_APN_SETTINGS)
.then(value => {
currentApn = value;
})
@ -128,7 +58,7 @@ function testNonDefaultDataConnection() {
.then(() => doTestNonDefaultDataConnection("ims"))
.then(() => doTestNonDefaultDataConnection("dun"))
// Restore APN settings
.then(() => setSettings(APN_KEY, currentApn));
.then(() => setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, currentApn));
}
// Start test

View File

@ -0,0 +1,88 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = "head.js";
// Must sync with hardware/ril/reference-ril/reference-ril.c
const MAX_DATA_CONTEXTS = 4;
function setEmulatorAPN() {
// Use different apn for each network type.
let apn = [[ { "carrier":"T-Mobile US",
"apn":"epc1.tmobile.com",
"types":["default"] },
{ "carrier":"T-Mobile US",
"apn":"epc2.tmobile.com",
"mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc",
"types":["mms"] },
{ "carrier":"T-Mobile US",
"apn":"epc3.tmobile.com",
"types":["supl"] },
{ "carrier":"T-Mobile US",
"apn":"epc4.tmobile.com",
"types":["ims"] },
{ "carrier":"T-Mobile US",
"apn":"epc5.tmobile.com",
"types":["dun"] }]];
return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, apn);
}
// Test initial State
function testInitialState() {
log("= testInitialState =");
// Data should be off before starting any test.
return getSettings(SETTINGS_KEY_DATA_ENABLED)
.then(value => {
is(value, false, "Data must be off");
});
}
function testSetupConcurrentDataCalls() {
log("= testSetupConcurrentDataCalls =");
let promise = Promise.resolve();
let types = Object.keys(mobileTypeMapping);
// Skip default mobile type.
for (let i = 1; i < MAX_DATA_CONTEXTS; i++) {
let type = types[i];
promise = promise.then(() => setupDataCallAndWait(type));
}
return promise;
}
function testDeactivateConcurrentDataCalls() {
log("= testDeactivateConcurrentDataCalls =");
let promise = Promise.resolve();
let types = Object.keys(mobileTypeMapping);
// Skip default mobile type.
for (let i = 1; i < MAX_DATA_CONTEXTS; i++) {
let type = types[i];
promise = promise.then(() => deactivateDataCallAndWait(type));
}
return promise;
}
// Start test
startTestBase(function() {
let origApnSettings;
return testInitialState()
.then(() => getSettings(SETTINGS_KEY_DATA_APN_SETTINGS))
.then(value => {
origApnSettings = value;
})
.then(() => setEmulatorAPN())
.then(() => setDataEnabledAndWait(true))
.then(() => testSetupConcurrentDataCalls())
.then(() => testDeactivateConcurrentDataCalls())
.then(() => setDataEnabledAndWait(false))
.then(() => {
if (origApnSettings) {
return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, origApnSettings);
}
});
});

View File

@ -4,9 +4,6 @@
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = "head.js";
const SETTINGS_KEY_DATA_ENABLED = "ril.data.enabled";
const TOPIC_NETWORK_ACTIVE_CHANGED = "network-active-changed";
let networkManager =
Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager);
ok(networkManager,

View File

@ -151,6 +151,15 @@ callback interface MozIdleObserver {
void onactive();
};
#ifdef MOZ_B2G
[NoInterfaceObject]
interface NavigatorMobileId {
[Throws, NewObject]
Promise getMobileIdAssertion();
};
Navigator implements NavigatorMobileId;
#endif // MOZ_B2G
// nsIDOMNavigator
partial interface Navigator {
[Throws]

View File

@ -0,0 +1,15 @@
[DEFAULT]
; true if the test requires an emulator, otherwise false
qemu = false
; true if the test is compatible with the browser, otherwise false
browser = true
; true if the test is compatible with b2g, otherwise false
b2g = true
; true if the test should be skipped
skip = false
[test_touchcaret.py]
b2g = false ; Bug 1020261

View File

@ -0,0 +1,307 @@
# -*- coding: 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/.
import string
from by import By
from marionette import Actions
from marionette_test import MarionetteTestCase
class TouchCaretTest(MarionetteTestCase):
_input_selector = (By.ID, 'input')
_textarea_selector = (By.ID, 'textarea')
_contenteditable_selector = (By.ID, 'contenteditable')
def setUp(self):
# Code to execute before a tests are run.
MarionetteTestCase.setUp(self)
self.actions = Actions(self.marionette)
def openTestHtml(self, enabled=True):
'''Open html for testing and locate elements, and enable/disable touch
caret.'''
self.marionette.execute_script(
'SpecialPowers.setBoolPref("touchcaret.enabled", %s);' %
('true' if enabled else 'false'))
test_html = self.marionette.absolute_url('test_touchcaret.html')
self.marionette.navigate(test_html)
self._input = self.marionette.find_element(*self._input_selector)
self._textarea = self.marionette.find_element(*self._textarea_selector)
self._contenteditable = self.marionette.find_element(*self._contenteditable_selector)
def is_input_or_textarea(self, element):
'''Return True if element is either <input> or <textarea>'''
return element.tag_name in ('input', 'textarea')
def get_js_selection_cmd(self, element):
'''Return a command snippet to get selection object.
If the element is <input> or <textarea>, return the selection object
associated with it. Otherwise, return the current selection object.
Note: "element" must be provided as the first argument to
execute_script().
'''
if self.is_input_or_textarea(element):
# We must unwrap sel so that DOMRect could be returned to Python
# side.
return '''var sel = SpecialPowers.wrap(arguments[0]).editor.selection;
sel = SpecialPowers.unwrap(sel);'''
else:
return '''var sel = window.getSelection();'''
def caret_rect(self, element):
'''Return the caret's DOMRect object.
If the element is either <input> or <textarea>, return the caret's
DOMRect within the element. Otherwise, return the DOMRect of the
current selected caret.
'''
cmd = self.get_js_selection_cmd(element) +\
'''return sel.getRangeAt(0).getClientRects()[0];'''
return self.marionette.execute_script(cmd, script_args=[element])
def caret_location(self, element):
'''Return caret's center location by the number of characters offset
within the given element.
Return (x, y) coordinates of the caret's center by the number of
characters offset relative to the top left-hand corner of the given
element.
'''
rect = self.caret_rect(element)
x = rect['left'] + rect['width'] / 2.0 - element.location['x']
y = rect['top'] + rect['height'] / 2.0 - element.location['y']
return x, y
def touch_caret_location(self, element):
'''Return touch caret's location (based on current caret location).
Return (x, y) coordinates of the touch caret's tip relative to the top
left-hand corner of the given element.
'''
rect = self.caret_rect(element)
x = rect['left'] - element.location['x']
# Touch caret's tip is below the bottom of the caret. Add 5px to y
# should be sufficient to locate it.
y = rect['bottom'] + 5 - element.location['y']
return x, y
def move_caret_by_offset(self, element, offset, backward=False):
'''Move caret in the element by offset.'''
cmd = self.get_js_selection_cmd(element) +\
'''sel.modify("move", arguments[1], "character");'''
direction = 'backward' if backward else 'forward'
for i in range(offset):
self.marionette.execute_script(
cmd, script_args=[element, direction])
def move_caret_to_front(self, element):
if self.is_input_or_textarea(element):
cmd = '''arguments[0].setSelectionRange(0, 0);'''
else:
cmd = '''var sel = window.getSelection();
sel.collapse(arguments[0].firstChild, 0);'''
self.marionette.execute_script(cmd, script_args=[element])
def move_caret_to_end(self, element):
if self.is_input_or_textarea(element):
cmd = '''var len = arguments[0].value.length;
arguments[0].setSelectionRange(len, len);'''
else:
cmd = '''var sel = window.getSelection();
sel.collapse(arguments[0].lastChild, arguments[0].lastChild.length);'''
self.marionette.execute_script(cmd, script_args=[element])
def get_content(self, element):
'''Return the content of the element.'''
if self.is_input_or_textarea(element):
return element.get_attribute('value')
else:
return element.text
def _test_move_caret_to_the_right_by_one_character(self, el, assertFunc):
content_to_add = '!'
target_content = self.get_content(el)
target_content = target_content[:1] + content_to_add + target_content[1:]
# Get touch caret (x, y) at position 1 and 2.
self.move_caret_to_front(el)
caret0_x, caret0_y = self.caret_location(el)
touch_caret0_x, touch_caret0_y = self.touch_caret_location(el)
self.move_caret_by_offset(el, 1)
touch_caret1_x, touch_caret1_y = self.touch_caret_location(el)
# Tap the front of the input to make touch caret appear.
el.tap(caret0_x, caret0_y)
# Move touch caret
self.actions.flick(el, touch_caret0_x, touch_caret0_y,
touch_caret1_x, touch_caret1_y).perform()
el.send_keys(content_to_add)
assertFunc(target_content, self.get_content(el))
def _test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self, el, assertFunc):
content_to_add = '!'
target_content = self.get_content(el) + content_to_add
# Tap the front of the input to make touch caret appear.
self.move_caret_to_front(el)
el.tap(*self.caret_location(el))
# Move touch caret to the bottom-right corner of the element.
src_x, src_y = self.touch_caret_location(el)
dest_x, dest_y = el.size['width'], el.size['height']
self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
el.send_keys(content_to_add)
assertFunc(target_content, self.get_content(el))
def _test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self, el, assertFunc):
content_to_add = '!'
target_content = content_to_add + self.get_content(el)
# Tap to make touch caret appear. Note: it's strange that when the caret
# is at the end, the rect of the caret in <textarea> cannot be obtained.
# A bug perhaps.
self.move_caret_to_end(el)
self.move_caret_by_offset(el, 1, backward=True)
el.tap(*self.caret_location(el))
# Move touch caret to the top-left corner of the input box.
src_x, src_y = self.touch_caret_location(el)
dest_x, dest_y = 0, 0
self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
el.send_keys(content_to_add)
assertFunc(target_content, self.get_content(el))
def _test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self, el, assertFunc):
content_to_add = '!'
non_target_content = content_to_add + self.get_content(el)
# Get touch caret timeout in millisecond, and convert it to second.
timeout = self.marionette.execute_script(
'return SpecialPowers.getIntPref("touchcaret.expiration.time");')
timeout /= 1000.0
# Tap to make touch caret appear. Note: it's strange that when the caret
# is at the end, the rect of the caret in <textarea> cannot be obtained.
# A bug perhaps.
self.move_caret_to_end(el)
self.move_caret_by_offset(el, 1, backward=True)
el.tap(*self.caret_location(el))
# Wait until touch caret disappears, then pretend to move it to the
# top-left corner of the input box.
src_x, src_y = self.touch_caret_location(el)
dest_x, dest_y = 0, 0
self.actions.wait(timeout).flick(el, src_x, src_y, dest_x, dest_y).perform()
el.send_keys(content_to_add)
assertFunc(non_target_content, self.get_content(el))
########################################################################
# <input> test cases with touch caret enabled
########################################################################
def test_input_move_caret_to_the_right_by_one_character(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_the_right_by_one_character(self._input, self.assertEqual)
def test_input_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._input, self.assertEqual)
def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._input, self.assertEqual)
def test_input_touch_caret_timeout(self):
self.openTestHtml(enabled=True)
self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._input, self.assertNotEqual)
########################################################################
# <input> test cases with touch caret disabled
########################################################################
def test_input_move_caret_to_the_right_by_one_character_disabled(self):
self.openTestHtml(enabled=False)
self._test_move_caret_to_the_right_by_one_character(self._input, self.assertNotEqual)
def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
self.openTestHtml(enabled=False)
self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._input, self.assertNotEqual)
########################################################################
# <textarea> test cases with touch caret enabled
########################################################################
def test_textarea_move_caret_to_the_right_by_one_character(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertEqual)
def test_textarea_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._textarea, self.assertEqual)
def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._textarea, self.assertEqual)
def test_textarea_touch_caret_timeout(self):
self.openTestHtml(enabled=True)
self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._textarea, self.assertNotEqual)
########################################################################
# <textarea> test cases with touch caret disabled
########################################################################
def test_textarea_move_caret_to_the_right_by_one_character_disabled(self):
self.openTestHtml(enabled=False)
self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertNotEqual)
def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
self.openTestHtml(enabled=False)
self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._textarea, self.assertNotEqual)
########################################################################
# <div> contenteditable test cases with touch caret enabled
########################################################################
def test_contenteditable_move_caret_to_the_right_by_one_character(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertEqual)
def test_contenteditable_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._contenteditable, self.assertEqual)
def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._contenteditable, self.assertEqual)
def test_contenteditable_touch_caret_timeout(self):
self.openTestHtml(enabled=True)
self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._contenteditable, self.assertNotEqual)
########################################################################
# <div> contenteditable test cases with touch caret disabled
########################################################################
def test_contenteditable_move_caret_to_the_right_by_one_character_disabled(self):
self.openTestHtml(enabled=False)
self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertNotEqual)
def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
self.openTestHtml(enabled=False)
self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._contenteditable, self.assertNotEqual)

View File

@ -28,12 +28,49 @@ this.EXPORTED_SYMBOLS = ["HawkClient"];
const {interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://services-common/hawkrequest.js");
Cu.import("resource://services-common/observers.js");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
// loglevel should be one of "Fatal", "Error", "Warn", "Info", "Config",
// "Debug", "Trace" or "All". If none is specified, "Error" will be used by
// default.
const PREF_LOG_LEVEL = "services.hawk.loglevel";
// A pref that can be set so "sensitive" information (eg, personally
// identifiable info, credentials, etc) will be logged.
const PREF_LOG_SENSITIVE_DETAILS = "services.hawk.log.sensitive";
XPCOMUtils.defineLazyGetter(this, "log", function() {
let log = Log.repository.getLogger("Hawk");
log.addAppender(new Log.DumpAppender());
log.level = Log.Level.Error;
try {
let level =
Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
&& Services.prefs.getCharPref(PREF_LOG_LEVEL);
log.level = Log.Level[level] || Log.Level.Error;
} catch (e) {
log.error(e);
}
return log;
});
// A boolean to indicate if personally identifiable information (or anything
// else sensitive, such as credentials) should be logged.
XPCOMUtils.defineLazyGetter(this, 'logPII', function() {
try {
return Services.prefs.getBoolPref(PREF_LOG_SENSITIVE_DETAILS);
} catch (_) {
return false;
}
});
/*
* A general purpose client for making HAWK authenticated requests to a single
@ -232,7 +269,7 @@ this.HawkClient.prototype = {
// Given an optional header value, notify that a backoff has been requested.
_maybeNotifyBackoff: function (response, headerName) {
if (!this.observerPrefix) {
if (!this.observerPrefix || !response.headers) {
return;
}
let headerVal = response.headers[headerName];
@ -243,8 +280,8 @@ this.HawkClient.prototype = {
try {
backoffInterval = parseInt(headerVal, 10);
} catch (ex) {
this._log.error("hawkclient response had invalid backoff value in '" +
headerName + "' header: " + headerVal);
log.error("hawkclient response had invalid backoff value in '" +
headerName + "' header: " + headerVal);
return;
}
Observers.notify(this.observerPrefix + ":backoff:interval", backoffInterval);
@ -254,4 +291,5 @@ this.HawkClient.prototype = {
newHAWKAuthenticatedRESTRequest: function(uri, credentials, extra) {
return new HAWKAuthenticatedRESTRequest(uri, credentials, extra);
},
}

View File

@ -0,0 +1,158 @@
/* 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/. */
// REST client for
// https://github.com/mozilla-services/msisdn-gateway/blob/master/API.md
"use strict";
this.EXPORTED_SYMBOLS = ["MobileIdentityClient"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://services-common/hawkclient.js");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Services.jsm");
this.MobileIdentityClient = function(aServerUrl) {
let serverUrl = aServerUrl || SERVER_URL;
let forceHttps = false;
try {
// TODO: Force https in production. Bug 1021595.
forceHttps = Services.prefs.getBoolPref(PREF_FORCE_HTTPS);
} catch(e) {
log.warn("Getting force HTTPS pref failed. If this was not intentional " +
"check that " + PREF_FORCE_HTTPS + " is defined");
}
log.debug("Force HTTPS " + forceHttps);
if (forceHttps && !/^https/.exec(serverUrl.toLowerCase())) {
throw new Error(ERROR_INTERNAL_HTTP_NOT_ALLOWED);
}
this.hawk = new HawkClient(SERVER_URL);
this.hawk.observerPrefix = "MobileId:hawk";
};
this.MobileIdentityClient.prototype = {
discover: function(aMsisdn, aMcc, aMnc, aRoaming) {
return this._request(DISCOVER, "POST", null, {
msisdn: aMsisdn || undefined,
mcc: aMcc,
mnc: aMnc,
roaming: aRoaming
});
},
register: function() {
return this._request(REGISTER, "POST", null, {});
},
smsMtVerify: function(aSessionToken, aMsisdn, aWantShortCode = false) {
let credentials = this._deriveHawkCredentials(aSessionToken);
return this._request(SMS_MT_VERIFY, "POST", credentials, {
msisdn: aMsisdn,
shortVerificationCode: aWantShortCode
});
},
verifyCode: function(aSessionToken, aVerificationCode) {
log.debug("verificationCode " + aVerificationCode);
let credentials = this._deriveHawkCredentials(aSessionToken);
return this._request(SMS_VERIFY_CODE, "POST", credentials, {
code: aVerificationCode
});
},
sign: function(aSessionToken, aDuration, aPublicKey) {
let credentials = this._deriveHawkCredentials(aSessionToken);
return this._request(SIGN, "POST", credentials, {
duration: aDuration,
publicKey: aPublicKey
});
},
unregister: function(aSessionToken) {
let credentials = this._deriveHawkCredentials(aSessionToken);
return this._request(UNREGISTER, "POST", credentials, {});
},
/**
* The MobileID server expects requests to certain endpoints to be
* authorized using Hawk.
*
* Hawk credentials are derived using shared secrets.
*
* @param tokenHex
* The current session token encoded in hex
* @param context
* A context for the credentials
* @param size
* The size in bytes of the expected derived buffer
* @return credentials
* Returns an object:
* {
* algorithm: sha256
* id: the Hawk id (from the first 32 bytes derived)
* key: the Hawk key (from bytes 32 to 64)
* }
*/
_deriveHawkCredentials: function(aSessionToken) {
let token = CommonUtils.hexToBytes(aSessionToken);
let out = CryptoUtils.hkdf(token, undefined,
CREDENTIALS_DERIVATION_INFO,
CREDENTIALS_DERIVATION_SIZE);
return {
algorithm: "sha256",
key: CommonUtils.bytesAsHex(out.slice(32, 64)),
id: CommonUtils.bytesAsHex(out.slice(0, 32))
};
},
/**
* A general method for sending raw API calls to the mobile id verification
* server.
* All request bodies and responses are JSON.
*
* @param path
* API endpoint path
* @param method
* The HTTP request method
* @param credentials
* Hawk credentials
* @param jsonPayload
* A JSON payload
* @return Promise
* Returns a promise that resolves to the JSON response of the API
* call, or is rejected with an error.
*/
_request: function(path, method, credentials, jsonPayload) {
let deferred = Promise.defer();
this.hawk.request(path, method, credentials, jsonPayload).then(
(responseText) => {
log.debug("MobileIdentityClient -> responseText " + responseText);
try {
let response = JSON.parse(responseText);
deferred.resolve(response);
} catch (err) {
deferred.reject({error: err});
}
},
(error) => {
log.error("MobileIdentityClient -> Error ${}", error);
deferred.reject(SERVER_ERRNO_TO_ERROR[error.errno] || ERROR_UNKNOWN);
}
);
return deferred.promise;
},
};

View File

@ -0,0 +1,143 @@
/* 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 { interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Log.jsm");
// loglevel should be one of "Fatal", "Error", "Warn", "Info", "Config",
// "Debug", "Trace" or "All". If none is specified, "Error" will be used by
// default.
const PREF_LOG_LEVEL = "services.mobileid.loglevel";
XPCOMUtils.defineLazyGetter(this, "log", function() {
let log = Log.repository.getLogger("MobileId");
log.addAppender(new Log.DumpAppender());
log.level = Log.Level.Error;
try {
let level =
Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
&& Services.prefs.getCharPref(PREF_LOG_LEVEL);
log.level = Log.Level[level] || Log.Level.Error;
} catch (e) {
log.error(e);
}
return log;
});
this.PREF_FORCE_HTTPS = "services.mobileid.forcehttps";
// Permission.
this.MOBILEID_PERM = "mobileid";
// IPC messages.
this.GET_ASSERTION_IPC_MSG = "MobileId:GetAssertion";
// Verification methods.
this.SMS_MT = "sms/mt";
this.SMS_MO_MT = "sms/momt";
// Server endpoints.
this.DISCOVER = "/discover";
this.REGISTER = "/register";
this.SMS_MT_VERIFY = "/" + this.SMS_MT + "/verify";
this.SMS_MO_MT_VERIFY = "/" + this.SMS_MO_MT + "/verify";
this.SMS_VERIFY_CODE = "/sms/verify_code";
this.SIGN = "/certificate/sign";
this.UNREGISTER = "/unregister";
// Server consts.
this.SERVER_URL = Services.prefs.getCharPref("services.mobileid.server.uri");
this.CREDENTIALS_DERIVATION_INFO = "identity.mozilla.com/picl/v1/sessionToken";
this.CREDENTIALS_DERIVATION_SIZE = 2 * 32;
this.SILENT_SMS_RECEIVED_TOPIC = "silent-sms-received";
this.ASSERTION_LIFETIME = 1000 * 60 * 5; // 5 minutes.
this.CERTIFICATE_LIFETIME = 1000 * 3600 * 6; // 6 hours.
this.KEY_LIFETIME = 1000 * 3600 * 12; // 12 hours.
this.VERIFICATIONCODE_TIMEOUT = 60000;
this.VERIFICATIONCODE_RETRIES = 3;
// Internal Errors.
this.ERROR_INTERNAL_CANNOT_CREATE_VERIFICATION_FLOW = "INTERNAL_CANNOT_CREATE_VERIFICATION_FLOW";
this.ERROR_INTERNAL_CANNOT_GENERATE_ASSERTION = "INTERNAL_CANNOT_GENERATE_ASSERTION";
this.ERROR_INTERNAL_CANNOT_VERIFY_SELECTION = "INTERNAL_CANNOT_VERIFY_SELECTION";
this.ERROR_INTERNAL_DB_ERROR = "INTERNAL_DB_ERROR";
this.ERROR_INTERNAL_HTTP_NOT_ALLOWED = "INTERNAL_HTTP_NOT_ALLOWED";
this.ERROR_INTERNAL_INVALID_CERTIFICATE = "INTERNAL_INVALID_CERTIFICATE";
this.ERROR_INTERNAL_INVALID_PROMPT_RESULT = "INTERNAL_INVALID_PROMPT_RESULT";
this.ERROR_INTERNAL_INVALID_USER_SELECTION = "INTERNAL_INVALID_USER_SELECTION";
this.ERROR_INTERNAL_INVALID_VERIFICATION_FLOW = "INTERNAL_INVALID_VERIFICATION_FLOW";
this.ERROR_INTERNAL_INVALID_VERIFICATION_RESULT = "INTERNAL_INVALID_VERIFICATION_RESULT";
this.ERROR_INTERNAL_UNEXPECTED = "INTERNAL_UNEXPECTED";
// Errors.
this.ERROR_ENDPOINT_NOT_SUPPORTED = "ENDPOINT_NOT_SUPPORTED";
this.ERROR_INVALID_ASSERTION = "INVALID_ASSERTION";
this.ERROR_INVALID_AUTH_TOKEN = "INVALID_AUTH_TOKEN";
this.ERROR_INVALID_BODY_JSON = "INVALID_BODY_JSON";
this.ERROR_INVALID_BODY_MISSING_PARAMS = "INVALID_BODY_MISSING_PARAMS";
this.ERROR_INVALID_BODY_PARAMS = "INVALID_BODY_PARAMS";
this.ERROR_INVALID_PHONE_NUMBER = "INVALID_PHONE_NUMBER";
this.ERROR_INVALID_PROMPT_RESULT = "INVALID_PROMPT_RESULT";
this.ERROR_INVALID_REQUEST_SIGNATURE = "INVALID_REQUEST_SIGNATURE";
this.ERROR_INVALID_VERIFICATION_CODE = "INVALID_VERIFICATION_CODE";
this.ERROR_MISSING_CONTENT_LENGTH_HEADER = "MISSING_CONTENT_LENGTH_HEADER";
this.ERROR_NO_RETRIES_LEFT = "NO_RETRIES_LEFT";
this.ERROR_OFFLINE = "OFFLINE";
this.ERROR_REQUEST_BODY_TOO_LARGE = "REQUEST_BODY_TOO_LARGE";
this.ERROR_SERVICE_TEMPORARILY_UNAVAILABLE = "SERVICE_TEMPORARILY_UNAVAILABLE";
this.ERROR_TOO_MANY_REQUESTS_MSISDN = "TOO_MANY_REQUESTS_MSISDN";
this.ERROR_TOO_MANY_REQUESTS_UNSPECIFIED = "TOO_MANY_REQUESTS_UNSPECIFIED";
this.ERROR_TOO_MANY_REQUESTS_VERIFICAITON_CODE = "TOO_MANY_REQUESTS_VERIFICATION_CODE";
this.ERROR_TOO_MANY_REQUESTS_VERIFICATION_METHOD = "TOO_MANY_REQUESTS_VERIFICATION_METHOD";
this.ERROR_UNKNOWN = "UNKNOWN";
this.ERROR_UNVERIFIED_ACCOUNT = "UNVERIFIED_ACCOUNT";
this.ERROR_VERIFICATION_CODE_TIMEOUT = "VERIFICATION_CODE_TIMEOUT";
// Server errno.
// From https://github.com/mozilla-services/msisdn-gateway/blob/master/API.md#response-format
this.ERRNO_UNVERIFIED_ACCOUNT = 104;
this.ERRNO_INVALID_VERIFICATION_CODE = 105;
this.ERRNO_INVALID_BODY_JSON = 106;
this.ERRNO_INVALID_BODY_INVALID_PARAMS = 107;
this.ERRNO_INVALID_BODY_MISSING_PARAMS = 108;
this.ERRNO_INVALID_REQUEST_SIGNATURE = 109;
this.ERRNO_INVALID_AUTH_TOKEN = 110;
this.ERRNO_ENDPOINT_NOT_SUPPORTED = 111;
this.ERRNO_MISSING_CONTENT_LENGTH_HEADER = 112;
this.ERRNO_REQUEST_BODY_TOO_LARGE = 113;
this.ERRNO_TOO_MANY_REQUESTS_VERIFICATION_CODE = 114;
this.ERRNO_TOO_MANY_REQUESTS_MSISDN = 115;
this.ERRNO_TOO_MANY_REQUESTS_VERIFICATION_METHOD = 116;
this.ERRNO_TOO_MANY_REQUESTS_UNSPECIFIED = 117;
this.ERRNO_SERVICE_TEMPORARILY_UNAVAILABLE = 201;
this.ERRNO_UNKNOWN_ERROR = 999;
// Error matching.
this.SERVER_ERRNO_TO_ERROR = {};
SERVER_ERRNO_TO_ERROR[ERRNO_UNVERIFIED_ACCOUNT] = ERROR_UNVERIFIED_ACCOUNT;
SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_VERIFICATION_CODE] = ERROR_INVALID_VERIFICATION_CODE;
SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_BODY_JSON] = ERROR_INVALID_BODY_JSON;
SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_BODY_INVALID_PARAMS] = ERROR_INVALID_BODY_PARAMS;
SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_BODY_MISSING_PARAMS] = ERROR_INVALID_BODY_MISSING_PARAMS;
SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_REQUEST_SIGNATURE] = ERROR_INVALID_REQUEST_SIGNATURE;
SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_AUTH_TOKEN] = ERROR_INVALID_AUTH_TOKEN;
SERVER_ERRNO_TO_ERROR[ERRNO_ENDPOINT_NOT_SUPPORTED] = ERROR_ENDPOINT_NOT_SUPPORTED;
SERVER_ERRNO_TO_ERROR[ERRNO_MISSING_CONTENT_LENGTH_HEADER] = ERROR_MISSING_CONTENT_LENGTH_HEADER;
SERVER_ERRNO_TO_ERROR[ERRNO_REQUEST_BODY_TOO_LARGE] = ERROR_REQUEST_BODY_TOO_LARGE;
SERVER_ERRNO_TO_ERROR[ERRNO_TOO_MANY_REQUESTS_VERIFICATION_CODE] = ERROR_TOO_MANY_REQUESTS_VERIFICAITON_CODE;
SERVER_ERRNO_TO_ERROR[ERRNO_TOO_MANY_REQUESTS_MSISDN] = ERROR_TOO_MANY_REQUESTS_MSISDN;;
SERVER_ERRNO_TO_ERROR[ERRNO_TOO_MANY_REQUESTS_VERIFICATION_METHOD] = ERROR_TOO_MANY_REQUESTS_VERIFICATION_METHOD;;
SERVER_ERRNO_TO_ERROR[ERRNO_TOO_MANY_REQUESTS_UNSPECIFIED] = ERROR_TOO_MANY_REQUESTS_UNSPECIFIED;;
SERVER_ERRNO_TO_ERROR[ERRNO_SERVICE_TEMPORARILY_UNAVAILABLE] = ERROR_SERVICE_TEMPORARILY_UNAVAILABLE;
SERVER_ERRNO_TO_ERROR[ERRNO_UNKNOWN_ERROR] = ERROR_UNKNOWN;
// Allow this file to be imported via Components.utils.import().
this.EXPORTED_SYMBOLS = Object.keys(this);

View File

@ -0,0 +1,173 @@
/* 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";
this.EXPORTED_SYMBOLS = ["MobileIdentityCredentialsStore"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
const CREDENTIALS_DB_NAME = "mobile-id-credentials";
const CREDENTIALS_DB_VERSION = 1;
const CREDENTIALS_STORE_NAME = "credentials-store";
this.MobileIdentityCredentialsStore = function() {
};
this.MobileIdentityCredentialsStore.prototype = {
__proto__: IndexedDBHelper.prototype,
init: function() {
log.debug("MobileIdentityCredentialsStore init");
this.initDBHelper(CREDENTIALS_DB_NAME,
CREDENTIALS_DB_VERSION,
[CREDENTIALS_STORE_NAME]);
},
upgradeSchema: function(aTransaction, aDb, aOldVersion, aNewVersion) {
log.debug("upgradeSchema");
/**
* We will be storing objects like:
* {
* msisdn: <string> (key),
* iccId: <string> (index),
* origin: <array> (index),
* msisdnSessionToken: <string>
* }
*/
let objectStore = aDb.createObjectStore(CREDENTIALS_STORE_NAME, {
keyPath: "msisdn"
});
objectStore.createIndex("iccId", "iccId", { unique: true });
objectStore.createIndex("origin", "origin", { unique: true, multiEntry: true });
},
add: function(aIccId, aMsisdn, aOrigin, aSessionToken) {
log.debug("put " + aIccId + ", " + aMsisdn + ", " + aOrigin + ", " +
aSessionToken);
if (!aOrigin || !aSessionToken) {
return Promise.reject(ERROR_INTERNAL_DB_ERROR);
}
let deferred = Promise.defer();
// We first try get an existing record for the given MSISDN.
this.newTxn(
"readwrite",
CREDENTIALS_STORE_NAME,
(aTxn, aStore) => {
let range = IDBKeyRange.only(aMsisdn);
let cursorReq = aStore.openCursor(range);
cursorReq.onsuccess = function(aEvent) {
let cursor = aEvent.target.result;
let record;
// If we already have a record of this MSISDN, we add the origin to
// the list of allowed origins.
if (cursor && cursor.value) {
record = cursor.value;
if (record.origin.indexOf(aOrigin) == -1) {
record.origin.push(aOrigin);
}
cursor.update(record);
} else {
// Otherwise, we store a new record.
record = {
iccId: aIccId,
msisdn: aMsisdn,
origin: [aOrigin],
sessionToken: aSessionToken
};
aStore.add(record);
}
deferred.resolve();
};
cursorReq.onerror = function(aEvent) {
log.error(aEvent.target.error);
deferred.reject(ERROR_INTERNAL_DB_ERROR);
};
}, null, deferred.reject);
return deferred.promise;
},
getByMsisdn: function(aMsisdn) {
log.debug("getByMsisdn " + aMsisdn);
if (!aMsisdn) {
return Promise.resolve(null);
}
let deferred = Promise.defer();
this.newTxn(
"readonly",
CREDENTIALS_STORE_NAME,
(aTxn, aStore) => {
aStore.get(aMsisdn).onsuccess = function(aEvent) {
aTxn.result = aEvent.target.result;
};
},
function(result) {
deferred.resolve(result);
},
deferred.reject
);
return deferred.promise;
},
getByIndex: function(aIndex, aValue) {
log.debug("getByIndex " + aIndex + ", " + aValue);
if (!aValue || !aIndex) {
return Promise.resolve(null);
}
let deferred = Promise.defer();
this.newTxn(
"readonly",
CREDENTIALS_STORE_NAME,
(aTxn, aStore) => {
let index = aStore.index(aIndex);
index.get(aValue).onsuccess = function(aEvent) {
aTxn.result = aEvent.target.result;
};
},
function(result) {
deferred.resolve(result);
},
deferred.reject
);
return deferred.promise;
},
getByOrigin: function(aOrigin) {
return this.getByIndex("origin", aOrigin);
},
getByIccId: function(aIccId) {
return this.getByIndex("iccId", aIccId);
},
delete: function(aMsisdn) {
log.debug("delete " + aMsisdn);
if (!aMsisdn) {
return Promise.resolve();
}
let deferred = Promise.defer();
this.newTxn(
"readwrite",
CREDENTIALS_STORE_NAME,
(aTxn, aStore) => {
aStore.delete(aMsisdn);
},
deferred.resolve,
deferred.reject
);
return deferred.promise;
}
};

View File

@ -0,0 +1,824 @@
/* 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";
this.EXPORTED_SYMBOLS = [];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
Cu.import("resource://gre/modules/MobileIdentityUIGlueCommon.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MobileIdentityCredentialsStore",
"resource://gre/modules/MobileIdentityCredentialsStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MobileIdentityClient",
"resource://gre/modules/MobileIdentityClient.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MobileIdentitySmsMtVerificationFlow",
"resource://gre/modules/MobileIdentitySmsMtVerificationFlow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MobileIdentitySmsMoMtVerificationFlow",
"resource://gre/modules/MobileIdentitySmsMoMtVerificationFlow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PhoneNumberUtils",
"resource://gre/modules/PhoneNumberUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto",
"resource://gre/modules/identity/jwcrypto.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageListenerManager");
XPCOMUtils.defineLazyServiceGetter(this, "permissionManager",
"@mozilla.org/permissionmanager;1",
"nsIPermissionManager");
XPCOMUtils.defineLazyServiceGetter(this, "securityManager",
"@mozilla.org/scriptsecuritymanager;1",
"nsIScriptSecurityManager");
XPCOMUtils.defineLazyServiceGetter(this, "appsService",
"@mozilla.org/AppsService;1",
"nsIAppsService");
#ifdef MOZ_B2G_RIL
XPCOMUtils.defineLazyServiceGetter(this, "gRil",
"@mozilla.org/ril;1",
"nsIRadioInterfaceLayer");
XPCOMUtils.defineLazyServiceGetter(this, "iccProvider",
"@mozilla.org/ril/content-helper;1",
"nsIIccProvider");
#endif
let MobileIdentityManager = {
init: function() {
log.debug("MobileIdentityManager init");
Services.obs.addObserver(this, "xpcom-shutdown", false);
ppmm.addMessageListener(GET_ASSERTION_IPC_MSG, this);
this.messageManagers = {};
// TODO: Store keyPairs and certificates in disk. Bug 1021605.
this.keyPairs = {};
this.certificates = {};
},
receiveMessage: function(aMessage) {
log.debug("Received " + aMessage.name);
if (aMessage.name !== GET_ASSERTION_IPC_MSG) {
return;
}
let msg = aMessage.json;
// We save the message target message manager so we can later dispatch
// back messages without broadcasting to all child processes.
let promiseId = msg.promiseId;
this.messageManagers[promiseId] = aMessage.target;
this.getMobileIdAssertion(aMessage.principal, promiseId,
msg.msisdn, msg.prompt);
},
observe: function(subject, topic, data) {
if (topic != "xpcom-shutdown") {
return;
}
ppmm.removeMessageListener(GET_ASSERTION_IPC_MSG, this);
Services.obs.removeObserver(this, "xpcom-shutdown");
this.messageManagers = null;
},
/*********************************************************
* Getters
********************************************************/
get iccInfo() {
#ifdef MOZ_B2G_RIL
if (this._iccInfo) {
return this._iccInfo;
}
this._iccInfo = [];
for (let i = 0; i < gRil.numRadioInterfaces; i++) {
let rilContext = gRil.getRadioInterface(i).rilContext;
if (!rilContext) {
log.warn("Tried to get the RIL context for an invalid service ID " + i);
continue;
}
let info = rilContext.iccInfo;
if (!info) {
log.warn("No ICC info");
continue;
}
let operator = null;
if (rilContext.voice.network &&
rilContext.voice.network.shortName &&
rilContext.voice.network.shortName.length) {
operator = rilContext.voice.network.shortName;
} else if (rilContext.data.network &&
rilContext.data.network.shortName &&
rilContext.data.network.shortName.length) {
operator = rilContext.data.network.shortName;
}
this._iccInfo.push({
iccId: info.iccid,
mcc: info.mcc,
mnc: info.mnc,
// GSM SIMs may have MSISDN while CDMA SIMs may have MDN
msisdn: info.msisdn || info.mdn || null,
operator: operator,
serviceId: i,
roaming: rilContext.voice.roaming
});
}
return this._iccInfo;
#endif
return null;
},
get credStore() {
if (!this._credStore) {
this._credStore = new MobileIdentityCredentialsStore();
this._credStore.init();
}
return this._credStore;
},
get ui() {
if (!this._ui) {
this._ui = Cc["@mozilla.org/services/mobileid-ui-glue;1"]
.createInstance(Ci.nsIMobileIdentityUIGlue);
this._ui.oncancel = this.onUICancel.bind(this);
this._ui.onresendcode = this.onUIResendCode.bind(this);
}
return this._ui;
},
get client() {
if (!this._client) {
this._client = new MobileIdentityClient();
}
return this._client;
},
get isMultiSim() {
return this.iccInfo && this.iccInfo.length > 1;
},
getVerificationOptionsForIcc: function(aServiceId) {
log.debug("getVerificationOptionsForIcc " + aServiceId);
log.debug("iccInfo ${}", this.iccInfo[aServiceId]);
// First of all we need to check if we already have existing credentials
// for the given SIM information (ICC id or MSISDN). If we have no valid
// credentials, we have to check with the server which options to do we
// have to verify the associated phone number.
return this.credStore.getByIccId(this.iccInfo[aServiceId].iccId)
.then(
(creds) => {
if (creds) {
this.iccInfo[aServiceId].credentials = creds;
return;
}
return this.credStore.getByMsisdn(this.iccInfo[aServiceId].msisdn);
}
)
.then(
(creds) => {
if (creds) {
this.iccInfo[aServiceId].credentials = creds;
return;
}
// We have no credentials for this SIM, so we need to ask the server
// which options do we have to verify the phone number.
// But we need to be online...
if (Services.io.offline) {
return Promise.reject(ERROR_OFFLINE);
}
return this.client.discover(this.iccInfo[aServiceId].msisdn,
this.iccInfo[aServiceId].mcc,
this.iccInfo[aServiceId].mnc,
this.iccInfo[aServiceId].roaming);
}
)
.then(
(result) => {
log.debug("Discover result ${}", result);
if (!result || !result.verificationMethods) {
return;
}
this.iccInfo[aServiceId].verificationMethods = result.verificationMethods;
this.iccInfo[aServiceId].verificationDetails = result.verificationDetails;
this.iccInfo[aServiceId].canDoSilentVerification =
(result.verificationMethods.indexOf(SMS_MO_MT) != -1);
return;
}
);
},
getVerificationOptions: function() {
log.debug("getVerificationOptions");
// We try to get if we already have credentials for any of the inserted
// SIM cards if any is available and we try to get the possible
// verification mechanisms for these SIM cards.
// All this information will be stored in iccInfo.
if (!this.iccInfo || !this.iccInfo.length) {
return Promise.resolve();
}
let promises = [];
for (let i = 0; i < this.iccInfo.length; i++) {
promises.push(this.getVerificationOptionsForIcc(i));
}
return Promise.all(promises);
},
getKeyPair: function(aSessionToken) {
if (this.keyPairs[aSessionToken] &&
this.keyPairs[aSessionToken].validUntil > this.client.hawk.now()) {
return Promise.resolve(this.keyPairs[aSessionToken].keyPair);
}
let validUntil = this.client.hawk.now() + KEY_LIFETIME;
let deferred = Promise.defer();
jwcrypto.generateKeyPair("DS160", (error, kp) => {
if (error) {
return deferred.reject(error);
}
this.keyPairs[aSessionToken] = {
keyPair: kp,
validUntil: validUntil
};
delete this.certificates[aSessionToken];
deferred.resolve(kp);
});
return deferred.promise;
},
getCertificate: function(aSessionToken, aPublicKey) {
if (this.certificates[aSessionToken] &&
this.certificates[aSessionToken].validUntil > this.client.hawk.now()) {
return Promise.resolve(this.certificates[aSessionToken].cert);
}
if (Services.io.offline) {
return Promise.reject(ERROR_OFFLINE);
}
let validUntil = this.client.hawk.now() + KEY_LIFETIME;
let deferred = Promise.defer();
this.client.sign(aSessionToken, CERTIFICATE_LIFETIME,
aPublicKey)
.then(
(signedCert) => {
this.certificates[aSessionToken] = {
cert: signedCert.cert,
validUntil: validUntil
};
deferred.resolve(signedCert.cert);
},
deferred.reject
);
return deferred.promise;
},
/*********************************************************
* UI callbacks
********************************************************/
onUICancel: function() {
log.debug("UI cancel");
if (this.activeVerificationFlow) {
this.activeVerificationFlow.cleanup(true);
}
},
onUIResendCode: function() {
log.debug("UI resend code");
if (!this.activeVerificationFlow) {
return;
}
this.doVerification();
},
/*********************************************************
* Permissions helpers
********************************************************/
hasPermission: function(aPrincipal) {
let permission = permissionManager.testPermissionFromPrincipal(aPrincipal,
MOBILEID_PERM);
return permission == Ci.nsIPermissionManager.ALLOW_ACTION;
},
addPermission: function(aPrincipal) {
permissionManager.addFromPrincipal(aPrincipal, MOBILEID_PERM,
Ci.nsIPermissionManager.ALLOW_ACTION);
},
/*********************************************************
* Phone number verification
********************************************************/
rejectVerification: function(aReason) {
if (!this.activeVerificationDeferred) {
return;
}
this.activeVerificationDeferred.reject(aReason);
this.activeVerificationDeferred = null;
this.cleanupVerification(true);
},
resolveVerification: function(aResult) {
if (!this.activeVerificationDeferred) {
return;
}
this.activeVerificationDeferred.resolve(aResult);
this.activeVerificationDeferred = null;
this.cleanupVerification();
},
cleanupVerification: function() {
if (!this.activeVerificationFlow) {
return;
}
this.activeVerificationFlow.cleanup();
this.activeVerificationFlow = null;
},
doVerification: function() {
this.activeVerificationFlow.doVerification()
.then(
(verificationResult) => {
log.debug("onVerificationResult ");
if (!verificationResult || !verificationResult.sessionToken ||
!verificationResult.msisdn) {
return this.rejectVerification(
ERROR_INTERNAL_INVALID_VERIFICATION_RESULT
);
}
this.resolveVerification(verificationResult);
}
)
.then(
null,
reason => {
// Verification timeout.
log.warn("doVerification " + reason);
}
);
},
_verificationFlow: function(aToVerify, aOrigin) {
log.debug("toVerify ${}", aToVerify);
// We create the corresponding verification flow and save its instance
// in case that we need to cancel it or retrigger it because the user
// requested its cancelation or a resend of the verification code.
if (aToVerify.verificationMethod.indexOf(SMS_MT) != -1 &&
aToVerify.msisdn &&
aToVerify.verificationDetails &&
aToVerify.verificationDetails.mtSender) {
this.activeVerificationFlow = new MobileIdentitySmsMtVerificationFlow(
aOrigin,
aToVerify.msisdn,
aToVerify.iccId,
aToVerify.serviceId === undefined, // external: the phone number does
// not seem to belong to any of the
// device SIM cards.
aToVerify.verificationDetails.mtSender,
this.ui,
this.client
);
#ifdef MOZ_B2G_RIL
} else if (aToVerify.verificationMethod.indexOf(SMS_MO_MT) != -1 &&
aToVerify.serviceId &&
aToVerify.verificationDetails &&
aToVerify.verificationDetails.moVerifier &&
aToVerify.verificationDetails.mtSender) {
this.activeVerificationFlow = new MobileIdentitySmsMoMtVerificationFlow(
aOrigin,
aToVerify.serviceId,
aToVerify.iccId,
aToVerify.verificationDetails.mtSender,
aToVerify.verificationDetails.moVerifier,
this.ui,
this.client
);
#endif
} else {
return Promise.reject(ERROR_INTERNAL_CANNOT_VERIFY_SELECTION);
}
if (!this.activeVerificationFlow) {
return Promise.reject(ERROR_INTERNAL_CANNOT_CREATE_VERIFICATION_FLOW);
}
this.activeVerificationDeferred = Promise.defer();
this.doVerification();
return this.activeVerificationDeferred.promise;
},
verificationFlow: function(aUserSelection, aOrigin) {
log.debug("verificationFlow ${}", aUserSelection);
if (!aUserSelection) {
return Promise.reject(ERROR_INTERNAL_INVALID_USER_SELECTION);
}
let serviceId = aUserSelection.serviceId || undefined;
// We check if the user entered phone number corresponds with any of the
// inserted SIMs known phone numbers.
if (aUserSelection.msisdn && this.iccInfo) {
for (let i = 0; i < this.iccInfo.length; i++) {
if (aUserSelection.msisdn == this.iccInfo[i].msisdn) {
serviceId = i;
break;
}
}
}
let toVerify = {};
if (serviceId !== undefined) {
log.debug("iccInfo ${}", this.iccInfo[serviceId]);
toVerify.serviceId = serviceId;
toVerify.iccId = this.iccInfo[serviceId].iccId;
toVerify.msisdn = this.iccInfo[serviceId].msisdn;
toVerify.verificationMethod =
this.iccInfo[serviceId].verificationMethods[0];
toVerify.verificationDetails =
this.iccInfo[serviceId].verificationDetails[toVerify.verificationMethod];
return this._verificationFlow(toVerify, aOrigin);
} else {
toVerify.msisdn = aUserSelection.msisdn;
return this.client.discover(aUserSelection.msisdn,
aUserSelection.mcc)
.then(
(discoverResult) => {
if (!discoverResult || !discoverResult.verificationMethods) {
return Promise.reject(ERROR_INTERNAL_UNEXPECTED);
}
log.debug("discoverResult ${}", discoverResult);
toVerify.verificationMethod = discoverResult.verificationMethods[0];
toVerify.verificationDetails =
discoverResult.verificationDetails[toVerify.verificationMethod];
return this._verificationFlow(toVerify, aOrigin);
}
);
}
},
/*********************************************************
* UI prompt functions.
********************************************************/
// The phone number prompt will be used to confirm that the user wants to
// verify and share a known phone number and to allow her to introduce an
// external phone or to select between phone numbers or SIM cards (if the
// phones are not known) in a multi-SIM scenario.
// This prompt will be considered as the permission prompt and its choice
// will be remembered per origin by default.
prompt: function prompt(aPrincipal, aManifestURL, aPhoneInfo) {
log.debug("prompt " + aPrincipal + ", " + aManifestURL + ", " +
aPhoneInfo);
let phoneInfoArray = [];
if (aPhoneInfo) {
phoneInfoArray.push(aPhoneInfo);
}
if (this.iccInfo) {
for (let i = 0; i < this.iccInfo.length; i++) {
// If we don't know the msisdn, there is no previous credentials and
// a silent verification is not possible, we don't allow the user to
// choose this option.
if (!this.iccInfo[i].msisdn && !this.iccInfo[i].credentials &&
!this.iccInfo[i].canDoSilentVerification) {
continue;
}
let phoneInfo = new MobileIdentityUIGluePhoneInfo(
this.iccInfo[i].msisdn,
this.iccInfo[i].operator,
i, // service ID
false, // external
false // primary
);
phoneInfoArray.push(phoneInfo);
}
}
return this.ui.startFlow(aManifestURL, phoneInfoArray)
.then(
(result) => {
if (!result ||
(!result.phoneNumber && !result.serviceId)) {
return Promise.reject(ERROR_INTERNAL_INVALID_PROMPT_RESULT);
}
let msisdn;
let mcc;
// If the user selected one of the existing SIM cards we have to check
// that we either have the MSISDN for that SIM or we can do a silent
// verification that does not require us to have the MSISDN in advance.
if (result.serviceId) {
let icc = this.iccInfo[result.serviceId];
log.debug("icc ${}", icc);
if (!icc || !icc.msisdn && !icc.canDoSilentVerification) {
return Promise.reject(ERROR_INTERNAL_CANNOT_VERIFY_SELECTION);
}
msisdn = icc.msisdn;
mcc = icc.mcc;
} else {
msisdn = result.prefix ? result.prefix + result.phoneNumber
: result.phoneNumber;
mcc = result.mcc;
}
// We need to check that the selected phone number is valid and
// if it is not notify the UI about the error and allow the user to
// retry.
if (msisdn && mcc &&
!PhoneNumberUtils.parseWithMCC(msisdn, mcc)) {
this.ui.error(ERROR_INVALID_PHONE_NUMBER);
return this.prompt(aPrincipal, aManifestURL, aPhoneInfo);
}
log.debug("Selected msisdn (if any): " + msisdn + " - " + mcc);
// The user gave permission for the requester origin, so we store it.
this.addPermission(aPrincipal);
return {
msisdn: msisdn,
mcc: mcc,
serviceId: result.serviceId
};
}
);
},
promptAndVerify: function(aPrincipal, aManifestURL, aCreds) {
log.debug("promptAndVerify " + aPrincipal + ", " + aManifestURL +
", ${}", aCreds);
let userSelection;
if (Services.io.offline) {
return Promise.reject(ERROR_OFFLINE);
}
// Before prompting the user we need to check with the server the
// phone number verification methods that are possible with the
// SIMs inserted in the device.
return this.getVerificationOptions()
.then(
() => {
// If we have an exisiting credentials, we add its associated
// phone number information to the list of choices to present
// to the user within the selection prompt.
let phoneInfo;
if (aCreds) {
phoneInfo = new MobileIdentityUIGluePhoneInfo(
aCreds.msisdn,
null, // operator
null, // service ID
!!aCreds.iccId, // external
true // primary
);
}
return this.prompt(aPrincipal, aManifestURL, phoneInfo);
}
)
.then(
(promptResult) => {
log.debug("promptResult ${}", promptResult);
// If we had credentials and the user didn't change her
// selection we return them. Otherwise, we need to verify
// the new number.
if (promptResult.msisdn && aCreds &&
promptResult.msisdn == aCreds.msisdn) {
return aCreds;
}
// We might already have credentials for the user selected icc. In
// that case, we update the credentials store with the new origin and
// return the credentials.
if (promptResult.serviceId) {
let creds = this.iccInfo[promptResult.serviceId].credentials;
if (creds) {
this.credStore.add(creds.iccId, creds.msisdn, aPrincipal.origin,
creds.sessionToken);
return creds;
}
}
// Or we might already have credentials for the selected phone
// number and so we do the same: update the credentials store with the
// new origin and return the credentials.
return this.credStore.getByMsisdn(promptResult.msisdn)
.then(
(creds) => {
if (creds) {
this.credStore.add(creds.iccId, creds.msisdn, aPrincipal.origin,
creds.sessionToken);
return creds;
}
// Otherwise, we need to verify the new number selected by the
// user.
return this.verificationFlow(promptResult, aPrincipal.origin);
}
);
}
);
},
/*********************************************************
* Assertion generation
********************************************************/
generateAssertion: function(aCredentials, aOrigin) {
if (!aCredentials.sessionToken) {
return Promise.reject(ERROR_INTERNAL_INVALID_TOKEN);
}
let deferred = Promise.defer();
this.getKeyPair(aCredentials.sessionToken)
.then(
(keyPair) => {
log.debug("keyPair " + keyPair.serializedPublicKey);
let options = {
duration: ASSERTION_LIFETIME,
now: this.client.hawk.now(),
localtimeOffsetMsec: this.client.hawk.localtimeOffsetMsec
};
this.getCertificate(aCredentials.sessionToken,
keyPair.serializedPublicKey)
.then(
(signedCert) => {
log.debug("generateAssertion " + signedCert);
jwcrypto.generateAssertion(signedCert, keyPair,
aOrigin, options,
(error, assertion) => {
if (error) {
log.error("Error generating assertion " + err);
deferred.reject(error);
return;
}
this.credStore.add(aCredentials.iccId,
aCredentials.msisdn,
aOrigin,
aCredentials.sessionToken)
.then(
() => {
deferred.resolve(assertion);
}
);
});
}, deferred.reject
);
}
);
return deferred.promise;
},
getMobileIdAssertion: function(aPrincipal, aPromiseId) {
log.debug("getMobileIdAssertion ${}", aPrincipal);
let uri = Services.io.newURI(aPrincipal.origin, null, null);
let principal = securityManager.getAppCodebasePrincipal(
uri, aPrincipal.appid, aPrincipal.isInBrowserElement);
let manifestURL = appsService.getManifestURLByLocalId(aPrincipal.appId);
// First of all we look if we already have credentials for this origin.
// If we don't have credentials it means that it is the first time that
// the caller requested an assertion.
return this.credStore.getByOrigin(aPrincipal.origin)
.then(
(creds) => {
log.debug("creds ${creds} - ${origin}", { creds: creds,
origin: aPrincipal.origin});
if (!creds || !creds.sessionToken) {
log.debug("No credentials");
return;
}
// It is possible that the ICC associated with the stored
// credentials is not present in the device anymore, so we ask the
// user if she still wants to use it anyway or if she prefers to use
// another phone number.
// If the credentials are associated with an external SIM or there is
// no SIM in the device, we just return the credentials.
if (this.iccInfo && creds.iccId) {
for (let i = 0; i < this.iccInfo.length; i++) {
if (this.iccInfo[i].iccId == creds.iccId) {
return creds;
}
}
// At this point we know that the SIM associated with the credentials
// is not present in the device any more, so we need to ask the user
// what to do.
return this.promptAndVerify(principal, manifestURL, creds);
}
return creds;
}
)
.then(
(creds) => {
// Even if we have credentails it is possible that the user has
// removed the permission to share its mobile id with this origin, so
// we check the permission and if it is not granted, we ask the user
// before generating and sharing the assertion.
// If we've just prompted the user in the previous step, the permission
// is already granted and stored so we just progress the credentials.
if (creds) {
if (this.hasPermission(principal)) {
return creds;
}
return this.promptAndVerify(principal, manifestURL, creds);
}
return this.promptAndVerify(principal, manifestURL);
}
)
.then(
(creds) => {
if (creds) {
return this.generateAssertion(creds, principal.origin);
}
return Promise.reject(ERROR_INTERNAL_CANNOT_GENERATE_ASSERTION);
}
)
.then(
(assertion) => {
if (!assertion) {
return Promise.reject(ERROR_INTERNAL_CANNOT_GENERATE_ASSERTION);
}
// Get the verified phone number from the assertion.
let segments = assertion.split(".");
if (!segments) {
return Promise.reject(ERROR_INVALID_ASSERTION);
}
// We need to translate the base64 alphabet used in JWT to our base64
// alphabet before calling atob.
let decodedPayload = JSON.parse(atob(segments[1].replace(/-/g, '+')
.replace(/_/g, '/')));
if (!decodedPayload || !decodedPayload.verifiedMSISDN) {
return Promise.reject(ERROR_INVALID_ASSERTION);
}
this.ui.verified(decodedPayload.verifiedMSISDN);
let mm = this.messageManagers[aPromiseId];
mm.sendAsyncMessage("MobileId:GetAssertion:Return:OK", {
promiseId: aPromiseId,
result: assertion
});
}
)
.then(
null,
(error) => {
log.error("getMobileIdAssertion rejected with " + error);
// Notify the error to the UI.
this.ui.error(error);
let mm = this.messageManagers[aPromiseId];
mm.sendAsyncMessage("MobileId:GetAssertion:Return:KO", {
promiseId: aPromiseId,
error: error
});
}
);
},
};
MobileIdentityManager.init();

View File

@ -0,0 +1,89 @@
/* 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";
this.EXPORTED_SYMBOLS = ["MobileIdentitySmsMoMtVerificationFlow"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
Cu.import("resource://gre/modules/MobileIdentitySmsVerificationFlow.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "smsService",
"@mozilla.org/sms/smsservice;1",
"nsISmsService");
// In order to send messages through nsISmsService, we need to implement
// nsIMobileMessageCallback, as the WebSMS API implementation is not usable
// from JS.
function SilentSmsRequest(aDeferred) {
this.deferred = aDeferred;
}
SilentSmsRequest.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileMessageCallback]),
classID: Components.ID("{ff46f1a8-e040-4ff4-98a7-d5a5b86a2c3e}"),
notifyMessageSent: function notifyMessageSent(aMessage) {
log.debug("Silent message successfully sent");
this.deferred.resolve(aMessage);
},
notifySendMessageFailed: function notifySendMessageFailed(aError) {
log.error("Error sending silent message " + aError);
this.deferred.reject(aError);
}
};
this.MobileIdentitySmsMoMtVerificationFlow = function(aOrigin,
aServiceId,
aIccId,
aMtSender,
aMoVerifier,
aUI,
aClient) {
log.debug("MobileIdentitySmsMoMtVerificationFlow");
MobileIdentitySmsVerificationFlow.call(this,
aOrigin,
null, //msisdn
aIccId,
aServiceId,
false, // external
aMtSender,
aMoVerifier,
aUI,
aClient,
this.smsVerifyStrategy);
};
this.MobileIdentitySmsMoMtVerificationFlow.prototype = {
__proto__: MobileIdentitySmsVerificationFlow.prototype,
smsVerifyStrategy: function() {
// In the MO+MT flow we need to send an SMS to the given moVerifier number
// so the server can find out our phone number to send an SMS back with a
// verification code.
let deferred = Promise.defer();
let silentSmsRequest = new SilentSmsRequest(deferred);
// The MO SMS body that the server expects contains the API endpoint for
// the MO verify request and the HAWK ID parameter derived via HKDF from
// the session token. These parameters should go unnamed and space limited.
let body = SMS_MO_MT_VERIFY + " " +
this.client._deriveHawkCredentials(this.sessionToken).id;
smsService.send(this.verificationOptions.serviceId,
this.verificationOptions.moVerifier,
body,
true, // silent
silentSmsRequest);
log.debug("Sending " + body + " to " + this.verificationOptions.moVerifier);
return deferred.promise;
}
};

View File

@ -0,0 +1,49 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["MobileIdentitySmsMtVerificationFlow"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
Cu.import("resource://gre/modules/MobileIdentitySmsVerificationFlow.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
this.MobileIdentitySmsMtVerificationFlow = function(aOrigin,
aMsisdn,
aIccId,
aExternal,
aMtSender,
aUI,
aClient) {
log.debug("MobileIdentitySmsVerificationFlow " + aMsisdn + ", external: " +
aExternal);
MobileIdentitySmsVerificationFlow.call(this,
aOrigin,
aMsisdn,
aIccId,
null, // service ID
aExternal,
aMtSender,
null, // moVerifier
aUI,
aClient,
this.smsVerifyStrategy);
};
this.MobileIdentitySmsMtVerificationFlow.prototype = {
__proto__: MobileIdentitySmsVerificationFlow.prototype,
smsVerifyStrategy: function() {
return this.client.smsMtVerify(this.sessionToken,
this.verificationOptions.msisdn,
this.verificationOptions.external);
}
};

View File

@ -0,0 +1,120 @@
/* 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";
this.EXPORTED_SYMBOLS = ["MobileIdentitySmsVerificationFlow"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
Cu.import("resource://gre/modules/MobileIdentityVerificationFlow.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
#ifdef MOZ_B2G_RIL
XPCOMUtils.defineLazyServiceGetter(this, "smsService",
"@mozilla.org/sms/smsservice;1",
"nsISmsService");
#endif
this.MobileIdentitySmsVerificationFlow = function(aOrigin,
aMsisdn,
aIccId,
aServiceId,
aExternal,
aMtSender,
aMoVerifier,
aUI,
aClient,
aVerifyStrategy) {
// SMS MT or SMS MO+MT specific verify strategy.
this.smsVerifyStrategy = aVerifyStrategy;
MobileIdentityVerificationFlow.call(this, {
origin: aOrigin,
msisdn: aMsisdn,
iccId: aIccId,
serviceId: aServiceId,
external: aExternal,
mtSender: aMtSender,
moVerifier: aMoVerifier
}, aUI, aClient, this._verifyStrategy, this._cleanupStrategy);
};
this.MobileIdentitySmsVerificationFlow.prototype = {
__proto__: MobileIdentityVerificationFlow.prototype,
observedSilentNumber: null,
onSilentSms: null,
_verifyStrategy: function() {
if (!this.smsVerifyStrategy) {
return Promise.reject(ERROR_INTERNAL_UNEXPECTED);
}
// Even if the user selection is given to us as a possible external phone
// number, it is also possible that the phone number introduced by the
// user belongs to one of the SIMs inserted in the device which MSISDN
// is unknown for us, so we always observe for incoming messages coming
// from the given mtSender.
#ifdef MOZ_B2G_RIL
this.observedSilentNumber = this.verificationOptions.mtSender;
try {
smsService.addSilentNumber(this.observedSilentNumber);
} catch (e) {
log.warn("We are already listening for that number");
}
this.onSilentSms = (function(aSubject, aTopic, aData) {
log.debug("Got silent message " + aSubject.sender + " - " + aSubject.body);
// We might have observed a notification of an incoming silent message
// for other number. In that case, we just bail out.
if (aSubject.sender != this.observedSilentNumber) {
return;
}
// We got the SMS containing the verification code.
// If the phone number we are trying to verify is or can be an external
// phone number (meaning that it doesn't belong to any of the inserted
// SIMs) we will be receiving an human readable SMS containing a short
// verification code. In this case we need to parse the SMS body to
// extract the verification code.
// Otherwise, we just use the whole SMS body as it should contain a long
// verification code.
let verificationCode = aSubject.body;
if (this.verificationOptions.external) {
// We just take the numerical characters from the body.
verificationCode = aSubject.body.replace(/[^0-9]/g,'');
}
log.debug("Verification code: " + verificationCode);
this.verificationCodeDeferred.resolve(verificationCode);
}).bind(this);
Services.obs.addObserver(this.onSilentSms,
SILENT_SMS_RECEIVED_TOPIC,
false);
log.debug("Observing messages from " + this.observedSilentNumber);
#endif
return this.smsVerifyStrategy();
},
_cleanupStrategy: function() {
#ifdef MOZ_B2G_RIL
smsService.removeSilentNumber(this.observedSilentNumber);
Services.obs.removeObserver(this.onSilentSms,
SILENT_SMS_RECEIVED_TOPIC);
this.observedSilentNumber = null;
this.onSilentSms = null;
#endif
}
};

View File

@ -0,0 +1,31 @@
/* 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";
this.EXPORTED_SYMBOLS = ["MobileIdentityUIGluePhoneInfo",
"MobileIdentityUIGluePromptResult"];
this.MobileIdentityUIGluePhoneInfo = function (aMsisdn, aOperator, aServiceId,
aExternal, aPrimary) {
this.msisdn = aMsisdn;
this.operator = aOperator;
this.serviceId = aServiceId;
// A phone number is considered "external" when it doesn't or we don't know
// if it does belong to any of the device SIM cards.
this.external = aExternal;
this.primary = aPrimary;
}
this.MobileIdentityUIGluePhoneInfo.prototype = {};
this.MobileIdentityUIGluePromptResult = function (aPhoneNumber, aPrefix, aMcc,
aServiceId) {
this.phoneNumber = aPhoneNumber;
this.prefix = aPrefix;
this.mcc = aMcc;
this.serviceId = aServiceId;
}
this.MobileIdentityUIGluePromptResult.prototype = {};

View File

@ -0,0 +1,213 @@
/* 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";
this.EXPORTED_SYMBOLS = ["MobileIdentityVerificationFlow"];
const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
this.MobileIdentityVerificationFlow = function(aVerificationOptions,
aUI,
aClient,
aVerifyStrategy,
aCleanupStrategy) {
this.verificationOptions = aVerificationOptions;
this.ui = aUI;
this.client = aClient;
this.retries = VERIFICATIONCODE_RETRIES;
this.verifyStrategy = aVerifyStrategy;
this.cleanupStrategy = aCleanupStrategy;
};
MobileIdentityVerificationFlow.prototype = {
doVerification: function() {
log.debug("Start verification flow");
return this.register()
.then(
(registerResult) => {
log.debug("Register result ${}", registerResult);
if (!registerResult || !registerResult.msisdnSessionToken) {
return Promise.reject(ERROR_INTERNAL_UNEXPECTED);
}
this.sessionToken = registerResult.msisdnSessionToken;
return this._doVerification();
}
)
},
_doVerification: function() {
log.debug("_doVerification");
// We save the timestamp of the start of the verification timeout to be
// able to provide to the UI the remaining time on each retry.
if (!this.timer) {
log.debug("Creating verification code timer");
this.timerCreation = Date.now();
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this.timer.initWithCallback(this.onVerificationCodeTimeout.bind(this),
VERIFICATIONCODE_TIMEOUT,
this.timer.TYPE_ONE_SHOT);
}
if (!this.verifyStrategy) {
return Promise.reject(ERROR_INTERNAL_INVALID_VERIFICATION_FLOW);
}
this.verificationCodeDeferred = Promise.defer();
this.verifyStrategy()
.then(
() => {
// If the verification flow can be for an external phone number,
// we need to ask the user for the verification code.
// In that case we don't do a notification about the verification
// process being done until the user enters the verification code
// in the UI.
if (this.verificationOptions.external) {
let timeLeft = 0;
if (this.timer) {
timeLeft = this.timerCreation + VERIFICATIONCODE_TIMEOUT -
Date.now();
}
this.ui.verificationCodePrompt(this.retries,
VERIFICATIONCODE_TIMEOUT / 1000,
timeLeft / 1000)
.then(
(verificationCode) => {
if (!verificationCode) {
return this.verificationCodeDeferred.reject(
ERROR_INTERNAL_INVALID_PROMPT_RESULT);
}
// If the user got the verification code that means that the
// introduced phone number didn't belong to any of the inserted
// SIMs.
this.ui.verify();
this.verificationCodeDeferred.resolve(verificationCode);
}
);
} else {
this.ui.verify();
}
},
(reason) => {
this.verificationCodeDeferred.reject(reason);
}
);
return this.verificationCodeDeferred.promise.then(
this.onVerificationCode.bind(this)
);
},
// When we receive a verification code from the UI, we check it against
// the server. If the verification code is incorrect, we decrease the
// number of retries left and allow the user to try again. If there is no
// possible retry left, we notify about this error so the UI can allow the
// user to request the resend of a new verification code.
onVerificationCode: function(aVerificationCode) {
log.debug("onVerificationCode " + aVerificationCode);
if (!aVerificationCode) {
this.ui.error(ERROR_INVALID_VERIFICATION_CODE);
return this._doVerification();
}
// Before checking the verification code against the server we set the
// "verifying" flag to queue timeout expiration events received before
// the server request is completed. If the server request is positive
// we will discard the timeout event, otherwise we will progress the
// event to the UI to allow the user to retry.
this.verifying = true;
return this.verifyCode(aVerificationCode)
.then(
(result) => {
if (!result) {
return Promise.reject(INTERNAL_UNEXPECTED);
}
// The code was correct!
// At this point the phone number is verified.
// We return the given verification options with the session token
// to be stored in the credentials store. With this data we will be
// asking the server to give us a certificate to generate assertions.
this.verificationOptions.sessionToken = this.sessionToken;
this.verificationOptions.msisdn = result.msisdn ||
this.verificationOptions.msisdn;
return this.verificationOptions;
},
(error) => {
log.error("Verification code error " + error);
this.retries--;
log.error("Retries left " + this.retries);
if (!this.retries) {
this.ui.error(ERROR_NO_RETRIES_LEFT);
return Promise.reject(ERROR_NO_RETRIES_LEFT);
}
this.verifying = false;
if (this.queuedTimeout) {
this.onVerificationCodeTimeout();
}
return this._doVerification();
}
);
},
onVerificationCodeTimeout: function() {
// It is possible that we get the timeout when we are checking a
// verification code with the server. In that case, we queue the
// timeout to be triggered after we receive the reply from the server
// if needed.
if (this.verifying) {
this.queuedTimeout = true;
return;
}
// When the verification process times out we do a clean up, reject
// the corresponding promise and notify the UI about the timeout.
if (this.verificationCodeDeferred) {
this.verificationCodeDeferred.reject(ERROR_VERIFICATION_CODE_TIMEOUT);
}
this.ui.error(ERROR_VERIFICATION_CODE_TIMEOUT);
},
register: function() {
return this.client.register();
},
verifyCode: function(aVerificationCode) {
return this.client.verifyCode(this.sessionToken, aVerificationCode);
},
unregister: function() {
return this.client.unregister(this.sessionToken);
},
cleanup: function(aUnregister = false) {
log.debug("Verification flow cleanup");
this.queuedTimeout = false;
this.retries = VERIFICATIONCODE_RETRIES;
if (this.timer) {
this.timer.cancel();
this.timer = null;
}
if (aUnregister) {
this.unregister().
then(
() => {
this.sessionToken = null;
}
);
}
if (this.cleanupStrategy) {
this.cleanupStrategy();
}
}
};

View File

@ -0,0 +1,11 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
XPIDL_SOURCES += [
'nsIMobileIdentityUIGlue.idl'
]
XPIDL_MODULE = 'services_mobileidentity'

View File

@ -0,0 +1,77 @@
/* 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(6c4c5758-e041-4e0d-98da-67bb552f8018)]
interface nsIMobileIdentityUIGlue : nsISupports
{
/**
* Request the creation of a Mobile ID UI flow.
*
* The permission prompt starts the verification flow asking the user
* for permission to share her phone number and allowing her to choose
* an already known phone number, a SIM which phone number is unknown
* (even in a multi-SIM scenario) or an external phone number.
* Selecting a phone number implies giving permission to share it with the
* API caller, so the UI should be clear about this.
*
* @manifestURL manifest URL of the mobile ID requester.
* @iccInfo array of objects containing the information about the
* SIM cards available in the device and that can be used for the
* phone number verification and share process.
*
* Returns a Promise. An instance of nsIMobileIdentityUIGluePromptResult will
* be returned as result of the Promise or a single string containing an error
* in case of rejection.
*/
jsval startFlow(in DOMString manifestURL, in jsval iccInfo);
/**
* Will prompt the user to enter a code used to verify a phone number.
* This will only be called if an external phone number is selected in
* startFlow().
*
* @retries number of retries left to validate a verification code.
* @timeout the verification code expires after the timeout fires. This is
* the total life time of the verification code.
* @timeLeft we might call verificationCodePrompt more than once for the
* same verification flow (i.e. when the verification code entered
* by the user is incorrect) so we give to the UI the amount of
* time left before the verification code expires.
*
* Returns a Promise. The value of the resolved promise will be the
* verification code introduced through the UI or an error in case of
* rejection of the promise.
*/
jsval verificationCodePrompt(in short retries,
in long timeout,
in long timeLeft);
/**
* Notify the UI about the start of the verification process.
*/
void verify();
/**
* Notify the UI about an error in the verification process.
*/
void error(in DOMString error);
/**
* Notify the UI about the succesful phone number verification.
*/
void verified(in DOMString verifiedPhoneNumber);
/**
* Callback to be called when the user cancels the verification flow via UI.
*/
attribute jsval oncancel;
/**
* Callback to be called when the user requests a resend of a verification
* code.
*/
attribute jsval onresendcode;
};

View File

@ -0,0 +1,22 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
PARALLEL_DIRS += ['interfaces']
EXTRA_JS_MODULES += [
'MobileIdentityClient.jsm',
'MobileIdentityCommon.jsm',
'MobileIdentityCredentialsStore.jsm',
'MobileIdentitySmsMoMtVerificationFlow.jsm',
'MobileIdentitySmsMtVerificationFlow.jsm',
'MobileIdentityUIGlueCommon.jsm',
'MobileIdentityVerificationFlow.jsm'
]
EXTRA_PP_JS_MODULES += [
'MobileIdentityManager.jsm',
'MobileIdentitySmsVerificationFlow.jsm'
]

View File

@ -6,7 +6,7 @@
PARALLEL_DIRS += [
'common',
'crypto',
'crypto'
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
@ -27,4 +27,7 @@ if CONFIG['MOZ_SERVICES_FXACCOUNTS']:
if CONFIG['MOZ_SERVICES_SYNC']:
PARALLEL_DIRS += ['sync']
if CONFIG['MOZ_B2G']:
PARALLEL_DIRS += ['mobileid']
SPHINX_TREES['services'] = 'docs'

View File

@ -27,3 +27,6 @@ skip = false
[include:../../../../../dom/nfc/tests/marionette/manifest.ini]
[include:../../../../../dom/events/test/marionette/manifest.ini]
[include:../../../../../dom/wifi/test/marionette/manifest.ini]
; layout tests
[include:../../../../../layout/base/tests/marionette/manifest.ini]

View File

@ -0,0 +1,17 @@
<!-- 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 id="html">
<head>
<title>Bug 960897: Marionette tests for touch caret</title>
</head>
<body>
<div><input id="input" value="ABCDEFGHI"></input></div>
<br />
<div><textarea name="textarea" id="textarea" rows="4" cols="6">ABCDEFGHI</textarea></div>
<br />
<div style="width: 10em; height: 2em; word-wrap: break-word; overflow: auto;" contenteditable="true" id="contenteditable">ABCDEFGHI</div>
</body>
</html>