browser/modules/test/browser_SignInToWebsite.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/modules/test/browser_SignInToWebsite.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,566 @@
     1.4 +/* Any copyright is dedicated to the Public Domain.
     1.5 + * http://creativecommons.org/publicdomain/zero/1.0/ */
     1.6 +
     1.7 +"use strict";
     1.8 +
     1.9 +/**
    1.10 + * TO TEST:
    1.11 + * - test state saved on doorhanger dismissal
    1.12 + * - links to switch steps
    1.13 + * - TOS and PP link clicks
    1.14 + * - identityList is populated correctly
    1.15 + */
    1.16 +
    1.17 +Services.prefs.setBoolPref("toolkit.identity.debug", true);
    1.18 +
    1.19 +XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
    1.20 +                                  "resource://gre/modules/identity/Identity.jsm");
    1.21 +
    1.22 +const TEST_ORIGIN = "https://example.com";
    1.23 +const TEST_EMAIL = "user@example.com";
    1.24 +
    1.25 +let gTestIndex = 0;
    1.26 +let outerWinId = gBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
    1.27 +                         .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
    1.28 +
    1.29 +function NotificationBase(aNotId) {
    1.30 +  this.id = aNotId;
    1.31 +}
    1.32 +NotificationBase.prototype = {
    1.33 +  message: TEST_ORIGIN,
    1.34 +  mainAction: {
    1.35 +    label: "",
    1.36 +    callback: function() {
    1.37 +      this.mainActionClicked = true;
    1.38 +    }.bind(this),
    1.39 +  },
    1.40 +  secondaryActions: [],
    1.41 +  options: {
    1.42 +    "identity": {
    1.43 +      origin: TEST_ORIGIN,
    1.44 +      rpId: outerWinId,
    1.45 +    },
    1.46 +  },
    1.47 +};
    1.48 +
    1.49 +let tests = [
    1.50 +  {
    1.51 +    name: "test_request_required_typed",
    1.52 +
    1.53 +    run: function() {
    1.54 +      setupRPFlow();
    1.55 +      this.notifyOptions = {
    1.56 +        rpId: outerWinId,
    1.57 +        origin: TEST_ORIGIN,
    1.58 +      };
    1.59 +      this.notifyObj = new NotificationBase("identity-request");
    1.60 +      Services.obs.notifyObservers({wrappedJSObject: this.notifyOptions},
    1.61 +                                   "identity-request", null);
    1.62 +    },
    1.63 +
    1.64 +    onShown: function(popup) {
    1.65 +      checkPopup(popup, this.notifyObj);
    1.66 +      let notification = popup.childNodes[0];
    1.67 +
    1.68 +      // Check identity popup state
    1.69 +      let state = notification.identity;
    1.70 +      ok(!state.typedEmail, "Nothing should be typed yet");
    1.71 +      ok(!state.selected, "Identity should not be selected yet");
    1.72 +      ok(!state.termsOfService, "No TOS specified");
    1.73 +      ok(!state.privacyPolicy, "No PP specified");
    1.74 +      is(state.step, 0, "Step should be persisted with default value");
    1.75 +      is(state.rpId, outerWinId, "Check rpId");
    1.76 +      is(state.origin, TEST_ORIGIN, "Check origin");
    1.77 +
    1.78 +      is(notification.step, 0, "Should be on the new email step");
    1.79 +      is(notification.chooseEmailLink.hidden, true, "Identity list is empty so link to list view should be hidden");
    1.80 +      is(notification.addEmailLink.parentElement.hidden, true, "We are already on the email input step so choose email pane should be hidden");
    1.81 +      is(notification.emailField.value, "", "Email field should default to empty on a new notification");
    1.82 +      let notifDoc = notification.ownerDocument;
    1.83 +      ok(notifDoc.getAnonymousElementByAttribute(notification, "anonid", "tos").hidden,
    1.84 +         "TOS link should be hidden");
    1.85 +      ok(notifDoc.getAnonymousElementByAttribute(notification, "anonid", "privacypolicy").hidden,
    1.86 +         "PP link should be hidden");
    1.87 +
    1.88 +      // Try to continue with a missing email address
    1.89 +      triggerMainCommand(popup);
    1.90 +      is(notification.throbber.style.visibility, "hidden", "is throbber visible");
    1.91 +      ok(!notification.button.disabled, "Button should not be disabled");
    1.92 +      is(window.gIdentitySelected, null, "Check no identity selected");
    1.93 +
    1.94 +      // Fill in an invalid email address and try again
    1.95 +      notification.emailField.value = "foo";
    1.96 +      triggerMainCommand(popup);
    1.97 +      is(notification.throbber.style.visibility, "hidden", "is throbber visible");
    1.98 +      ok(!notification.button.disabled, "Button should not be disabled");
    1.99 +      is(window.gIdentitySelected, null, "Check no identity selected");
   1.100 +
   1.101 +      // Fill in an email address and try again
   1.102 +      notification.emailField.value = TEST_EMAIL;
   1.103 +      triggerMainCommand(popup);
   1.104 +      is(window.gIdentitySelected.rpId, outerWinId, "Check identity selected rpId");
   1.105 +      is(window.gIdentitySelected.identity, TEST_EMAIL, "Check identity selected email");
   1.106 +      is(notification.identity.selected, TEST_EMAIL, "Check persisted email");
   1.107 +      is(notification.throbber.style.visibility, "visible", "is throbber visible");
   1.108 +      ok(notification.button.disabled, "Button should be disabled");
   1.109 +      ok(notification.emailField.disabled, "Email field should be disabled");
   1.110 +      ok(notification.identityList.disabled, "Identity list should be disabled");
   1.111 +
   1.112 +      PopupNotifications.getNotification("identity-request").remove();
   1.113 +    },
   1.114 +
   1.115 +    onHidden: function(popup) { },
   1.116 +  },
   1.117 +  {
   1.118 +    name: "test_request_optional",
   1.119 +
   1.120 +    run: function() {
   1.121 +      this.notifyOptions = {
   1.122 +        rpId: outerWinId,
   1.123 +        origin: TEST_ORIGIN,
   1.124 +        privacyPolicy: TEST_ORIGIN + "/pp.txt",
   1.125 +        termsOfService: TEST_ORIGIN + "/tos.tzt",
   1.126 +      };
   1.127 +      this.notifyObj = new NotificationBase("identity-request");
   1.128 +      Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
   1.129 +                                   "identity-request", null);
   1.130 +    },
   1.131 +
   1.132 +    onShown: function(popup) {
   1.133 +      checkPopup(popup, this.notifyObj);
   1.134 +      let notification = popup.childNodes[0];
   1.135 +
   1.136 +      // Check identity popup state
   1.137 +      let state = notification.identity;
   1.138 +      ok(!state.typedEmail, "Nothing should be typed yet");
   1.139 +      ok(!state.selected, "Identity should not be selected yet");
   1.140 +      is(state.termsOfService, this.notifyOptions.termsOfService, "Check TOS URL");
   1.141 +      is(state.privacyPolicy, this.notifyOptions.privacyPolicy, "Check PP URL");
   1.142 +      is(state.step, 0, "Step should be persisted with default value");
   1.143 +      is(state.rpId, outerWinId, "Check rpId");
   1.144 +      is(state.origin, TEST_ORIGIN, "Check origin");
   1.145 +
   1.146 +      is(notification.step, 0, "Should be on the new email step");
   1.147 +      is(notification.chooseEmailLink.hidden, true, "Identity list is empty so link to list view should be hidden");
   1.148 +      is(notification.addEmailLink.parentElement.hidden, true, "We are already on the email input step so choose email pane should be hidden");
   1.149 +      is(notification.emailField.value, "", "Email field should default to empty on a new notification");
   1.150 +      let notifDoc = notification.ownerDocument;
   1.151 +      let tosLink = notifDoc.getAnonymousElementByAttribute(notification, "anonid", "tos");
   1.152 +      ok(!tosLink.hidden, "TOS link should be visible");
   1.153 +      is(tosLink.href, this.notifyOptions.termsOfService, "Check TOS link URL");
   1.154 +      let ppLink = notifDoc.getAnonymousElementByAttribute(notification, "anonid", "privacypolicy");
   1.155 +      ok(!ppLink.hidden, "PP link should be visible");
   1.156 +      is(ppLink.href, this.notifyOptions.privacyPolicy, "Check PP link URL");
   1.157 +
   1.158 +      // Try to continue with a missing email address
   1.159 +      triggerMainCommand(popup);
   1.160 +      is(notification.throbber.style.visibility, "hidden", "is throbber visible");
   1.161 +      ok(!notification.button.disabled, "Button should not be disabled");
   1.162 +      is(window.gIdentitySelected, null, "Check no identity selected");
   1.163 +
   1.164 +      // Fill in an invalid email address and try again
   1.165 +      notification.emailField.value = "foo";
   1.166 +      triggerMainCommand(popup);
   1.167 +      is(notification.throbber.style.visibility, "hidden", "is throbber visible");
   1.168 +      ok(!notification.button.disabled, "Button should not be disabled");
   1.169 +      is(window.gIdentitySelected, null, "Check no identity selected");
   1.170 +
   1.171 +      // Fill in an email address and try again
   1.172 +      notification.emailField.value = TEST_EMAIL;
   1.173 +      triggerMainCommand(popup);
   1.174 +      is(window.gIdentitySelected.rpId, outerWinId, "Check identity selected rpId");
   1.175 +      is(window.gIdentitySelected.identity, TEST_EMAIL, "Check identity selected email");
   1.176 +      is(notification.identity.selected, TEST_EMAIL, "Check persisted email");
   1.177 +      is(notification.throbber.style.visibility, "visible", "is throbber visible");
   1.178 +      ok(notification.button.disabled, "Button should be disabled");
   1.179 +      ok(notification.emailField.disabled, "Email field should be disabled");
   1.180 +      ok(notification.identityList.disabled, "Identity list should be disabled");
   1.181 +
   1.182 +      PopupNotifications.getNotification("identity-request").remove();
   1.183 +    },
   1.184 +
   1.185 +    onHidden: function(popup) {},
   1.186 +  },
   1.187 +  {
   1.188 +    name: "test_login_state_changed",
   1.189 +    run: function () {
   1.190 +      this.notifyOptions = {
   1.191 +        rpId: outerWinId,
   1.192 +      };
   1.193 +      this.notifyObj = new NotificationBase("identity-logged-in");
   1.194 +      this.notifyObj.message = "Signed in as: user@example.com";
   1.195 +      this.notifyObj.mainAction.label = "Sign Out";
   1.196 +      this.notifyObj.mainAction.accessKey = "O";
   1.197 +      Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
   1.198 +                                   "identity-login-state-changed", TEST_EMAIL);
   1.199 +      executeSoon(function() {
   1.200 +        PopupNotifications.getNotification("identity-logged-in").anchorElement.click();
   1.201 +      });
   1.202 +    },
   1.203 +
   1.204 +    onShown: function(popup) {
   1.205 +      checkPopup(popup, this.notifyObj);
   1.206 +
   1.207 +      // Fire the notification that the user is no longer logged-in to close the UI.
   1.208 +      Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
   1.209 +                                   "identity-login-state-changed", null);
   1.210 +    },
   1.211 +
   1.212 +    onHidden: function(popup) {},
   1.213 +  },
   1.214 +  {
   1.215 +    name: "test_login_state_changed_logout",
   1.216 +    run: function () {
   1.217 +      this.notifyOptions = {
   1.218 +        rpId: outerWinId,
   1.219 +      };
   1.220 +      this.notifyObj = new NotificationBase("identity-logged-in");
   1.221 +      this.notifyObj.message = "Signed in as: user@example.com";
   1.222 +      this.notifyObj.mainAction.label = "Sign Out";
   1.223 +      this.notifyObj.mainAction.accessKey = "O";
   1.224 +      Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
   1.225 +                                   "identity-login-state-changed", TEST_EMAIL);
   1.226 +      executeSoon(function() {
   1.227 +        PopupNotifications.getNotification("identity-logged-in").anchorElement.click();
   1.228 +      });
   1.229 +    },
   1.230 +
   1.231 +    onShown: function(popup) {
   1.232 +      checkPopup(popup, this.notifyObj);
   1.233 +
   1.234 +      // This time trigger the Sign Out button and make sure the UI goes away.
   1.235 +      triggerMainCommand(popup);
   1.236 +    },
   1.237 +
   1.238 +    onHidden: function(popup) {},
   1.239 +  },
   1.240 +];
   1.241 +
   1.242 +function test_auth() {
   1.243 +  let notifyOptions = {
   1.244 +    provId: outerWinId,
   1.245 +    origin: TEST_ORIGIN,
   1.246 +  };
   1.247 +
   1.248 +  Services.obs.addObserver(function() {
   1.249 +    // prepare to send auth-complete and close the window
   1.250 +    let winCloseObs = new WindowObserver(function(closedWin) {
   1.251 +      info("closed window");
   1.252 +      finish();
   1.253 +    }, "domwindowclosed");
   1.254 +    Services.ww.registerNotification(winCloseObs);
   1.255 +    Services.obs.notifyObservers(null, "identity-auth-complete", IdentityService.IDP.authenticationFlowSet.authId);
   1.256 +
   1.257 +  }, "test-identity-auth-window", false);
   1.258 +
   1.259 +  let winObs = new WindowObserver(function(authWin) {
   1.260 +    ok(authWin, "Authentication window opened");
   1.261 +    ok(authWin.contentWindow.location);
   1.262 +  });
   1.263 +
   1.264 +  Services.ww.registerNotification(winObs);
   1.265 +
   1.266 +  Services.obs.notifyObservers({ wrappedJSObject: notifyOptions },
   1.267 +                               "identity-auth", TEST_ORIGIN + "/auth");
   1.268 +}
   1.269 +
   1.270 +function test() {
   1.271 +  waitForExplicitFinish();
   1.272 +
   1.273 +  let sitw = {};
   1.274 +  try {
   1.275 +    Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
   1.276 +  } catch (ex) {
   1.277 +    ok(true, "Skip the test since SignInToWebsite.jsm isn't packaged outside outside mozilla-central");
   1.278 +    finish();
   1.279 +    return;
   1.280 +  }
   1.281 +
   1.282 +  PopupNotifications.transitionsEnabled = false;
   1.283 +
   1.284 +  registerCleanupFunction(cleanUp);
   1.285 +
   1.286 +  ok(sitw.SignInToWebsiteUX, "SignInToWebsiteUX object exists");
   1.287 +  if (!Services.prefs.getBoolPref("dom.identity.enabled")) {
   1.288 +    // If the pref isn't enabled then init wasn't called so do that for the test.
   1.289 +    sitw.SignInToWebsiteUX.init();
   1.290 +  }
   1.291 +
   1.292 +  // Replace implementation of ID Service functions for testing
   1.293 +  window.selectIdentity = sitw.SignInToWebsiteUX.selectIdentity;
   1.294 +  sitw.SignInToWebsiteUX.selectIdentity = function(aRpId, aIdentity) {
   1.295 +    info("Identity selected: " + aIdentity);
   1.296 +    window.gIdentitySelected = {rpId: aRpId, identity: aIdentity};
   1.297 +  };
   1.298 +
   1.299 +  window.setAuthenticationFlow = IdentityService.IDP.setAuthenticationFlow;
   1.300 +  IdentityService.IDP.setAuthenticationFlow = function(aAuthId, aProvId) {
   1.301 +    info("setAuthenticationFlow: " + aAuthId + " : " + aProvId);
   1.302 +    this.authenticationFlowSet = { authId: aAuthId, provId: aProvId };
   1.303 +    Services.obs.notifyObservers(null, "test-identity-auth-window", aAuthId);
   1.304 +  };
   1.305 +
   1.306 +  runNextTest();
   1.307 +}
   1.308 +
   1.309 +// Cleanup between tests
   1.310 +function resetState() {
   1.311 +  delete window.gIdentitySelected;
   1.312 +  delete IdentityService.IDP.authenticationFlowSet;
   1.313 +  IdentityService.reset();
   1.314 +}
   1.315 +
   1.316 +// Cleanup after all tests
   1.317 +function cleanUp() {
   1.318 +  info("cleanup");
   1.319 +  resetState();
   1.320 +
   1.321 +  PopupNotifications.transitionsEnabled = true;
   1.322 +
   1.323 +  for (let topic in gActiveObservers)
   1.324 +    Services.obs.removeObserver(gActiveObservers[topic], topic);
   1.325 +  for (let eventName in gActiveListeners)
   1.326 +    PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false);
   1.327 +  delete IdentityService.RP._rpFlows[outerWinId];
   1.328 +
   1.329 +  // Put the JSM functions back to how they were
   1.330 +  IdentityService.IDP.setAuthenticationFlow = window.setAuthenticationFlow;
   1.331 +  delete window.setAuthenticationFlow;
   1.332 +
   1.333 +  let sitw = {};
   1.334 +  Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
   1.335 +  sitw.SignInToWebsiteUX.selectIdentity = window.selectIdentity;
   1.336 +  delete window.selectIdentity;
   1.337 +  if (!Services.prefs.getBoolPref("dom.identity.enabled")) {
   1.338 +    sitw.SignInToWebsiteUX.uninit();
   1.339 +  }
   1.340 +
   1.341 +  Services.prefs.clearUserPref("toolkit.identity.debug");
   1.342 +}
   1.343 +
   1.344 +let gActiveListeners = {};
   1.345 +let gActiveObservers = {};
   1.346 +let gShownState = {};
   1.347 +
   1.348 +function runNextTest() {
   1.349 +  let nextTest = tests[gTestIndex];
   1.350 +
   1.351 +  function goNext() {
   1.352 +    resetState();
   1.353 +    if (++gTestIndex == tests.length)
   1.354 +      executeSoon(test_auth);
   1.355 +    else
   1.356 +      executeSoon(runNextTest);
   1.357 +  }
   1.358 +
   1.359 +  function addObserver(topic) {
   1.360 +    function observer() {
   1.361 +      Services.obs.removeObserver(observer, "PopupNotifications-" + topic);
   1.362 +      delete gActiveObservers["PopupNotifications-" + topic];
   1.363 +
   1.364 +      info("[Test #" + gTestIndex + "] observer for " + topic + " called");
   1.365 +      nextTest[topic]();
   1.366 +      goNext();
   1.367 +    }
   1.368 +    Services.obs.addObserver(observer, "PopupNotifications-" + topic, false);
   1.369 +    gActiveObservers["PopupNotifications-" + topic] = observer;
   1.370 +  }
   1.371 +
   1.372 +  if (nextTest.backgroundShow) {
   1.373 +    addObserver("backgroundShow");
   1.374 +  } else if (nextTest.updateNotShowing) {
   1.375 +    addObserver("updateNotShowing");
   1.376 +  } else {
   1.377 +    doOnPopupEvent("popupshowing", function () {
   1.378 +      info("[Test #" + gTestIndex + "] popup showing");
   1.379 +    });
   1.380 +    doOnPopupEvent("popupshown", function () {
   1.381 +      gShownState[gTestIndex] = true;
   1.382 +      info("[Test #" + gTestIndex + "] popup shown");
   1.383 +      nextTest.onShown(this);
   1.384 +    });
   1.385 +
   1.386 +    // We allow multiple onHidden functions to be defined in an array.  They're
   1.387 +    // called in the order they appear.
   1.388 +    let onHiddenArray = nextTest.onHidden instanceof Array ?
   1.389 +                        nextTest.onHidden :
   1.390 +                        [nextTest.onHidden];
   1.391 +    doOnPopupEvent("popuphidden", function () {
   1.392 +      if (!gShownState[gTestIndex]) {
   1.393 +        // TODO: needed?
   1.394 +        info("Popup from test " + gTestIndex + " was hidden before its popupshown fired");
   1.395 +      }
   1.396 +
   1.397 +      let onHidden = onHiddenArray.shift();
   1.398 +      info("[Test #" + gTestIndex + "] popup hidden (" + onHiddenArray.length + " hides remaining)");
   1.399 +      executeSoon(function () {
   1.400 +        onHidden.call(nextTest, this);
   1.401 +        if (!onHiddenArray.length)
   1.402 +          goNext();
   1.403 +      }.bind(this));
   1.404 +    }, onHiddenArray.length);
   1.405 +    info("[Test #" + gTestIndex + "] added listeners; panel state: " + PopupNotifications.isPanelOpen);
   1.406 +  }
   1.407 +
   1.408 +  info("[Test #" + gTestIndex + "] running test");
   1.409 +  nextTest.run();
   1.410 +}
   1.411 +
   1.412 +function doOnPopupEvent(eventName, callback, numExpected) {
   1.413 +  gActiveListeners[eventName] = function (event) {
   1.414 +    if (event.target != PopupNotifications.panel)
   1.415 +      return;
   1.416 +    if (typeof(numExpected) === "number")
   1.417 +      numExpected--;
   1.418 +    if (!numExpected) {
   1.419 +      PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false);
   1.420 +      delete gActiveListeners[eventName];
   1.421 +    }
   1.422 +
   1.423 +    callback.call(PopupNotifications.panel);
   1.424 +  };
   1.425 +  PopupNotifications.panel.addEventListener(eventName, gActiveListeners[eventName], false);
   1.426 +}
   1.427 +
   1.428 +function checkPopup(popup, notificationObj) {
   1.429 +  info("[Test #" + gTestIndex + "] checking popup");
   1.430 +
   1.431 +  let notifications = popup.childNodes;
   1.432 +  is(notifications.length, 1, "only one notification displayed");
   1.433 +  let notification = notifications[0];
   1.434 +  let icon = document.getAnonymousElementByAttribute(notification, "class", "popup-notification-icon");
   1.435 +  is(notification.getAttribute("label"), notificationObj.message, "message matches");
   1.436 +  is(notification.id, notificationObj.id + "-notification", "id matches");
   1.437 +  if (notificationObj.id != "identity-request" && notificationObj.mainAction) {
   1.438 +    is(notification.getAttribute("buttonlabel"), notificationObj.mainAction.label, "main action label matches");
   1.439 +    is(notification.getAttribute("buttonaccesskey"), notificationObj.mainAction.accessKey, "main action accesskey matches");
   1.440 +  }
   1.441 +  let actualSecondaryActions = notification.childNodes;
   1.442 +  let secondaryActions = notificationObj.secondaryActions || [];
   1.443 +  let actualSecondaryActionsCount = actualSecondaryActions.length;
   1.444 +  if (secondaryActions.length) {
   1.445 +    let lastChild = actualSecondaryActions.item(actualSecondaryActions.length - 1);
   1.446 +    is(lastChild.tagName, "menuseparator", "menuseparator exists");
   1.447 +    actualSecondaryActionsCount--;
   1.448 +  }
   1.449 +  is(actualSecondaryActionsCount, secondaryActions.length, actualSecondaryActions.length + " secondary actions");
   1.450 +  secondaryActions.forEach(function (a, i) {
   1.451 +    is(actualSecondaryActions[i].getAttribute("label"), a.label, "label for secondary action " + i + " matches");
   1.452 +    is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey, "accessKey for secondary action " + i + " matches");
   1.453 +  });
   1.454 +}
   1.455 +
   1.456 +function triggerMainCommand(popup) {
   1.457 +  info("[Test #" + gTestIndex + "] triggering main command");
   1.458 +  let notifications = popup.childNodes;
   1.459 +  ok(notifications.length > 0, "at least one notification displayed");
   1.460 +  let notification = notifications[0];
   1.461 +
   1.462 +  // 20, 10 so that the inner button is hit
   1.463 +  EventUtils.synthesizeMouse(notification.button, 20, 10, {});
   1.464 +}
   1.465 +
   1.466 +function triggerSecondaryCommand(popup, index) {
   1.467 +  info("[Test #" + gTestIndex + "] triggering secondary command");
   1.468 +  let notifications = popup.childNodes;
   1.469 +  ok(notifications.length > 0, "at least one notification displayed");
   1.470 +  let notification = notifications[0];
   1.471 +
   1.472 +  notification.button.focus();
   1.473 +
   1.474 +  popup.addEventListener("popupshown", function () {
   1.475 +    popup.removeEventListener("popupshown", arguments.callee, false);
   1.476 +
   1.477 +    // Press down until the desired command is selected
   1.478 +    for (let i = 0; i <= index; i++)
   1.479 +      EventUtils.synthesizeKey("VK_DOWN", {});
   1.480 +
   1.481 +    // Activate
   1.482 +    EventUtils.synthesizeKey("VK_RETURN", {});
   1.483 +  }, false);
   1.484 +
   1.485 +  // One down event to open the popup
   1.486 +  EventUtils.synthesizeKey("VK_DOWN", { altKey: (navigator.platform.indexOf("Mac") == -1) });
   1.487 +}
   1.488 +
   1.489 +function dismissNotification(popup) {
   1.490 +  info("[Test #" + gTestIndex + "] dismissing notification");
   1.491 +  executeSoon(function () {
   1.492 +    EventUtils.synthesizeKey("VK_ESCAPE", {});
   1.493 +  });
   1.494 +}
   1.495 +
   1.496 +function partial(fn) {
   1.497 +  let args = Array.prototype.slice.call(arguments, 1);
   1.498 +  return function() {
   1.499 +    return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));
   1.500 +  };
   1.501 +}
   1.502 +
   1.503 +// create a mock "doc" object, which the Identity Service
   1.504 +// uses as a pointer back into the doc object
   1.505 +function mock_doc(aIdentity, aOrigin, aDoFunc) {
   1.506 +  let mockedDoc = {};
   1.507 +  mockedDoc.id = outerWinId;
   1.508 +  mockedDoc.loggedInEmail = aIdentity;
   1.509 +  mockedDoc.origin = aOrigin;
   1.510 +  mockedDoc['do'] = aDoFunc;
   1.511 +  mockedDoc.doReady = partial(aDoFunc, 'ready');
   1.512 +  mockedDoc.doLogin = partial(aDoFunc, 'login');
   1.513 +  mockedDoc.doLogout = partial(aDoFunc, 'logout');
   1.514 +  mockedDoc.doError = partial(aDoFunc, 'error');
   1.515 +  mockedDoc.doCancel = partial(aDoFunc, 'cancel');
   1.516 +  mockedDoc.doCoffee = partial(aDoFunc, 'coffee');
   1.517 +
   1.518 +  return mockedDoc;
   1.519 +}
   1.520 +
   1.521 +// takes a list of functions and returns a function that
   1.522 +// when called the first time, calls the first func,
   1.523 +// then the next time the second, etc.
   1.524 +function call_sequentially() {
   1.525 +  let numCalls = 0;
   1.526 +  let funcs = arguments;
   1.527 +
   1.528 +  return function() {
   1.529 +    if (!funcs[numCalls]) {
   1.530 +      let argString = Array.prototype.slice.call(arguments).join(",");
   1.531 +      ok(false, "Too many calls: " + argString);
   1.532 +      return;
   1.533 +    }
   1.534 +    funcs[numCalls].apply(funcs[numCalls], arguments);
   1.535 +    numCalls += 1;
   1.536 +  };
   1.537 +}
   1.538 +
   1.539 +function setupRPFlow(aIdentity) {
   1.540 +  IdentityService.RP.watch(mock_doc(aIdentity, TEST_ORIGIN, call_sequentially(
   1.541 +    function(action, params) {
   1.542 +      is(action, "ready", "1st callback");
   1.543 +      is(params, null);
   1.544 +    },
   1.545 +    function(action, params) {
   1.546 +      is(action, "logout", "2nd callback");
   1.547 +      is(params, null);
   1.548 +    },
   1.549 +    function(action, params) {
   1.550 +      is(action, "ready", "3rd callback");
   1.551 +      is(params, null);
   1.552 +    }
   1.553 +  )));
   1.554 +}
   1.555 +
   1.556 +function WindowObserver(aCallback, aObserveTopic = "domwindowopened") {
   1.557 +  this.observe = function(aSubject, aTopic, aData) {
   1.558 +    if (aTopic != aObserveTopic) {
   1.559 +      return;
   1.560 +    }
   1.561 +    info(aObserveTopic);
   1.562 +    Services.ww.unregisterNotification(this);
   1.563 +
   1.564 +    SimpleTest.executeSoon(function() {
   1.565 +      let domWin = aSubject.QueryInterface(Ci.nsIDOMWindow);
   1.566 +      aCallback(domWin);
   1.567 +    });
   1.568 +  };
   1.569 +}

mercurial