mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 929386 - BrowserID DOM API extension for Firefox Accounts. r=MattN, r=smaug
This commit is contained in:
parent
66571e65c7
commit
e53404d029
@ -5,6 +5,15 @@
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
const PREF_FXA_ENABLED = "identity.fxaccounts.enabled";
|
||||
let _fxa_enabled = false;
|
||||
try {
|
||||
if (Services.prefs.getPrefType(PREF_FXA_ENABLED) === Ci.nsIPrefBranch.PREF_BOOL) {
|
||||
_fxa_enabled = Services.prefs.getBoolPref(PREF_FXA_ENABLED);
|
||||
}
|
||||
} catch(noPref) {
|
||||
}
|
||||
const FXA_ENABLED = _fxa_enabled;
|
||||
|
||||
// This is the parent process corresponding to nsDOMIdentity.
|
||||
this.EXPORTED_SYMBOLS = ["DOMIdentity"];
|
||||
@ -22,6 +31,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
|
||||
"resource://gre/modules/identity/Identity.jsm");
|
||||
#endif
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FirefoxAccounts",
|
||||
"resource://gre/modules/identity/FirefoxAccounts.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject",
|
||||
"resource://gre/modules/identity/IdentityUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this,
|
||||
"Logger",
|
||||
"resource://gre/modules/identity/LogUtils.jsm");
|
||||
@ -101,9 +116,17 @@ function RPWatchContext(aOptions, aTargetMM) {
|
||||
// default for no loggedInUser is undefined, not null
|
||||
this.loggedInUser = aOptions.loggedInUser;
|
||||
|
||||
// Maybe internal
|
||||
// Maybe internal. For hosted b2g identity shim.
|
||||
this._internal = aOptions._internal;
|
||||
|
||||
// By default, set the audience of the assertion to the origin of the RP. Bug
|
||||
// 947374 will make it possible for certified apps and packaged apps on
|
||||
// FirefoxOS to request a different audience from their origin.
|
||||
//
|
||||
// For BrowserID on b2g, this audience value is consumed by a hosted identity
|
||||
// shim, set up by b2g/components/SignInToWebsite.jsm.
|
||||
this.audience = this.origin;
|
||||
|
||||
this._mm = aTargetMM;
|
||||
}
|
||||
|
||||
@ -141,6 +164,72 @@ RPWatchContext.prototype = {
|
||||
};
|
||||
|
||||
this.DOMIdentity = {
|
||||
/*
|
||||
* When relying parties (RPs) invoke the watch() method, they can request
|
||||
* to use Firefox Accounts as their auth service or BrowserID (the default).
|
||||
* For each RP, we create an RPWatchContext to store the parameters given to
|
||||
* watch(), and to provide hooks to invoke the onlogin(), onlogout(), etc.
|
||||
* callbacks held in the nsDOMIdentity state.
|
||||
*
|
||||
* The serviceContexts map associates the window ID of the RP with the
|
||||
* context object. The mmContexts map associates a message manager with a
|
||||
* window ID. We use the mmContexts map when child-process-shutdown is
|
||||
* observed, and all we have is a message manager to identify the window in
|
||||
* question.
|
||||
*/
|
||||
_serviceContexts: new Map(),
|
||||
_mmContexts: new Map(),
|
||||
|
||||
/*
|
||||
* Create a new RPWatchContext, and update the context maps.
|
||||
*/
|
||||
newContext: function(message, targetMM) {
|
||||
let context = new RPWatchContext(message, targetMM);
|
||||
this._serviceContexts.set(message.id, context);
|
||||
this._mmContexts.set(targetMM, message.id);
|
||||
return context;
|
||||
},
|
||||
|
||||
/*
|
||||
* Get the identity service used for an RP.
|
||||
*
|
||||
* @object message
|
||||
* A message received from an RP. Will include the id of the window
|
||||
* whence the message originated.
|
||||
*
|
||||
* Returns FirefoxAccounts or IdentityService
|
||||
*/
|
||||
getService: function(message) {
|
||||
if (!this._serviceContexts.has(message.id)) {
|
||||
throw new Error("getService called before newContext for " + message.id);
|
||||
}
|
||||
|
||||
let context = this._serviceContexts.get(message.id);
|
||||
if (context.wantIssuer == "firefox-accounts") {
|
||||
if (FXA_ENABLED) {
|
||||
return FirefoxAccounts;
|
||||
}
|
||||
log("WARNING: Firefox Accounts is not enabled; Defaulting to BrowserID");
|
||||
}
|
||||
return IdentityService;
|
||||
},
|
||||
|
||||
/*
|
||||
* Get the RPWatchContext object for a given message manager.
|
||||
*/
|
||||
getContextForMM: function(targetMM) {
|
||||
return this._serviceContexts.get(this._mmContexts.get(targetMM));
|
||||
},
|
||||
|
||||
/*
|
||||
* Delete the RPWatchContext object for a given message manager. Removes the
|
||||
* mapping both from _serviceContexts and _mmContexts.
|
||||
*/
|
||||
deleteContextForMM: function(targetMM) {
|
||||
this._serviceContexts.delete(this._mmContexts.get(targetMM));
|
||||
this._mmContexts.delete(targetMM);
|
||||
},
|
||||
|
||||
// nsIMessageListener
|
||||
receiveMessage: function DOMIdentity_receiveMessage(aMessage) {
|
||||
let msg = aMessage.json;
|
||||
@ -235,69 +324,63 @@ this.DOMIdentity = {
|
||||
ppmm = null;
|
||||
},
|
||||
|
||||
_resetFrameState: function(aContext) {
|
||||
log("_resetFrameState: ", aContext.id);
|
||||
if (!aContext._mm) {
|
||||
throw new Error("ERROR: Trying to reset an invalid context");
|
||||
}
|
||||
let message = new IDDOMMessage({id: aContext.id});
|
||||
aContext._mm.sendAsyncMessage("Identity:ResetState", message);
|
||||
},
|
||||
|
||||
_watch: function DOMIdentity__watch(message, targetMM) {
|
||||
log("DOMIdentity__watch: " + message.id);
|
||||
// Pass an object with the watch members to Identity.jsm so it can call the
|
||||
// callbacks.
|
||||
let context = new RPWatchContext(message, targetMM);
|
||||
IdentityService.RP.watch(context);
|
||||
let context = this.newContext(message, targetMM);
|
||||
this.getService(message).RP.watch(context);
|
||||
},
|
||||
|
||||
_unwatch: function DOMIdentity_unwatch(message, targetMM) {
|
||||
IdentityService.RP.unwatch(message.id, targetMM);
|
||||
this.getService(message).RP.unwatch(message.id, targetMM);
|
||||
},
|
||||
|
||||
_request: function DOMIdentity__request(message) {
|
||||
IdentityService.RP.request(message.id, message);
|
||||
this.getService(message).RP.request(message.id, message);
|
||||
},
|
||||
|
||||
_logout: function DOMIdentity__logout(message) {
|
||||
IdentityService.RP.logout(message.id, message.origin, message);
|
||||
log("logout " + message + "\n");
|
||||
this.getService(message).RP.logout(message.id, message.origin, message);
|
||||
},
|
||||
|
||||
_childProcessShutdown: function DOMIdentity__childProcessShutdown(targetMM) {
|
||||
IdentityService.RP.childProcessShutdown(targetMM);
|
||||
this.getContextForMM(targetMM).RP.childProcessShutdown(targetMM);
|
||||
this.deleteContextForMM(targetMM);
|
||||
|
||||
let options = makeMessageObject({messageManager: targetMM, id: null, origin: null});
|
||||
Services.obs.notifyObservers({wrappedJSObject: options}, "identity-child-process-shutdown", null);
|
||||
},
|
||||
|
||||
_beginProvisioning: function DOMIdentity__beginProvisioning(message, targetMM) {
|
||||
let context = new IDPProvisioningContext(message.id, message.origin,
|
||||
targetMM);
|
||||
IdentityService.IDP.beginProvisioning(context);
|
||||
this.getService(message).IDP.beginProvisioning(context);
|
||||
},
|
||||
|
||||
_genKeyPair: function DOMIdentity__genKeyPair(message) {
|
||||
IdentityService.IDP.genKeyPair(message.id);
|
||||
this.getService(message).IDP.genKeyPair(message.id);
|
||||
},
|
||||
|
||||
_registerCertificate: function DOMIdentity__registerCertificate(message) {
|
||||
IdentityService.IDP.registerCertificate(message.id, message.cert);
|
||||
this.getService(message).IDP.registerCertificate(message.id, message.cert);
|
||||
},
|
||||
|
||||
_provisioningFailure: function DOMIdentity__provisioningFailure(message) {
|
||||
IdentityService.IDP.raiseProvisioningFailure(message.id, message.reason);
|
||||
this.getService(message).IDP.raiseProvisioningFailure(message.id, message.reason);
|
||||
},
|
||||
|
||||
_beginAuthentication: function DOMIdentity__beginAuthentication(message, targetMM) {
|
||||
let context = new IDPAuthenticationContext(message.id, message.origin,
|
||||
targetMM);
|
||||
IdentityService.IDP.beginAuthentication(context);
|
||||
this.getService(message).IDP.beginAuthentication(context);
|
||||
},
|
||||
|
||||
_completeAuthentication: function DOMIdentity__completeAuthentication(message) {
|
||||
IdentityService.IDP.completeAuthentication(message.id);
|
||||
this.getService(message).IDP.completeAuthentication(message.id);
|
||||
},
|
||||
|
||||
_authenticationFailure: function DOMIdentity__authenticationFailure(message) {
|
||||
IdentityService.IDP.cancelAuthentication(message.id);
|
||||
this.getService(message).IDP.cancelAuthentication(message.id);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -76,6 +76,11 @@ nsDOMIdentity.prototype = {
|
||||
|
||||
watch: function nsDOMIdentity_watch(aOptions) {
|
||||
if (this._rpWatcher) {
|
||||
// For the initial release of Firefox Accounts, we support callers who
|
||||
// invoke watch() either for Firefox Accounts, or Persona, but not both.
|
||||
// In the future, we may wish to support the dual invocation (say, for
|
||||
// packaged apps so they can sign users in who reject the app's request
|
||||
// to sign in with their Firefox Accounts identity).
|
||||
throw new Error("navigator.id.watch was already called");
|
||||
}
|
||||
|
||||
@ -83,19 +88,28 @@ nsDOMIdentity.prototype = {
|
||||
throw new Error("options argument to watch is required");
|
||||
}
|
||||
|
||||
// Check for required callbacks
|
||||
let requiredCallbacks = ["onlogin", "onlogout"];
|
||||
for (let cbName of requiredCallbacks) {
|
||||
if ((!(cbName in aOptions))
|
||||
|| typeof(aOptions[cbName]) !== "function") {
|
||||
throw new Error(cbName + " callback is required.");
|
||||
}
|
||||
// The relying party (RP) provides callbacks on watch().
|
||||
//
|
||||
// In the future, BrowserID will probably only require an onlogin()
|
||||
// callback [1], lifting the requirement that BrowserID handle logged-in
|
||||
// state management for RPs. See
|
||||
// https://github.com/mozilla/id-specs/blob/greenfield/browserid/api-rp.md
|
||||
//
|
||||
// However, Firefox Accounts will almost certainly require RPs to provide
|
||||
// onlogout(), onready(), and possibly an onerror() callback.
|
||||
// XXX Bug 945278
|
||||
//
|
||||
// To accomodate the more and less lenient uses of the API, we will simply
|
||||
// be strict about checking for onlogin here.
|
||||
if (typeof(aOptions["onlogin"]) != "function") {
|
||||
throw new Error("onlogin() callback is required.");
|
||||
}
|
||||
|
||||
// Optional callback "onready"
|
||||
if (aOptions["onready"]
|
||||
&& typeof(aOptions['onready']) !== "function") {
|
||||
throw new Error("onready must be a function");
|
||||
// Optional callbacks
|
||||
for (let cb of ["onready", "onlogout"]) {
|
||||
if (aOptions[cb] && typeof(aOptions[cb]) != "function") {
|
||||
throw new Error(cb + " must be a function");
|
||||
}
|
||||
}
|
||||
|
||||
let message = this.DOMIdentityMessage(aOptions);
|
||||
@ -379,6 +393,7 @@ nsDOMIdentity.prototype = {
|
||||
// Store window and origin URI.
|
||||
this._window = aWindow;
|
||||
this._origin = aWindow.document.nodePrincipal.origin;
|
||||
this._appStatus = aWindow.document.nodePrincipal.appStatus;
|
||||
|
||||
// Setup identifiers for current window.
|
||||
let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
@ -536,6 +551,11 @@ nsDOMIdentity.prototype = {
|
||||
// window origin
|
||||
message.origin = this._origin;
|
||||
|
||||
// On b2g, an app's status can be NOT_INSTALLED, INSTALLED, PRIVILEGED, or
|
||||
// CERTIFIED. Compare the appStatus value to the constants enumerated in
|
||||
// Ci.nsIPrincipal.APP_STATUS_*.
|
||||
message.appStatus = this._appStatus;
|
||||
|
||||
return message;
|
||||
},
|
||||
|
||||
|
229
toolkit/identity/FirefoxAccounts.jsm
Normal file
229
toolkit/identity/FirefoxAccounts.jsm
Normal file
@ -0,0 +1,229 @@
|
||||
/* 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 = ["FirefoxAccounts"];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/identity/LogUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "objectCopy",
|
||||
"resource://gre/modules/identity/IdentityUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject",
|
||||
"resource://gre/modules/identity/IdentityUtils.jsm");
|
||||
|
||||
// loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO",
|
||||
// "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by
|
||||
// default.
|
||||
const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel";
|
||||
try {
|
||||
this.LOG_LEVEL =
|
||||
Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
|
||||
&& Services.prefs.getCharPref(PREF_LOG_LEVEL);
|
||||
} catch (e) {
|
||||
this.LOG_LEVEL = Log.Level.Error;
|
||||
}
|
||||
|
||||
let log = Log.repository.getLogger("Identity.FxAccounts");
|
||||
log.level = LOG_LEVEL;
|
||||
log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
|
||||
|
||||
#ifdef MOZ_B2G
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsManager",
|
||||
"resource://gre/modules/FxAccountsManager.jsm",
|
||||
"FxAccountsManager");
|
||||
#else
|
||||
log.warn("The FxAccountsManager is only functional in B2G at this time.");
|
||||
var FxAccountsManager = null;
|
||||
#endif
|
||||
|
||||
function FxAccountsService() {
|
||||
Services.obs.addObserver(this, "quit-application-granted", false);
|
||||
|
||||
// Maintain interface parity with Identity.jsm and MinimalIdentity.jsm
|
||||
this.RP = this;
|
||||
|
||||
this._rpFlows = new Map();
|
||||
|
||||
// Enable us to mock FxAccountsManager service in testing
|
||||
this.fxAccountsManager = FxAccountsManager;
|
||||
}
|
||||
|
||||
FxAccountsService.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
|
||||
|
||||
observe: function observe(aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "quit-application-granted":
|
||||
Services.obs.removeObserver(this, "quit-application-granted");
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a listener for a given windowID as a result of a call to
|
||||
* navigator.id.watch().
|
||||
*
|
||||
* @param aCaller
|
||||
* (Object) an object that represents the caller document, and
|
||||
* is expected to have properties:
|
||||
* - id (unique, e.g. uuid)
|
||||
* - origin (string)
|
||||
*
|
||||
* and a bunch of callbacks
|
||||
* - doReady()
|
||||
* - doLogin()
|
||||
* - doLogout()
|
||||
* - doError()
|
||||
* - doCancel()
|
||||
*
|
||||
*/
|
||||
watch: function watch(aRpCaller) {
|
||||
this._rpFlows.set(aRpCaller.id, aRpCaller);
|
||||
log.debug("Current rp flows: " + this._rpFlows.size);
|
||||
|
||||
// Nothing to do but call ready()
|
||||
let runnable = {
|
||||
run: () => {
|
||||
this.doReady(aRpCaller.id);
|
||||
}
|
||||
};
|
||||
Services.tm.currentThread.dispatch(runnable,
|
||||
Ci.nsIThread.DISPATCH_NORMAL);
|
||||
},
|
||||
|
||||
unwatch: function(aRpCaller, aTargetMM) {
|
||||
// nothing to do
|
||||
},
|
||||
|
||||
/**
|
||||
* Initiate a login with user interaction as a result of a call to
|
||||
* navigator.id.request().
|
||||
*
|
||||
* @param aRPId
|
||||
* (integer) the id of the doc object obtained in .watch()
|
||||
*
|
||||
* @param aOptions
|
||||
* (Object) options including privacyPolicy, termsOfService
|
||||
*/
|
||||
request: function request(aRPId, aOptions) {
|
||||
aOptions = aOptions || {};
|
||||
let rp = this._rpFlows.get(aRPId);
|
||||
if (!rp) {
|
||||
log.error("request() called before watch()");
|
||||
return;
|
||||
}
|
||||
|
||||
let options = makeMessageObject(rp);
|
||||
objectCopy(aOptions, options);
|
||||
|
||||
log.debug("get assertion for " + rp.audience);
|
||||
|
||||
this.fxAccountsManager.getAssertion(rp.audience).then(
|
||||
data => {
|
||||
log.debug("got assertion: " + JSON.stringify(data));
|
||||
this.doLogin(aRPId, data);
|
||||
},
|
||||
error => {
|
||||
log.error("get assertion failed: " + JSON.stringify(error));
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked when a user wishes to logout of a site (for instance, when clicking
|
||||
* on an in-content logout button).
|
||||
*
|
||||
* @param aRpCallerId
|
||||
* (integer) the id of the doc object obtained in .watch()
|
||||
*
|
||||
*/
|
||||
logout: function logout(aRpCallerId) {
|
||||
// XXX Bug 945363 - Resolve the SSO story for FXA and implement
|
||||
// logout accordingly.
|
||||
//
|
||||
// For now, it makes no sense to logout from a specific RP in
|
||||
// Firefox Accounts, so just directly call the logout callback.
|
||||
if (!this._rpFlows.has(aRpCallerId)) {
|
||||
log.error("logout() called before watch()");
|
||||
return;
|
||||
}
|
||||
|
||||
// Call logout() on the next tick
|
||||
let runnable = {
|
||||
run: () => {
|
||||
this.doLogout(aRpCallerId);
|
||||
}
|
||||
};
|
||||
Services.tm.currentThread.dispatch(runnable,
|
||||
Ci.nsIThread.DISPATCH_NORMAL);
|
||||
},
|
||||
|
||||
childProcessShutdown: function childProcessShutdown(messageManager) {
|
||||
for (let [key,] of this._rpFlows) {
|
||||
if (this._rpFlows.get(key)._mm === messageManager) {
|
||||
this._rpFlows.delete(key);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
doLogin: function doLogin(aRpCallerId, aAssertion) {
|
||||
let rp = this._rpFlows.get(aRpCallerId);
|
||||
if (!rp) {
|
||||
log.warn("doLogin found no rp to go with callerId " + aRpCallerId + "\n");
|
||||
return;
|
||||
}
|
||||
|
||||
rp.doLogin(aAssertion);
|
||||
},
|
||||
|
||||
doLogout: function doLogout(aRpCallerId) {
|
||||
let rp = this._rpFlows.get(aRpCallerId);
|
||||
if (!rp) {
|
||||
log.warn("doLogout found no rp to go with callerId " + aRpCallerId + "\n");
|
||||
return;
|
||||
}
|
||||
|
||||
rp.doLogout();
|
||||
},
|
||||
|
||||
doReady: function doReady(aRpCallerId) {
|
||||
let rp = this._rpFlows.get(aRpCallerId);
|
||||
if (!rp) {
|
||||
log.warn("doReady found no rp to go with callerId " + aRpCallerId + "\n");
|
||||
return;
|
||||
}
|
||||
|
||||
rp.doReady();
|
||||
},
|
||||
|
||||
doCancel: function doCancel(aRpCallerId) {
|
||||
let rp = this._rpFlows.get(aRpCallerId);
|
||||
if (!rp) {
|
||||
log.warn("doCancel found no rp to go with callerId " + aRpCallerId + "\n");
|
||||
return;
|
||||
}
|
||||
|
||||
rp.doCancel();
|
||||
},
|
||||
|
||||
doError: function doError(aRpCallerId, aError) {
|
||||
let rp = this._rpFlows.get(aRpCallerId);
|
||||
if (!rp) {
|
||||
log.warn("doCancel found no rp to go with callerId " + aRpCallerId + "\n");
|
||||
return;
|
||||
}
|
||||
|
||||
rp.doError(aError);
|
||||
}
|
||||
};
|
||||
|
||||
this.FirefoxAccounts = new FxAccountsService();
|
||||
|
@ -12,7 +12,8 @@ this.EXPORTED_SYMBOLS = [
|
||||
"checkDeprecated",
|
||||
"checkRenamed",
|
||||
"getRandomId",
|
||||
"objectCopy"
|
||||
"objectCopy",
|
||||
"makeMessageObject",
|
||||
];
|
||||
|
||||
const Cu = Components.utils;
|
||||
@ -73,3 +74,38 @@ this.objectCopy = function objectCopy(source, target){
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.makeMessageObject = function makeMessageObject(aRpCaller) {
|
||||
let options = {};
|
||||
|
||||
options.id = aRpCaller.id;
|
||||
options.origin = aRpCaller.origin;
|
||||
|
||||
// Backwards compatibility with Persona beta:
|
||||
// loggedInUser can be undefined, null, or a string
|
||||
options.loggedInUser = aRpCaller.loggedInUser;
|
||||
|
||||
// Special flag for internal calls for Persona in b2g
|
||||
options._internal = aRpCaller._internal;
|
||||
|
||||
Object.keys(aRpCaller).forEach(function(option) {
|
||||
// Duplicate the callerobject, scrubbing out functions and other
|
||||
// internal variables (like _mm, the message manager object)
|
||||
if (!Object.hasOwnProperty(this, option)
|
||||
&& option[0] !== '_'
|
||||
&& typeof aRpCaller[option] !== 'function') {
|
||||
options[option] = aRpCaller[option];
|
||||
}
|
||||
});
|
||||
|
||||
// check validity of message structure
|
||||
if ((typeof options.id === 'undefined') ||
|
||||
(typeof options.origin === 'undefined')) {
|
||||
let err = "id and origin required in relying-party message: " + JSON.stringify(options);
|
||||
reportError(err);
|
||||
throw new Error(err);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
|
@ -29,9 +29,8 @@ Cu.import("resource://gre/modules/identity/LogUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "objectCopy",
|
||||
"resource://gre/modules/identity/IdentityUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this,
|
||||
"jwcrypto",
|
||||
"resource://gre/modules/identity/jwcrypto.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject",
|
||||
"resource://gre/modules/identity/IdentityUtils.jsm");
|
||||
|
||||
function log(...aMessageArgs) {
|
||||
Logger.log.apply(Logger, ["minimal core"].concat(aMessageArgs));
|
||||
@ -40,42 +39,8 @@ function reportError(...aMessageArgs) {
|
||||
Logger.reportError.apply(Logger, ["core"].concat(aMessageArgs));
|
||||
}
|
||||
|
||||
function makeMessageObject(aRpCaller) {
|
||||
let options = {};
|
||||
|
||||
options.id = aRpCaller.id;
|
||||
options.origin = aRpCaller.origin;
|
||||
|
||||
// loggedInUser can be undefined, null, or a string
|
||||
options.loggedInUser = aRpCaller.loggedInUser;
|
||||
|
||||
// Special flag for internal calls
|
||||
options._internal = aRpCaller._internal;
|
||||
|
||||
Object.keys(aRpCaller).forEach(function(option) {
|
||||
// Duplicate the callerobject, scrubbing out functions and other
|
||||
// internal variables (like _mm, the message manager object)
|
||||
if (!Object.hasOwnProperty(this, option)
|
||||
&& option[0] !== '_'
|
||||
&& typeof aRpCaller[option] !== 'function') {
|
||||
options[option] = aRpCaller[option];
|
||||
}
|
||||
});
|
||||
|
||||
// check validity of message structure
|
||||
if ((typeof options.id === 'undefined') ||
|
||||
(typeof options.origin === 'undefined')) {
|
||||
let err = "id and origin required in relying-party message: " + JSON.stringify(options);
|
||||
reportError(err);
|
||||
throw new Error(err);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function IDService() {
|
||||
Services.obs.addObserver(this, "quit-application-granted", false);
|
||||
// Services.obs.addObserver(this, "identity-auth-complete", false);
|
||||
|
||||
// simplify, it's one object
|
||||
this.RP = this;
|
||||
@ -212,8 +177,6 @@ IDService.prototype = {
|
||||
},
|
||||
|
||||
childProcessShutdown: function childProcessShutdown(messageManager) {
|
||||
let options = makeMessageObject({messageManager: messageManager, id: null, origin: null});
|
||||
Services.obs.notifyObservers({wrappedJSObject: options}, "identity-child-process-shutdown", null);
|
||||
Object.keys(this._rpFlows).forEach(function(key) {
|
||||
if (this._rpFlows[key]._mm === messageManager) {
|
||||
log("child process shutdown for rp", key, "- deleting flow");
|
||||
@ -273,202 +236,7 @@ IDService.prototype = {
|
||||
}
|
||||
|
||||
rp.doCancel();
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* XXX Bug 804229: Implement Identity Provider Functions
|
||||
*
|
||||
* Stubs for Identity Provider functions follow
|
||||
*/
|
||||
|
||||
/**
|
||||
* the provisioning iframe sandbox has called navigator.id.beginProvisioning()
|
||||
*
|
||||
* @param aCaller
|
||||
* (object) the iframe sandbox caller with all callbacks and
|
||||
* other information. Callbacks include:
|
||||
* - doBeginProvisioningCallback(id, duration_s)
|
||||
* - doGenKeyPairCallback(pk)
|
||||
*/
|
||||
beginProvisioning: function beginProvisioning(aCaller) {
|
||||
},
|
||||
|
||||
/**
|
||||
* the provisioning iframe sandbox has called
|
||||
* navigator.id.raiseProvisioningFailure()
|
||||
*
|
||||
* @param aProvId
|
||||
* (int) the identifier of the provisioning flow tied to that sandbox
|
||||
* @param aReason
|
||||
*/
|
||||
raiseProvisioningFailure: function raiseProvisioningFailure(aProvId, aReason) {
|
||||
reportError("Provisioning failure", aReason);
|
||||
},
|
||||
|
||||
/**
|
||||
* When navigator.id.genKeyPair is called from provisioning iframe sandbox.
|
||||
* Generates a keypair for the current user being provisioned.
|
||||
*
|
||||
* @param aProvId
|
||||
* (int) the identifier of the provisioning caller tied to that sandbox
|
||||
*
|
||||
* It is an error to call genKeypair without receiving the callback for
|
||||
* the beginProvisioning() call first.
|
||||
*/
|
||||
genKeyPair: function genKeyPair(aProvId) {
|
||||
},
|
||||
|
||||
/**
|
||||
* When navigator.id.registerCertificate is called from provisioning iframe
|
||||
* sandbox.
|
||||
*
|
||||
* Sets the certificate for the user for which a certificate was requested
|
||||
* via a preceding call to beginProvisioning (and genKeypair).
|
||||
*
|
||||
* @param aProvId
|
||||
* (integer) the identifier of the provisioning caller tied to that
|
||||
* sandbox
|
||||
*
|
||||
* @param aCert
|
||||
* (String) A JWT representing the signed certificate for the user
|
||||
* being provisioned, provided by the IdP.
|
||||
*/
|
||||
registerCertificate: function registerCertificate(aProvId, aCert) {
|
||||
},
|
||||
|
||||
/**
|
||||
* The authentication frame has called navigator.id.beginAuthentication
|
||||
*
|
||||
* IMPORTANT: the aCaller is *always* non-null, even if this is called from
|
||||
* a regular content page. We have to make sure, on every DOM call, that
|
||||
* aCaller is an expected authentication-flow identifier. If not, we throw
|
||||
* an error or something.
|
||||
*
|
||||
* @param aCaller
|
||||
* (object) the authentication caller
|
||||
*
|
||||
*/
|
||||
beginAuthentication: function beginAuthentication(aCaller) {
|
||||
},
|
||||
|
||||
/**
|
||||
* The auth frame has called navigator.id.completeAuthentication
|
||||
*
|
||||
* @param aAuthId
|
||||
* (int) the identifier of the authentication caller tied to that sandbox
|
||||
*
|
||||
*/
|
||||
completeAuthentication: function completeAuthentication(aAuthId) {
|
||||
},
|
||||
|
||||
/**
|
||||
* The auth frame has called navigator.id.cancelAuthentication
|
||||
*
|
||||
* @param aAuthId
|
||||
* (int) the identifier of the authentication caller
|
||||
*
|
||||
*/
|
||||
cancelAuthentication: function cancelAuthentication(aAuthId) {
|
||||
},
|
||||
|
||||
// methods for chrome and add-ons
|
||||
|
||||
/**
|
||||
* Discover the IdP for an identity
|
||||
*
|
||||
* @param aIdentity
|
||||
* (string) the email we're logging in with
|
||||
*
|
||||
* @param aCallback
|
||||
* (function) callback to invoke on completion
|
||||
* with first-positional parameter the error.
|
||||
*/
|
||||
_discoverIdentityProvider: function _discoverIdentityProvider(aIdentity, aCallback) {
|
||||
// XXX bug 767610 - validate email address call
|
||||
// When that is available, we can remove this custom parser
|
||||
var parsedEmail = this.parseEmail(aIdentity);
|
||||
if (parsedEmail === null) {
|
||||
return aCallback("Could not parse email: " + aIdentity);
|
||||
}
|
||||
log("_discoverIdentityProvider: identity:", aIdentity, "domain:", parsedEmail.domain);
|
||||
|
||||
this._fetchWellKnownFile(parsedEmail.domain, function fetchedWellKnown(err, idpParams) {
|
||||
// idpParams includes the pk, authorization url, and
|
||||
// provisioning url.
|
||||
|
||||
// XXX bug 769861 follow any authority delegations
|
||||
// if no well-known at any point in the delegation
|
||||
// fall back to browserid.org as IdP
|
||||
return aCallback(err, idpParams);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch the well-known file from the domain.
|
||||
*
|
||||
* @param aDomain
|
||||
*
|
||||
* @param aScheme
|
||||
* (string) (optional) Protocol to use. Default is https.
|
||||
* This is necessary because we are unable to test
|
||||
* https.
|
||||
*
|
||||
* @param aCallback
|
||||
*
|
||||
*/
|
||||
_fetchWellKnownFile: function _fetchWellKnownFile(aDomain, aCallback, aScheme='https') {
|
||||
// XXX bug 769854 make tests https and remove aScheme option
|
||||
let url = aScheme + '://' + aDomain + "/.well-known/browserid";
|
||||
log("_fetchWellKnownFile:", url);
|
||||
|
||||
// this appears to be a more successful way to get at xmlhttprequest (which supposedly will close with a window
|
||||
let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
|
||||
// XXX bug 769865 gracefully handle being off-line
|
||||
// XXX bug 769866 decide on how to handle redirects
|
||||
req.open("GET", url, true);
|
||||
req.responseType = "json";
|
||||
req.mozBackgroundRequest = true;
|
||||
req.onload = function _fetchWellKnownFile_onload() {
|
||||
if (req.status < 200 || req.status >= 400) {
|
||||
log("_fetchWellKnownFile", url, ": server returned status:", req.status);
|
||||
return aCallback("Error");
|
||||
}
|
||||
try {
|
||||
let idpParams = req.response;
|
||||
|
||||
// Verify that the IdP returned a valid configuration
|
||||
if (! (idpParams.provisioning &&
|
||||
idpParams.authentication &&
|
||||
idpParams['public-key'])) {
|
||||
let errStr= "Invalid well-known file from: " + aDomain;
|
||||
log("_fetchWellKnownFile:", errStr);
|
||||
return aCallback(errStr);
|
||||
}
|
||||
|
||||
let callbackObj = {
|
||||
domain: aDomain,
|
||||
idpParams: idpParams,
|
||||
};
|
||||
log("_fetchWellKnownFile result: ", callbackObj);
|
||||
// Yay. Valid IdP configuration for the domain.
|
||||
return aCallback(null, callbackObj);
|
||||
|
||||
} catch (err) {
|
||||
reportError("_fetchWellKnownFile", "Bad configuration from", aDomain, err);
|
||||
return aCallback(err.toString());
|
||||
}
|
||||
};
|
||||
req.onerror = function _fetchWellKnownFile_onerror() {
|
||||
log("_fetchWellKnownFile", "ERROR:", req.status, req.statusText);
|
||||
log("ERROR: _fetchWellKnownFile:", err);
|
||||
return aCallback("Error");
|
||||
};
|
||||
req.send(null);
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
this.IdentityService = new IDService();
|
||||
|
@ -30,6 +30,10 @@ EXTRA_JS_MODULES += [
|
||||
'Sandbox.jsm',
|
||||
]
|
||||
|
||||
EXTRA_PP_JS_MODULES += [
|
||||
'FirefoxAccounts.jsm',
|
||||
]
|
||||
|
||||
FAIL_ON_WARNINGS = True
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
@ -34,11 +34,13 @@ XPCOMUtils.defineLazyServiceGetter(this,
|
||||
"@mozilla.org/uuid-generator;1",
|
||||
"nsIUUIDGenerator");
|
||||
|
||||
const TEST_MESSAGE_MANAGER = "Mr McFeeley";
|
||||
const TEST_URL = "https://myfavoritebacon.com";
|
||||
const TEST_URL2 = "https://myfavoritebaconinacan.com";
|
||||
const TEST_USER = "user@mozilla.com";
|
||||
const TEST_PRIVKEY = "fake-privkey";
|
||||
const TEST_CERT = "fake-cert";
|
||||
const TEST_ASSERTION = "face-assertion";
|
||||
const TEST_IDPPARAMS = {
|
||||
domain: "myfavoriteflan.com",
|
||||
authentication: "/foo/authenticate.html",
|
||||
@ -74,12 +76,36 @@ function mock_doc(aIdentity, aOrigin, aDoFunc) {
|
||||
mockedDoc.loggedInUser = aIdentity;
|
||||
mockedDoc.origin = aOrigin;
|
||||
mockedDoc['do'] = aDoFunc;
|
||||
mockedDoc._mm = TEST_MESSAGE_MANAGER;
|
||||
mockedDoc.doReady = partial(aDoFunc, 'ready');
|
||||
mockedDoc.doLogin = partial(aDoFunc, 'login');
|
||||
mockedDoc.doLogout = partial(aDoFunc, 'logout');
|
||||
mockedDoc.doError = partial(aDoFunc, 'error');
|
||||
mockedDoc.doCancel = partial(aDoFunc, 'cancel');
|
||||
mockedDoc.doCoffee = partial(aDoFunc, 'coffee');
|
||||
mockedDoc.childProcessShutdown = partial(aDoFunc, 'child-process-shutdown');
|
||||
|
||||
mockedDoc.RP = mockedDoc;
|
||||
|
||||
return mockedDoc;
|
||||
}
|
||||
|
||||
function mock_fxa_rp(aIdentity, aOrigin, aDoFunc) {
|
||||
let mockedDoc = {};
|
||||
mockedDoc.id = uuid();
|
||||
mockedDoc.emailHint = aIdentity;
|
||||
mockedDoc.origin = aOrigin;
|
||||
mockedDoc.wantIssuer = "firefox-accounts";
|
||||
mockedDoc._mm = TEST_MESSAGE_MANAGER;
|
||||
|
||||
mockedDoc.doReady = partial(aDoFunc, "ready");
|
||||
mockedDoc.doLogin = partial(aDoFunc, "login");
|
||||
mockedDoc.doLogout = partial(aDoFunc, "logout");
|
||||
mockedDoc.doError = partial(aDoFunc, 'error');
|
||||
mockedDoc.doCancel = partial(aDoFunc, 'cancel');
|
||||
mockedDoc.childProcessShutdown = partial(aDoFunc, 'child-process-shutdown');
|
||||
|
||||
mockedDoc.RP = mockedDoc;
|
||||
|
||||
return mockedDoc;
|
||||
}
|
||||
@ -181,4 +207,24 @@ function setup_provisioning(identity, afterSetupCallback, doneProvisioningCallba
|
||||
}
|
||||
|
||||
// Switch debug messages on by default
|
||||
let initialPrefDebugValue = false;
|
||||
try {
|
||||
initialPrefDebugValue = Services.prefs.getBoolPref("toolkit.identity.debug");
|
||||
} catch(noPref) {}
|
||||
Services.prefs.setBoolPref("toolkit.identity.debug", true);
|
||||
|
||||
// Switch on firefox accounts
|
||||
let initialPrefFXAValue = false;
|
||||
try {
|
||||
initialPrefFXAValue = Services.prefs.getBoolPref("identity.fxaccounts.enabled");
|
||||
} catch(noPref) {}
|
||||
Services.prefs.setBoolPref("identity.fxaccounts.enabled", true);
|
||||
|
||||
// after execution, restore prefs
|
||||
do_register_cleanup(function() {
|
||||
log("restoring prefs to their initial values");
|
||||
Services.prefs.setBoolPref("toolkit.identity.debug", initialPrefDebugValue);
|
||||
Services.prefs.setBoolPref("identity.fxaccounts.enabled", initialPrefFXAValue);
|
||||
});
|
||||
|
||||
|
||||
|
168
toolkit/identity/tests/unit/test_firefox_accounts.js
Normal file
168
toolkit/identity/tests/unit/test_firefox_accounts.js
Normal file
@ -0,0 +1,168 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/DOMIdentity.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FirefoxAccounts",
|
||||
"resource://gre/modules/identity/FirefoxAccounts.jsm");
|
||||
|
||||
// Make the profile dir available; this is necessary so that
|
||||
// services/fxaccounts/FxAccounts.jsm can read and write its signed-in user
|
||||
// data.
|
||||
do_get_profile();
|
||||
|
||||
function MockFXAManager() {}
|
||||
MockFXAManager.prototype = {
|
||||
getAssertion: function(audience) {
|
||||
let deferred = Promise.defer();
|
||||
deferred.resolve(TEST_ASSERTION);
|
||||
return deferred.promise;
|
||||
}
|
||||
}
|
||||
|
||||
let originalManager = FirefoxAccounts.fxAccountsManager;
|
||||
FirefoxAccounts.fxAccountsManager = new MockFXAManager();
|
||||
do_register_cleanup(() => {
|
||||
print("restoring fxaccountsmanager");
|
||||
FirefoxAccounts.fxAccountsManager = originalManager;
|
||||
});
|
||||
|
||||
function test_overall() {
|
||||
do_check_neq(FirefoxAccounts, null);
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
function test_mock() {
|
||||
do_test_pending();
|
||||
|
||||
FirefoxAccounts.fxAccountsManager.getAssertion().then(assertion => {
|
||||
do_check_eq(assertion, TEST_ASSERTION);
|
||||
do_test_finished();
|
||||
run_next_test();
|
||||
});
|
||||
}
|
||||
|
||||
function test_watch() {
|
||||
do_test_pending();
|
||||
|
||||
let mockedRP = mock_fxa_rp(null, TEST_URL, function(method) {
|
||||
do_check_eq(method, "ready");
|
||||
do_test_finished();
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
FirefoxAccounts.RP.watch(mockedRP);
|
||||
}
|
||||
|
||||
function test_request() {
|
||||
do_test_pending();
|
||||
|
||||
let received = [];
|
||||
|
||||
let mockedRP = mock_fxa_rp(null, TEST_URL, function(method) {
|
||||
// We will received "ready" as a result of watch(), then "login"
|
||||
// as a result of request()
|
||||
received.push(method);
|
||||
|
||||
if (received.length == 2) {
|
||||
do_check_eq(received[0], "ready");
|
||||
do_check_eq(received[1], "login");
|
||||
do_test_finished();
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// Second, call request()
|
||||
if (method == "ready") {
|
||||
FirefoxAccounts.RP.request(mockedRP.id);
|
||||
}
|
||||
});
|
||||
|
||||
// First, call watch()
|
||||
FirefoxAccounts.RP.watch(mockedRP);
|
||||
}
|
||||
|
||||
function test_logout() {
|
||||
do_test_pending();
|
||||
|
||||
let received = [];
|
||||
|
||||
let mockedRP = mock_fxa_rp(null, TEST_URL, function(method) {
|
||||
// We will receive "ready" as a result of watch(), and "logout"
|
||||
// as a result of logout()
|
||||
received.push(method);
|
||||
|
||||
if (received.length == 2) {
|
||||
do_check_eq(received[0], "ready");
|
||||
do_check_eq(received[1], "logout");
|
||||
do_test_finished();
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
if (method == "ready") {
|
||||
// Second, call logout()
|
||||
FirefoxAccounts.RP.logout(mockedRP.id);
|
||||
}
|
||||
});
|
||||
|
||||
// First, call watch()
|
||||
FirefoxAccounts.RP.watch(mockedRP);
|
||||
}
|
||||
|
||||
function test_child_process_shutdown() {
|
||||
do_test_pending();
|
||||
let rpCount = FirefoxAccounts.RP._rpFlows.size;
|
||||
|
||||
makeObserver("identity-child-process-shutdown", (aTopic, aSubject, aData) => {
|
||||
// Last of all, the shutdown observer message will be fired.
|
||||
// This takes place after the RP has a chance to delete flows
|
||||
// and clean up.
|
||||
do_check_eq(FirefoxAccounts.RP._rpFlows.size, rpCount);
|
||||
do_test_finished();
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
let mockedRP = mock_fxa_rp(null, TEST_URL, (method) => {
|
||||
// We should enter this function for 'ready' and 'child-process-shutdown'.
|
||||
// After we have a chance to do our thing, the shutdown observer message
|
||||
// will fire and be caught by the function above.
|
||||
do_check_eq(FirefoxAccounts.RP._rpFlows.size, rpCount + 1);
|
||||
switch (method) {
|
||||
case "ready":
|
||||
DOMIdentity._childProcessShutdown("my message manager");
|
||||
break;
|
||||
|
||||
case "child-process-shutdown":
|
||||
// We have to call this explicitly because there's no real
|
||||
// dom window here.
|
||||
FirefoxAccounts.RP.childProcessShutdown(mockedRP._mm);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
mockedRP._mm = "my message manager";
|
||||
FirefoxAccounts.RP.watch(mockedRP);
|
||||
|
||||
// fake a dom window context
|
||||
DOMIdentity.newContext(mockedRP, mockedRP._mm);
|
||||
}
|
||||
|
||||
let TESTS = [
|
||||
test_overall,
|
||||
test_mock,
|
||||
test_watch,
|
||||
test_request,
|
||||
test_logout,
|
||||
test_child_process_shutdown,
|
||||
];
|
||||
|
||||
TESTS.forEach(add_test);
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "MinimalIDService",
|
||||
@ -5,6 +8,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "MinimalIDService",
|
||||
"IdentityService");
|
||||
|
||||
Cu.import("resource://gre/modules/identity/LogUtils.jsm");
|
||||
Cu.import("resource://gre/modules/DOMIdentity.jsm");
|
||||
|
||||
function log(...aMessageArgs) {
|
||||
Logger.log.apply(Logger, ["test_minimalidentity"].concat(aMessageArgs));
|
||||
@ -164,6 +168,40 @@ function test_unwatchBeforeWatch() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that the RP flow is cleaned up on child process shutdown
|
||||
*/
|
||||
|
||||
function test_childProcessShutdown() {
|
||||
do_test_pending();
|
||||
let UNIQUE_MESSAGE_MANAGER = "i am a beautiful snowflake";
|
||||
let initialRPCount = Object.keys(MinimalIDService.RP._rpFlows).length;
|
||||
|
||||
let mockedDoc = mock_doc(null, TEST_URL, (action, params) => {
|
||||
if (action == "child-process-shutdown") {
|
||||
// since there's no actual dom window connection, we have to
|
||||
// do this bit manually here.
|
||||
MinimalIDService.RP.childProcessShutdown(UNIQUE_MESSAGE_MANAGER);
|
||||
}
|
||||
});
|
||||
mockedDoc._mm = UNIQUE_MESSAGE_MANAGER;
|
||||
|
||||
makeObserver("identity-controller-watch", function (aSubject, aTopic, aData) {
|
||||
DOMIdentity._childProcessShutdown(UNIQUE_MESSAGE_MANAGER);
|
||||
});
|
||||
|
||||
makeObserver("identity-child-process-shutdown", (aTopic, aSubject, aData) => {
|
||||
do_check_eq(Object.keys(MinimalIDService.RP._rpFlows).length, initialRPCount);
|
||||
do_test_finished();
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
// fake a dom window context
|
||||
DOMIdentity.newContext(mockedDoc, UNIQUE_MESSAGE_MANAGER);
|
||||
|
||||
MinimalIDService.RP.watch(mockedDoc);
|
||||
}
|
||||
|
||||
let TESTS = [
|
||||
test_overall,
|
||||
test_mock_doc,
|
||||
@ -175,6 +213,7 @@ let TESTS = [
|
||||
test_logoutBeforeWatch,
|
||||
test_requestBeforeWatch,
|
||||
test_unwatchBeforeWatch,
|
||||
test_childProcessShutdown,
|
||||
];
|
||||
|
||||
TESTS.forEach(add_test);
|
||||
|
@ -8,6 +8,7 @@ support-files =
|
||||
# Test load modules first so syntax failures are caught early.
|
||||
[test_load_modules.js]
|
||||
[test_minimalidentity.js]
|
||||
[test_firefox_accounts.js]
|
||||
|
||||
[test_identity_utils.js]
|
||||
[test_log_utils.js]
|
||||
|
Loading…
Reference in New Issue
Block a user