michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: "use strict"; michael@0: michael@0: /** michael@0: * TO TEST: michael@0: * - test state saved on doorhanger dismissal michael@0: * - links to switch steps michael@0: * - TOS and PP link clicks michael@0: * - identityList is populated correctly michael@0: */ michael@0: michael@0: Services.prefs.setBoolPref("toolkit.identity.debug", true); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "IdentityService", michael@0: "resource://gre/modules/identity/Identity.jsm"); michael@0: michael@0: const TEST_ORIGIN = "https://example.com"; michael@0: const TEST_EMAIL = "user@example.com"; michael@0: michael@0: let gTestIndex = 0; michael@0: let outerWinId = gBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils).outerWindowID; michael@0: michael@0: function NotificationBase(aNotId) { michael@0: this.id = aNotId; michael@0: } michael@0: NotificationBase.prototype = { michael@0: message: TEST_ORIGIN, michael@0: mainAction: { michael@0: label: "", michael@0: callback: function() { michael@0: this.mainActionClicked = true; michael@0: }.bind(this), michael@0: }, michael@0: secondaryActions: [], michael@0: options: { michael@0: "identity": { michael@0: origin: TEST_ORIGIN, michael@0: rpId: outerWinId, michael@0: }, michael@0: }, michael@0: }; michael@0: michael@0: let tests = [ michael@0: { michael@0: name: "test_request_required_typed", michael@0: michael@0: run: function() { michael@0: setupRPFlow(); michael@0: this.notifyOptions = { michael@0: rpId: outerWinId, michael@0: origin: TEST_ORIGIN, michael@0: }; michael@0: this.notifyObj = new NotificationBase("identity-request"); michael@0: Services.obs.notifyObservers({wrappedJSObject: this.notifyOptions}, michael@0: "identity-request", null); michael@0: }, michael@0: michael@0: onShown: function(popup) { michael@0: checkPopup(popup, this.notifyObj); michael@0: let notification = popup.childNodes[0]; michael@0: michael@0: // Check identity popup state michael@0: let state = notification.identity; michael@0: ok(!state.typedEmail, "Nothing should be typed yet"); michael@0: ok(!state.selected, "Identity should not be selected yet"); michael@0: ok(!state.termsOfService, "No TOS specified"); michael@0: ok(!state.privacyPolicy, "No PP specified"); michael@0: is(state.step, 0, "Step should be persisted with default value"); michael@0: is(state.rpId, outerWinId, "Check rpId"); michael@0: is(state.origin, TEST_ORIGIN, "Check origin"); michael@0: michael@0: is(notification.step, 0, "Should be on the new email step"); michael@0: is(notification.chooseEmailLink.hidden, true, "Identity list is empty so link to list view should be hidden"); michael@0: is(notification.addEmailLink.parentElement.hidden, true, "We are already on the email input step so choose email pane should be hidden"); michael@0: is(notification.emailField.value, "", "Email field should default to empty on a new notification"); michael@0: let notifDoc = notification.ownerDocument; michael@0: ok(notifDoc.getAnonymousElementByAttribute(notification, "anonid", "tos").hidden, michael@0: "TOS link should be hidden"); michael@0: ok(notifDoc.getAnonymousElementByAttribute(notification, "anonid", "privacypolicy").hidden, michael@0: "PP link should be hidden"); michael@0: michael@0: // Try to continue with a missing email address michael@0: triggerMainCommand(popup); michael@0: is(notification.throbber.style.visibility, "hidden", "is throbber visible"); michael@0: ok(!notification.button.disabled, "Button should not be disabled"); michael@0: is(window.gIdentitySelected, null, "Check no identity selected"); michael@0: michael@0: // Fill in an invalid email address and try again michael@0: notification.emailField.value = "foo"; michael@0: triggerMainCommand(popup); michael@0: is(notification.throbber.style.visibility, "hidden", "is throbber visible"); michael@0: ok(!notification.button.disabled, "Button should not be disabled"); michael@0: is(window.gIdentitySelected, null, "Check no identity selected"); michael@0: michael@0: // Fill in an email address and try again michael@0: notification.emailField.value = TEST_EMAIL; michael@0: triggerMainCommand(popup); michael@0: is(window.gIdentitySelected.rpId, outerWinId, "Check identity selected rpId"); michael@0: is(window.gIdentitySelected.identity, TEST_EMAIL, "Check identity selected email"); michael@0: is(notification.identity.selected, TEST_EMAIL, "Check persisted email"); michael@0: is(notification.throbber.style.visibility, "visible", "is throbber visible"); michael@0: ok(notification.button.disabled, "Button should be disabled"); michael@0: ok(notification.emailField.disabled, "Email field should be disabled"); michael@0: ok(notification.identityList.disabled, "Identity list should be disabled"); michael@0: michael@0: PopupNotifications.getNotification("identity-request").remove(); michael@0: }, michael@0: michael@0: onHidden: function(popup) { }, michael@0: }, michael@0: { michael@0: name: "test_request_optional", michael@0: michael@0: run: function() { michael@0: this.notifyOptions = { michael@0: rpId: outerWinId, michael@0: origin: TEST_ORIGIN, michael@0: privacyPolicy: TEST_ORIGIN + "/pp.txt", michael@0: termsOfService: TEST_ORIGIN + "/tos.tzt", michael@0: }; michael@0: this.notifyObj = new NotificationBase("identity-request"); michael@0: Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions }, michael@0: "identity-request", null); michael@0: }, michael@0: michael@0: onShown: function(popup) { michael@0: checkPopup(popup, this.notifyObj); michael@0: let notification = popup.childNodes[0]; michael@0: michael@0: // Check identity popup state michael@0: let state = notification.identity; michael@0: ok(!state.typedEmail, "Nothing should be typed yet"); michael@0: ok(!state.selected, "Identity should not be selected yet"); michael@0: is(state.termsOfService, this.notifyOptions.termsOfService, "Check TOS URL"); michael@0: is(state.privacyPolicy, this.notifyOptions.privacyPolicy, "Check PP URL"); michael@0: is(state.step, 0, "Step should be persisted with default value"); michael@0: is(state.rpId, outerWinId, "Check rpId"); michael@0: is(state.origin, TEST_ORIGIN, "Check origin"); michael@0: michael@0: is(notification.step, 0, "Should be on the new email step"); michael@0: is(notification.chooseEmailLink.hidden, true, "Identity list is empty so link to list view should be hidden"); michael@0: is(notification.addEmailLink.parentElement.hidden, true, "We are already on the email input step so choose email pane should be hidden"); michael@0: is(notification.emailField.value, "", "Email field should default to empty on a new notification"); michael@0: let notifDoc = notification.ownerDocument; michael@0: let tosLink = notifDoc.getAnonymousElementByAttribute(notification, "anonid", "tos"); michael@0: ok(!tosLink.hidden, "TOS link should be visible"); michael@0: is(tosLink.href, this.notifyOptions.termsOfService, "Check TOS link URL"); michael@0: let ppLink = notifDoc.getAnonymousElementByAttribute(notification, "anonid", "privacypolicy"); michael@0: ok(!ppLink.hidden, "PP link should be visible"); michael@0: is(ppLink.href, this.notifyOptions.privacyPolicy, "Check PP link URL"); michael@0: michael@0: // Try to continue with a missing email address michael@0: triggerMainCommand(popup); michael@0: is(notification.throbber.style.visibility, "hidden", "is throbber visible"); michael@0: ok(!notification.button.disabled, "Button should not be disabled"); michael@0: is(window.gIdentitySelected, null, "Check no identity selected"); michael@0: michael@0: // Fill in an invalid email address and try again michael@0: notification.emailField.value = "foo"; michael@0: triggerMainCommand(popup); michael@0: is(notification.throbber.style.visibility, "hidden", "is throbber visible"); michael@0: ok(!notification.button.disabled, "Button should not be disabled"); michael@0: is(window.gIdentitySelected, null, "Check no identity selected"); michael@0: michael@0: // Fill in an email address and try again michael@0: notification.emailField.value = TEST_EMAIL; michael@0: triggerMainCommand(popup); michael@0: is(window.gIdentitySelected.rpId, outerWinId, "Check identity selected rpId"); michael@0: is(window.gIdentitySelected.identity, TEST_EMAIL, "Check identity selected email"); michael@0: is(notification.identity.selected, TEST_EMAIL, "Check persisted email"); michael@0: is(notification.throbber.style.visibility, "visible", "is throbber visible"); michael@0: ok(notification.button.disabled, "Button should be disabled"); michael@0: ok(notification.emailField.disabled, "Email field should be disabled"); michael@0: ok(notification.identityList.disabled, "Identity list should be disabled"); michael@0: michael@0: PopupNotifications.getNotification("identity-request").remove(); michael@0: }, michael@0: michael@0: onHidden: function(popup) {}, michael@0: }, michael@0: { michael@0: name: "test_login_state_changed", michael@0: run: function () { michael@0: this.notifyOptions = { michael@0: rpId: outerWinId, michael@0: }; michael@0: this.notifyObj = new NotificationBase("identity-logged-in"); michael@0: this.notifyObj.message = "Signed in as: user@example.com"; michael@0: this.notifyObj.mainAction.label = "Sign Out"; michael@0: this.notifyObj.mainAction.accessKey = "O"; michael@0: Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions }, michael@0: "identity-login-state-changed", TEST_EMAIL); michael@0: executeSoon(function() { michael@0: PopupNotifications.getNotification("identity-logged-in").anchorElement.click(); michael@0: }); michael@0: }, michael@0: michael@0: onShown: function(popup) { michael@0: checkPopup(popup, this.notifyObj); michael@0: michael@0: // Fire the notification that the user is no longer logged-in to close the UI. michael@0: Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions }, michael@0: "identity-login-state-changed", null); michael@0: }, michael@0: michael@0: onHidden: function(popup) {}, michael@0: }, michael@0: { michael@0: name: "test_login_state_changed_logout", michael@0: run: function () { michael@0: this.notifyOptions = { michael@0: rpId: outerWinId, michael@0: }; michael@0: this.notifyObj = new NotificationBase("identity-logged-in"); michael@0: this.notifyObj.message = "Signed in as: user@example.com"; michael@0: this.notifyObj.mainAction.label = "Sign Out"; michael@0: this.notifyObj.mainAction.accessKey = "O"; michael@0: Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions }, michael@0: "identity-login-state-changed", TEST_EMAIL); michael@0: executeSoon(function() { michael@0: PopupNotifications.getNotification("identity-logged-in").anchorElement.click(); michael@0: }); michael@0: }, michael@0: michael@0: onShown: function(popup) { michael@0: checkPopup(popup, this.notifyObj); michael@0: michael@0: // This time trigger the Sign Out button and make sure the UI goes away. michael@0: triggerMainCommand(popup); michael@0: }, michael@0: michael@0: onHidden: function(popup) {}, michael@0: }, michael@0: ]; michael@0: michael@0: function test_auth() { michael@0: let notifyOptions = { michael@0: provId: outerWinId, michael@0: origin: TEST_ORIGIN, michael@0: }; michael@0: michael@0: Services.obs.addObserver(function() { michael@0: // prepare to send auth-complete and close the window michael@0: let winCloseObs = new WindowObserver(function(closedWin) { michael@0: info("closed window"); michael@0: finish(); michael@0: }, "domwindowclosed"); michael@0: Services.ww.registerNotification(winCloseObs); michael@0: Services.obs.notifyObservers(null, "identity-auth-complete", IdentityService.IDP.authenticationFlowSet.authId); michael@0: michael@0: }, "test-identity-auth-window", false); michael@0: michael@0: let winObs = new WindowObserver(function(authWin) { michael@0: ok(authWin, "Authentication window opened"); michael@0: ok(authWin.contentWindow.location); michael@0: }); michael@0: michael@0: Services.ww.registerNotification(winObs); michael@0: michael@0: Services.obs.notifyObservers({ wrappedJSObject: notifyOptions }, michael@0: "identity-auth", TEST_ORIGIN + "/auth"); michael@0: } michael@0: michael@0: function test() { michael@0: waitForExplicitFinish(); michael@0: michael@0: let sitw = {}; michael@0: try { michael@0: Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw); michael@0: } catch (ex) { michael@0: ok(true, "Skip the test since SignInToWebsite.jsm isn't packaged outside outside mozilla-central"); michael@0: finish(); michael@0: return; michael@0: } michael@0: michael@0: PopupNotifications.transitionsEnabled = false; michael@0: michael@0: registerCleanupFunction(cleanUp); michael@0: michael@0: ok(sitw.SignInToWebsiteUX, "SignInToWebsiteUX object exists"); michael@0: if (!Services.prefs.getBoolPref("dom.identity.enabled")) { michael@0: // If the pref isn't enabled then init wasn't called so do that for the test. michael@0: sitw.SignInToWebsiteUX.init(); michael@0: } michael@0: michael@0: // Replace implementation of ID Service functions for testing michael@0: window.selectIdentity = sitw.SignInToWebsiteUX.selectIdentity; michael@0: sitw.SignInToWebsiteUX.selectIdentity = function(aRpId, aIdentity) { michael@0: info("Identity selected: " + aIdentity); michael@0: window.gIdentitySelected = {rpId: aRpId, identity: aIdentity}; michael@0: }; michael@0: michael@0: window.setAuthenticationFlow = IdentityService.IDP.setAuthenticationFlow; michael@0: IdentityService.IDP.setAuthenticationFlow = function(aAuthId, aProvId) { michael@0: info("setAuthenticationFlow: " + aAuthId + " : " + aProvId); michael@0: this.authenticationFlowSet = { authId: aAuthId, provId: aProvId }; michael@0: Services.obs.notifyObservers(null, "test-identity-auth-window", aAuthId); michael@0: }; michael@0: michael@0: runNextTest(); michael@0: } michael@0: michael@0: // Cleanup between tests michael@0: function resetState() { michael@0: delete window.gIdentitySelected; michael@0: delete IdentityService.IDP.authenticationFlowSet; michael@0: IdentityService.reset(); michael@0: } michael@0: michael@0: // Cleanup after all tests michael@0: function cleanUp() { michael@0: info("cleanup"); michael@0: resetState(); michael@0: michael@0: PopupNotifications.transitionsEnabled = true; michael@0: michael@0: for (let topic in gActiveObservers) michael@0: Services.obs.removeObserver(gActiveObservers[topic], topic); michael@0: for (let eventName in gActiveListeners) michael@0: PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false); michael@0: delete IdentityService.RP._rpFlows[outerWinId]; michael@0: michael@0: // Put the JSM functions back to how they were michael@0: IdentityService.IDP.setAuthenticationFlow = window.setAuthenticationFlow; michael@0: delete window.setAuthenticationFlow; michael@0: michael@0: let sitw = {}; michael@0: Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw); michael@0: sitw.SignInToWebsiteUX.selectIdentity = window.selectIdentity; michael@0: delete window.selectIdentity; michael@0: if (!Services.prefs.getBoolPref("dom.identity.enabled")) { michael@0: sitw.SignInToWebsiteUX.uninit(); michael@0: } michael@0: michael@0: Services.prefs.clearUserPref("toolkit.identity.debug"); michael@0: } michael@0: michael@0: let gActiveListeners = {}; michael@0: let gActiveObservers = {}; michael@0: let gShownState = {}; michael@0: michael@0: function runNextTest() { michael@0: let nextTest = tests[gTestIndex]; michael@0: michael@0: function goNext() { michael@0: resetState(); michael@0: if (++gTestIndex == tests.length) michael@0: executeSoon(test_auth); michael@0: else michael@0: executeSoon(runNextTest); michael@0: } michael@0: michael@0: function addObserver(topic) { michael@0: function observer() { michael@0: Services.obs.removeObserver(observer, "PopupNotifications-" + topic); michael@0: delete gActiveObservers["PopupNotifications-" + topic]; michael@0: michael@0: info("[Test #" + gTestIndex + "] observer for " + topic + " called"); michael@0: nextTest[topic](); michael@0: goNext(); michael@0: } michael@0: Services.obs.addObserver(observer, "PopupNotifications-" + topic, false); michael@0: gActiveObservers["PopupNotifications-" + topic] = observer; michael@0: } michael@0: michael@0: if (nextTest.backgroundShow) { michael@0: addObserver("backgroundShow"); michael@0: } else if (nextTest.updateNotShowing) { michael@0: addObserver("updateNotShowing"); michael@0: } else { michael@0: doOnPopupEvent("popupshowing", function () { michael@0: info("[Test #" + gTestIndex + "] popup showing"); michael@0: }); michael@0: doOnPopupEvent("popupshown", function () { michael@0: gShownState[gTestIndex] = true; michael@0: info("[Test #" + gTestIndex + "] popup shown"); michael@0: nextTest.onShown(this); michael@0: }); michael@0: michael@0: // We allow multiple onHidden functions to be defined in an array. They're michael@0: // called in the order they appear. michael@0: let onHiddenArray = nextTest.onHidden instanceof Array ? michael@0: nextTest.onHidden : michael@0: [nextTest.onHidden]; michael@0: doOnPopupEvent("popuphidden", function () { michael@0: if (!gShownState[gTestIndex]) { michael@0: // TODO: needed? michael@0: info("Popup from test " + gTestIndex + " was hidden before its popupshown fired"); michael@0: } michael@0: michael@0: let onHidden = onHiddenArray.shift(); michael@0: info("[Test #" + gTestIndex + "] popup hidden (" + onHiddenArray.length + " hides remaining)"); michael@0: executeSoon(function () { michael@0: onHidden.call(nextTest, this); michael@0: if (!onHiddenArray.length) michael@0: goNext(); michael@0: }.bind(this)); michael@0: }, onHiddenArray.length); michael@0: info("[Test #" + gTestIndex + "] added listeners; panel state: " + PopupNotifications.isPanelOpen); michael@0: } michael@0: michael@0: info("[Test #" + gTestIndex + "] running test"); michael@0: nextTest.run(); michael@0: } michael@0: michael@0: function doOnPopupEvent(eventName, callback, numExpected) { michael@0: gActiveListeners[eventName] = function (event) { michael@0: if (event.target != PopupNotifications.panel) michael@0: return; michael@0: if (typeof(numExpected) === "number") michael@0: numExpected--; michael@0: if (!numExpected) { michael@0: PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false); michael@0: delete gActiveListeners[eventName]; michael@0: } michael@0: michael@0: callback.call(PopupNotifications.panel); michael@0: }; michael@0: PopupNotifications.panel.addEventListener(eventName, gActiveListeners[eventName], false); michael@0: } michael@0: michael@0: function checkPopup(popup, notificationObj) { michael@0: info("[Test #" + gTestIndex + "] checking popup"); michael@0: michael@0: let notifications = popup.childNodes; michael@0: is(notifications.length, 1, "only one notification displayed"); michael@0: let notification = notifications[0]; michael@0: let icon = document.getAnonymousElementByAttribute(notification, "class", "popup-notification-icon"); michael@0: is(notification.getAttribute("label"), notificationObj.message, "message matches"); michael@0: is(notification.id, notificationObj.id + "-notification", "id matches"); michael@0: if (notificationObj.id != "identity-request" && notificationObj.mainAction) { michael@0: is(notification.getAttribute("buttonlabel"), notificationObj.mainAction.label, "main action label matches"); michael@0: is(notification.getAttribute("buttonaccesskey"), notificationObj.mainAction.accessKey, "main action accesskey matches"); michael@0: } michael@0: let actualSecondaryActions = notification.childNodes; michael@0: let secondaryActions = notificationObj.secondaryActions || []; michael@0: let actualSecondaryActionsCount = actualSecondaryActions.length; michael@0: if (secondaryActions.length) { michael@0: let lastChild = actualSecondaryActions.item(actualSecondaryActions.length - 1); michael@0: is(lastChild.tagName, "menuseparator", "menuseparator exists"); michael@0: actualSecondaryActionsCount--; michael@0: } michael@0: is(actualSecondaryActionsCount, secondaryActions.length, actualSecondaryActions.length + " secondary actions"); michael@0: secondaryActions.forEach(function (a, i) { michael@0: is(actualSecondaryActions[i].getAttribute("label"), a.label, "label for secondary action " + i + " matches"); michael@0: is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey, "accessKey for secondary action " + i + " matches"); michael@0: }); michael@0: } michael@0: michael@0: function triggerMainCommand(popup) { michael@0: info("[Test #" + gTestIndex + "] triggering main command"); michael@0: let notifications = popup.childNodes; michael@0: ok(notifications.length > 0, "at least one notification displayed"); michael@0: let notification = notifications[0]; michael@0: michael@0: // 20, 10 so that the inner button is hit michael@0: EventUtils.synthesizeMouse(notification.button, 20, 10, {}); michael@0: } michael@0: michael@0: function triggerSecondaryCommand(popup, index) { michael@0: info("[Test #" + gTestIndex + "] triggering secondary command"); michael@0: let notifications = popup.childNodes; michael@0: ok(notifications.length > 0, "at least one notification displayed"); michael@0: let notification = notifications[0]; michael@0: michael@0: notification.button.focus(); michael@0: michael@0: popup.addEventListener("popupshown", function () { michael@0: popup.removeEventListener("popupshown", arguments.callee, false); michael@0: michael@0: // Press down until the desired command is selected michael@0: for (let i = 0; i <= index; i++) michael@0: EventUtils.synthesizeKey("VK_DOWN", {}); michael@0: michael@0: // Activate michael@0: EventUtils.synthesizeKey("VK_RETURN", {}); michael@0: }, false); michael@0: michael@0: // One down event to open the popup michael@0: EventUtils.synthesizeKey("VK_DOWN", { altKey: (navigator.platform.indexOf("Mac") == -1) }); michael@0: } michael@0: michael@0: function dismissNotification(popup) { michael@0: info("[Test #" + gTestIndex + "] dismissing notification"); michael@0: executeSoon(function () { michael@0: EventUtils.synthesizeKey("VK_ESCAPE", {}); michael@0: }); michael@0: } michael@0: michael@0: function partial(fn) { michael@0: let args = Array.prototype.slice.call(arguments, 1); michael@0: return function() { michael@0: return fn.apply(this, args.concat(Array.prototype.slice.call(arguments))); michael@0: }; michael@0: } michael@0: michael@0: // create a mock "doc" object, which the Identity Service michael@0: // uses as a pointer back into the doc object michael@0: function mock_doc(aIdentity, aOrigin, aDoFunc) { michael@0: let mockedDoc = {}; michael@0: mockedDoc.id = outerWinId; michael@0: mockedDoc.loggedInEmail = aIdentity; michael@0: mockedDoc.origin = aOrigin; michael@0: mockedDoc['do'] = aDoFunc; michael@0: mockedDoc.doReady = partial(aDoFunc, 'ready'); michael@0: mockedDoc.doLogin = partial(aDoFunc, 'login'); michael@0: mockedDoc.doLogout = partial(aDoFunc, 'logout'); michael@0: mockedDoc.doError = partial(aDoFunc, 'error'); michael@0: mockedDoc.doCancel = partial(aDoFunc, 'cancel'); michael@0: mockedDoc.doCoffee = partial(aDoFunc, 'coffee'); michael@0: michael@0: return mockedDoc; michael@0: } michael@0: michael@0: // takes a list of functions and returns a function that michael@0: // when called the first time, calls the first func, michael@0: // then the next time the second, etc. michael@0: function call_sequentially() { michael@0: let numCalls = 0; michael@0: let funcs = arguments; michael@0: michael@0: return function() { michael@0: if (!funcs[numCalls]) { michael@0: let argString = Array.prototype.slice.call(arguments).join(","); michael@0: ok(false, "Too many calls: " + argString); michael@0: return; michael@0: } michael@0: funcs[numCalls].apply(funcs[numCalls], arguments); michael@0: numCalls += 1; michael@0: }; michael@0: } michael@0: michael@0: function setupRPFlow(aIdentity) { michael@0: IdentityService.RP.watch(mock_doc(aIdentity, TEST_ORIGIN, call_sequentially( michael@0: function(action, params) { michael@0: is(action, "ready", "1st callback"); michael@0: is(params, null); michael@0: }, michael@0: function(action, params) { michael@0: is(action, "logout", "2nd callback"); michael@0: is(params, null); michael@0: }, michael@0: function(action, params) { michael@0: is(action, "ready", "3rd callback"); michael@0: is(params, null); michael@0: } michael@0: ))); michael@0: } michael@0: michael@0: function WindowObserver(aCallback, aObserveTopic = "domwindowopened") { michael@0: this.observe = function(aSubject, aTopic, aData) { michael@0: if (aTopic != aObserveTopic) { michael@0: return; michael@0: } michael@0: info(aObserveTopic); michael@0: Services.ww.unregisterNotification(this); michael@0: michael@0: SimpleTest.executeSoon(function() { michael@0: let domWin = aSubject.QueryInterface(Ci.nsIDOMWindow); michael@0: aCallback(domWin); michael@0: }); michael@0: }; michael@0: }