mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 745345: BrowserID support for Apps in the Cloud; r=khuey, r=gps
This commit is contained in:
parent
4607dba17b
commit
8e0c38bec9
458
services/aitc/modules/browserid.js
Normal file
458
services/aitc/modules/browserid.js
Normal file
@ -0,0 +1,458 @@
|
||||
/* 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 EXPORTED_SYMBOLS = ["BrowserID"];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://services-common/log4moz.js");
|
||||
Cu.import("resource://services-common/preferences.js");
|
||||
|
||||
const PREFS = new Preferences("services.aitc.browserid.");
|
||||
|
||||
/**
|
||||
* This implementation will be replaced with native crypto and assertion
|
||||
* generation goodness. See bug 753238.
|
||||
*/
|
||||
function BrowserIDService() {
|
||||
this._log = Log4Moz.repository.getLogger("Services.BrowserID");
|
||||
this._log.level = Log4Moz.Level[PREFS.get("log")];
|
||||
}
|
||||
BrowserIDService.prototype = {
|
||||
/**
|
||||
* Getter that returns the freshest value for ID_URI.
|
||||
*/
|
||||
get ID_URI() {
|
||||
return PREFS.get("url");
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtain a BrowserID assertion with the specified characteristics.
|
||||
*
|
||||
* @param cb
|
||||
* (Function) Callback to be called with (err, assertion) where 'err'
|
||||
* can be an Error or NULL, and 'assertion' can be NULL or a valid
|
||||
* BrowserID assertion. If no callback is provided, an exception is
|
||||
* thrown.
|
||||
*
|
||||
* @param options
|
||||
* (Object) An object that may contain the following properties:
|
||||
*
|
||||
* "requiredEmail" : An email for which the assertion is to be
|
||||
* issued. If one could not be obtained, the call
|
||||
* will fail. If this property is not specified,
|
||||
* the default email as set by the user will be
|
||||
* chosen. If both this property and "sameEmailAs"
|
||||
* are set, an exception will be thrown.
|
||||
*
|
||||
* "sameEmailAs" : If set, instructs the function to issue an
|
||||
* assertion for the same email that was provided
|
||||
* to the domain specified by this value. If this
|
||||
* information could not be obtained, the call
|
||||
* will fail. If both this property and
|
||||
* "requiredEmail" are set, an exception will be
|
||||
* thrown.
|
||||
*
|
||||
* "audience" : The audience for which the assertion is to be
|
||||
* issued. If this property is not set an exception
|
||||
* will be thrown.
|
||||
*
|
||||
* Any properties not listed above will be ignored.
|
||||
*
|
||||
* (This function could use some love in terms of what arguments it accepts.
|
||||
* See bug 746401.)
|
||||
*/
|
||||
getAssertion: function getAssertion(cb, options) {
|
||||
if (!cb) {
|
||||
throw new Error("getAssertion called without a callback");
|
||||
}
|
||||
if (!options) {
|
||||
throw new Error("getAssertion called without any options");
|
||||
}
|
||||
if (!options.audience) {
|
||||
throw new Error("getAssertion called without an audience");
|
||||
}
|
||||
if (options.sameEmailAs && options.requiredEmail) {
|
||||
throw new Error(
|
||||
"getAssertion sameEmailAs and requiredEmail are mutually exclusive"
|
||||
);
|
||||
}
|
||||
|
||||
new Sandbox(this._getEmails.bind(this, cb, options), this.ID_URI);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtain a BrowserID assertion by asking the user to login and select an
|
||||
* email address.
|
||||
*
|
||||
* @param cb
|
||||
* (Function) Callback to be called with (err, assertion) where 'err'
|
||||
* can be an Error or NULL, and 'assertion' can be NULL or a valid
|
||||
* BrowserID assertion. If no callback is provided, an exception is
|
||||
* thrown.
|
||||
*
|
||||
* @param win
|
||||
* (Window) A contentWindow that has a valid document loaded. If this
|
||||
* argument is provided the user will be asked to login in the context
|
||||
* of the document currently loaded in this window.
|
||||
*
|
||||
* The audience of the assertion will be set to the domain of the
|
||||
* loaded document, and the "audience" property in the "options"
|
||||
* argument (if provided), will be ignored. The email to which this
|
||||
* assertion issued will be selected by the user when they login (and
|
||||
* "requiredEmail" or "sameEmailAs", if provided, will be ignored). If
|
||||
* the user chooses to not login, this call will fail.
|
||||
*
|
||||
* Be aware! The provided contentWindow must also have loaded the
|
||||
* BrowserID include.js shim for this to work! This behavior is
|
||||
* temporary until we implement native support for navigator.id.
|
||||
*
|
||||
* @param options
|
||||
* (Object) Currently an empty object. Present for future compatiblity
|
||||
* when options for a login case may be added. Any properties, if
|
||||
* present, are ignored.
|
||||
*/
|
||||
getAssertionWithLogin: function getAssertionWithLogin(cb, win, options) {
|
||||
if (!cb) {
|
||||
throw new Error("getAssertionWithLogin called without a callback");
|
||||
}
|
||||
if (!win) {
|
||||
throw new Error("getAssertionWithLogin called without a window");
|
||||
}
|
||||
this._getAssertionWithLogin(cb, win);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Internal implementation methods begin here
|
||||
*/
|
||||
|
||||
// Try to get the user's email(s). If user isn't logged in, this will be empty
|
||||
_getEmails: function _getEmails(cb, options, sandbox) {
|
||||
let self = this;
|
||||
|
||||
function callback(res) {
|
||||
let emails = {};
|
||||
try {
|
||||
emails = JSON.parse(res);
|
||||
} catch (e) {
|
||||
self._log.error("Exception in JSON.parse for _getAssertion: " + e);
|
||||
}
|
||||
self._gotEmails(emails, sandbox, cb, options);
|
||||
}
|
||||
sandbox.box.importFunction(callback);
|
||||
|
||||
let scriptText =
|
||||
"var list = window.BrowserID.User.getStoredEmailKeypairs();" +
|
||||
"callback(JSON.stringify(list));";
|
||||
Cu.evalInSandbox(scriptText, sandbox.box, "1.8", this.ID_URI, 1);
|
||||
},
|
||||
|
||||
// Received a list of emails from BrowserID for current user
|
||||
_gotEmails: function _gotEmails(emails, sandbox, cb, options) {
|
||||
let keys = Object.keys(emails);
|
||||
|
||||
// If list is empty, user is not logged in, or doesn't have a default email.
|
||||
if (!keys.length) {
|
||||
sandbox.free();
|
||||
|
||||
let err = "User is not logged in, or no emails were found";
|
||||
this._log.error(err);
|
||||
try {
|
||||
cb(new Error(err), null);
|
||||
} catch(e) {
|
||||
this._log.warn("Callback threw in _gotEmails " +
|
||||
CommonUtils.exceptionStr(e));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// User is logged in. For which email shall we get an assertion?
|
||||
|
||||
// Case 1: Explicitely provided
|
||||
if (options.requiredEmail) {
|
||||
this._getAssertionWithEmail(
|
||||
sandbox, cb, options.requiredEmail, options.audience
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Case 2: Derive from a given domain
|
||||
if (options.sameEmailAs) {
|
||||
this._getAssertionWithDomain(
|
||||
sandbox, cb, options.sameEmailAs, options.audience
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Case 3: Default email
|
||||
this._getAssertionWithEmail(
|
||||
sandbox, cb, keys[0], options.audience
|
||||
);
|
||||
return;
|
||||
},
|
||||
|
||||
/**
|
||||
* Open a login window and ask the user to login, returning the assertion
|
||||
* generated as a result to the caller.
|
||||
*/
|
||||
_getAssertionWithLogin: function _getAssertionWithLogin(cb, win) {
|
||||
// We're executing navigator.id.get as a content script in win.
|
||||
// This results in a popup that we will temporarily unblock.
|
||||
let pm = Services.perms;
|
||||
let origin = Services.io.newURI(
|
||||
win.wrappedJSObject.location.toString(), null, null
|
||||
);
|
||||
let oldPerm = pm.testExactPermission(origin, "popup");
|
||||
try {
|
||||
pm.add(origin, "popup", pm.ALLOW_ACTION);
|
||||
} catch(e) {
|
||||
this._log.warn("Setting popup blocking to false failed " + e);
|
||||
}
|
||||
|
||||
// Open sandbox and execute script. This sandbox will be GC'ed.
|
||||
let sandbox = new Cu.Sandbox(win, {
|
||||
wantXrays: false,
|
||||
sandboxPrototype: win
|
||||
});
|
||||
|
||||
let self = this;
|
||||
function callback(val) {
|
||||
// Set popup blocker permission to original value.
|
||||
try {
|
||||
pm.add(origin, "popup", oldPerm);
|
||||
} catch(e) {
|
||||
this._log.warn("Setting popup blocking to original value failed " + e);
|
||||
}
|
||||
|
||||
if (val) {
|
||||
self._log.info("_getAssertionWithLogin succeeded");
|
||||
try {
|
||||
cb(null, val);
|
||||
} catch(e) {
|
||||
self._log.warn("Callback threw in _getAssertionWithLogin " +
|
||||
CommonUtils.exceptionStr(e));
|
||||
}
|
||||
} else {
|
||||
let msg = "Could not obtain assertion in _getAssertionWithLogin";
|
||||
self._log.error(msg);
|
||||
try {
|
||||
cb(new Error(msg), null);
|
||||
} catch(e) {
|
||||
self._log.warn("Callback threw in _getAssertionWithLogin " +
|
||||
CommonUtils.exceptionStr(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
sandbox.importFunction(callback);
|
||||
|
||||
function doGetAssertion() {
|
||||
self._log.info("_getAssertionWithLogin Started");
|
||||
let scriptText = "window.navigator.id.get(" +
|
||||
" callback, {allowPersistent: true}" +
|
||||
");";
|
||||
Cu.evalInSandbox(scriptText, sandbox, "1.8", self.ID_URI, 1);
|
||||
}
|
||||
|
||||
// Sometimes the provided win hasn't fully loaded yet
|
||||
if (!win.document || (win.document.readyState != "complete")) {
|
||||
win.addEventListener("DOMContentLoaded", function _contentLoaded() {
|
||||
win.removeEventListener("DOMContentLoaded", _contentLoaded, false);
|
||||
doGetAssertion();
|
||||
}, false);
|
||||
} else {
|
||||
doGetAssertion();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an assertion for the specified 'email' and 'audience'
|
||||
*/
|
||||
_getAssertionWithEmail: function _getAssertionWithEmail(sandbox, cb, email,
|
||||
audience) {
|
||||
let self = this;
|
||||
|
||||
function onSuccess(res) {
|
||||
// Cleanup first.
|
||||
sandbox.free();
|
||||
|
||||
// The internal API sometimes calls onSuccess even though no assertion
|
||||
// could be obtained! Double check:
|
||||
if (!res) {
|
||||
let msg = "BrowserID.User.getAssertion empty assertion for " + email;
|
||||
self._log.error(msg);
|
||||
try {
|
||||
cb(new Error(msg), null);
|
||||
} catch(e) {
|
||||
self._log.warn("Callback threw in _getAssertionWithEmail " +
|
||||
CommonUtils.exceptionStr(e));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Success
|
||||
self._log.info("BrowserID.User.getAssertion succeeded");
|
||||
try {
|
||||
cb(null, res);
|
||||
} catch(e) {
|
||||
self._log.warn("Callback threw in _getAssertionWithEmail " +
|
||||
CommonUtils.exceptionStr(e));
|
||||
}
|
||||
}
|
||||
|
||||
function onError(err) {
|
||||
sandbox.free();
|
||||
|
||||
self._log.info("BrowserID.User.getAssertion failed");
|
||||
try {
|
||||
cb(err, null);
|
||||
} catch(e) {
|
||||
self._log.warn("Callback threw in _getAssertionWithEmail " +
|
||||
CommonUtils.exceptionStr(e));
|
||||
}
|
||||
}
|
||||
sandbox.box.importFunction(onSuccess);
|
||||
sandbox.box.importFunction(onError);
|
||||
|
||||
self._log.info("_getAssertionWithEmail Started");
|
||||
let scriptText =
|
||||
"window.BrowserID.User.getAssertion(" +
|
||||
"'" + email + "', " +
|
||||
"'" + audience + "', " +
|
||||
"onSuccess, " +
|
||||
"onError" +
|
||||
");";
|
||||
Cu.evalInSandbox(scriptText, sandbox.box, "1.8", this.ID_URI, 1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the email which was used to login to 'domain'. If one was found,
|
||||
* _getAssertionWithEmail is called to obtain the assertion.
|
||||
*/
|
||||
_getAssertionWithDomain: function _getAssertionWithDomain(sandbox, cb, domain,
|
||||
audience) {
|
||||
let self = this;
|
||||
|
||||
function onDomainSuccess(email) {
|
||||
if (email) {
|
||||
self._getAssertionWithEmail(sandbox, cb, email, audience);
|
||||
} else {
|
||||
sandbox.free();
|
||||
try {
|
||||
cb(new Error("No email found for _getAssertionWithDomain"), null);
|
||||
} catch(e) {
|
||||
self._log.warn("Callback threw in _getAssertionWithDomain " +
|
||||
CommonUtils.exceptionStr(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
sandbox.box.importFunction(onDomainSuccess);
|
||||
|
||||
// This wil tell us which email was used to login to "domain", if any.
|
||||
self._log.info("_getAssertionWithDomain Started");
|
||||
let scriptText =
|
||||
"onDomainSuccess(window.BrowserID.Storage.site.get(" +
|
||||
"'" + domain + "', " +
|
||||
"'email'" +
|
||||
"));";
|
||||
Cu.evalInSandbox(scriptText, sandbox.box, "1.8", this.ID_URI, 1);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* An object that represents a sandbox in an iframe loaded with uri. The
|
||||
* callback provided to the constructor will be invoked when the sandbox is
|
||||
* ready to be used. The callback will receive this object as its only argument
|
||||
* and the prepared sandbox may be accessed via the "box" property.
|
||||
*
|
||||
* Please call free() when you are finished with the sandbox to explicitely free
|
||||
* up all associated resources.
|
||||
*
|
||||
* @param cb
|
||||
* (function) Callback to be invoked with a Sandbox, when ready.
|
||||
* @param uri
|
||||
* (String) URI to be loaded in the Sandbox.
|
||||
*/
|
||||
function Sandbox(cb, uri) {
|
||||
this._uri = uri;
|
||||
this._createFrame();
|
||||
this._createSandbox(cb, uri);
|
||||
}
|
||||
Sandbox.prototype = {
|
||||
/**
|
||||
* Frees the sandbox and releases the iframe created to host it.
|
||||
*/
|
||||
free: function free() {
|
||||
delete this.box;
|
||||
this._container.removeChild(this._frame);
|
||||
this._frame = null;
|
||||
this._container = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates an empty, hidden iframe and sets it to the _iframe
|
||||
* property of this object.
|
||||
*
|
||||
* @return frame
|
||||
* (iframe) An empty, hidden iframe
|
||||
*/
|
||||
_createFrame: function _createFrame() {
|
||||
let doc = Services.wm.getMostRecentWindow("navigator:browser").document;
|
||||
|
||||
// Insert iframe in to create docshell.
|
||||
let frame = doc.createElement("iframe");
|
||||
frame.setAttribute("type", "content");
|
||||
frame.setAttribute("collapsed", "true");
|
||||
doc.documentElement.appendChild(frame);
|
||||
|
||||
// Stop about:blank from being loaded.
|
||||
let webNav = frame.docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
webNav.stop(Ci.nsIWebNavigation.STOP_NETWORK);
|
||||
|
||||
// Set instance properties.
|
||||
this._frame = frame;
|
||||
this._container = doc.documentElement;
|
||||
},
|
||||
|
||||
_createSandbox: function _createSandbox(cb, uri) {
|
||||
let self = this;
|
||||
this._frame.addEventListener(
|
||||
"DOMContentLoaded",
|
||||
function _makeSandboxContentLoaded(event) {
|
||||
if (event.target.location.toString() != uri) {
|
||||
return;
|
||||
}
|
||||
event.target.removeEventListener(
|
||||
"DOMContentLoaded", _makeSandboxContentLoaded, false
|
||||
);
|
||||
let workerWindow = self._frame.contentWindow;
|
||||
self.box = new Cu.Sandbox(workerWindow, {
|
||||
wantXrays: false,
|
||||
sandboxPrototype: workerWindow
|
||||
});
|
||||
cb(self);
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
// Load the iframe.
|
||||
this._frame.docShell.loadURI(
|
||||
uri,
|
||||
this._frame.docShell.LOAD_FLAGS_NONE,
|
||||
null, // referrer
|
||||
null, // postData
|
||||
null // headers
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "BrowserID", function() {
|
||||
return new BrowserIDService();
|
||||
});
|
@ -1,2 +1,9 @@
|
||||
pref("services.aitc.browserid.url", "https://browserid.org/sign_in");
|
||||
pref("services.aitc.browserid.log.level", "Debug");
|
||||
|
||||
pref("services.aitc.client.log.level", "Debug");
|
||||
pref("services.aitc.storage.log.level", "Debug");pref("services.aitc.client.timeout", 120);
|
||||
pref("services.aitc.storage.log.level", "Debug");
|
||||
|
||||
pref("services.aitc.client.timeout", 120);
|
||||
|
||||
pref("services.aitc.storage.log.level", "Debug");
|
||||
|
@ -14,3 +14,12 @@ MODULE = test_services_aitc
|
||||
XPCSHELL_TESTS = unit
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_browser_files = \
|
||||
mochitest/head.js \
|
||||
mochitest/browser_id_simple.js \
|
||||
mochitest/file_browser_id_mock.html \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_browser_files)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
|
||||
|
44
services/aitc/tests/mochitest/browser_id_simple.js
Normal file
44
services/aitc/tests/mochitest/browser_id_simple.js
Normal file
@ -0,0 +1,44 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const AUD = "http://foo.net";
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
setEndpoint("browser_id_mock");
|
||||
|
||||
// Get an assertion for default email.
|
||||
BrowserID.getAssertion(gotDefaultAssertion, {audience: AUD});
|
||||
}
|
||||
|
||||
function gotDefaultAssertion(err, ast) {
|
||||
is(err, null, "gotDefaultAssertion failed with " + err);
|
||||
is(ast, "default@example.org_assertion_" + AUD,
|
||||
"gotDefaultAssertion returned wrong assertion");
|
||||
|
||||
// Get an assertion for a specific email.
|
||||
BrowserID.getAssertion(gotSpecificAssertion, {
|
||||
requiredEmail: "specific@example.org",
|
||||
audience: AUD
|
||||
});
|
||||
}
|
||||
|
||||
function gotSpecificAssertion(err, ast) {
|
||||
is(err, null, "gotSpecificAssertion failed with " + err);
|
||||
is(ast, "specific@example.org_assertion_" + AUD,
|
||||
"gotSpecificAssertion returned wrong assertion");
|
||||
|
||||
// Get an assertion using sameEmailAs for another domain.
|
||||
BrowserID.getAssertion(gotSameEmailAssertion, {
|
||||
sameEmailAs: "http://zombo.com",
|
||||
audience: AUD
|
||||
});
|
||||
}
|
||||
|
||||
function gotSameEmailAssertion(err, ast) {
|
||||
is(err, null, "gotSameEmailAssertion failed with " + err);
|
||||
is(ast, "assertion_for_sameEmailAs",
|
||||
"gotSameEmailAssertion returned wrong assertion");
|
||||
|
||||
finish();
|
||||
}
|
52
services/aitc/tests/mochitest/file_browser_id_mock.html
Normal file
52
services/aitc/tests/mochitest/file_browser_id_mock.html
Normal file
@ -0,0 +1,52 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<p>Mock BrowserID endpoint for a logged-in user</p>
|
||||
</body>
|
||||
<script>
|
||||
|
||||
/**
|
||||
* Object containing valid email/key paris for this user. An assertion is simply
|
||||
* the string "_assertion_$audience" appended to the email. The exception is
|
||||
* when the email address is "sameEmailAs@example.org" the assertion will
|
||||
* be "assertion_for_sameEmailAs".
|
||||
*/
|
||||
var _emails = {
|
||||
"default@example.org": "default@example.org_key",
|
||||
"specific@example.org": "specific@example.org_key",
|
||||
"sameEmailAs@example.org": "sameEmailAs@example.org_key"
|
||||
};
|
||||
var _sameEmailAs = "sameEmailAs@example.org";
|
||||
|
||||
// Mock internal API
|
||||
window.BrowserID = {};
|
||||
window.BrowserID.User = {
|
||||
getStoredEmailKeypairs: function() {
|
||||
return _emails;
|
||||
},
|
||||
getAssertion: function(email, audience, success, error) {
|
||||
if (email == _sameEmailAs) {
|
||||
success("assertion_for_sameEmailAs");
|
||||
return;
|
||||
}
|
||||
if (email in _emails) {
|
||||
success(email + "_assertion_" + audience);
|
||||
return;
|
||||
}
|
||||
error("invalid email specified");
|
||||
}
|
||||
};
|
||||
window.BrowserID.Storage = {
|
||||
site: {
|
||||
get: function(domain, key) {
|
||||
if (key == "email") {
|
||||
return _sameEmailAs;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</html>
|
23
services/aitc/tests/mochitest/head.js
Normal file
23
services/aitc/tests/mochitest/head.js
Normal file
@ -0,0 +1,23 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
let tmp = {};
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://services-aitc/browserid.js", tmp);
|
||||
|
||||
const BrowserID = tmp.BrowserID;
|
||||
const testPath = "http://mochi.test:8888/browser/services/aitc/tests/";
|
||||
|
||||
function loadURL(aURL, aCB) {
|
||||
gBrowser.selectedBrowser.addEventListener("load", function () {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
|
||||
is(gBrowser.currentURI.spec, aURL, "loaded expected URL");
|
||||
aCB();
|
||||
}, true);
|
||||
gBrowser.loadURI(aURL);
|
||||
}
|
||||
|
||||
function setEndpoint(name) {
|
||||
let fullPath = testPath + "file_" + name + ".html";
|
||||
Services.prefs.setCharPref("services.aitc.browserid.url", fullPath);
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
const modules = [
|
||||
"client.js",
|
||||
"browserid.js",
|
||||
"storage.js"
|
||||
];
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user