mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 764213 - Provisional desktop UI for website sign-in with Persona. r=dolske
--HG-- extra : rebase_source : 8785a377e88b11f59ec00d44b58e6c21fce576a7
This commit is contained in:
parent
248f3b6fa5
commit
c667677715
@ -459,6 +459,11 @@ window[chromehidden~="toolbar"] toolbar:not(.toolbar-primary):not(.chromeclass-m
|
||||
display: none;
|
||||
}
|
||||
|
||||
#notification-popup .text-link.custom-link {
|
||||
-moz-binding: url("chrome://global/content/bindings/text.xml#text-label");
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#invalid-form-popup > description {
|
||||
max-width: 280px;
|
||||
}
|
||||
@ -471,6 +476,10 @@ window[chromehidden~="toolbar"] toolbar:not(.toolbar-primary):not(.chromeclass-m
|
||||
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#addon-progress-notification");
|
||||
}
|
||||
|
||||
#identity-request-notification {
|
||||
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#identity-request-notification");
|
||||
}
|
||||
|
||||
/* override hidden="true" for the status bar compatibility shim
|
||||
in case it was persisted for the real status bar */
|
||||
#status-bar {
|
||||
|
@ -564,6 +564,7 @@
|
||||
onblur="setTimeout(function() document.getElementById('identity-box').style.MozUserFocus = '', 0);">
|
||||
<box id="notification-popup-box" hidden="true" align="center">
|
||||
<image id="default-notification-icon" class="notification-anchor-icon" role="button"/>
|
||||
<image id="identity-notification-icon" class="notification-anchor-icon" role="button"/>
|
||||
<image id="geo-notification-icon" class="notification-anchor-icon" role="button"/>
|
||||
<image id="addons-notification-icon" class="notification-anchor-icon" role="button"/>
|
||||
<image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"/>
|
||||
|
@ -78,7 +78,7 @@ function runNextTest() {
|
||||
onHidden.call(nextTest, this);
|
||||
if (!onHiddenArray.length)
|
||||
goNext();
|
||||
});
|
||||
}.bind(this));
|
||||
}, onHiddenArray.length);
|
||||
info("[Test #" + gTestIndex + "] added listeners; panel state: " + PopupNotifications.isPanelOpen);
|
||||
}
|
||||
|
@ -1175,6 +1175,261 @@
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="identity-request-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
|
||||
<content align="start">
|
||||
|
||||
<xul:image class="popup-notification-icon"
|
||||
xbl:inherits="popupid,src=icon"/>
|
||||
|
||||
<xul:vbox flex="1">
|
||||
<xul:vbox anonid="identity-deck">
|
||||
<xul:vbox flex="1" pack="center"> <!-- 1: add an email -->
|
||||
<html:input type="email" anonid="email" required="required" size="30"/>
|
||||
<xul:description anonid="newidentitydesc"/>
|
||||
<xul:spacer flex="1"/>
|
||||
<xul:label class="text-link custom-link small-margin" anonid="chooseemail" hidden="true"/>
|
||||
</xul:vbox>
|
||||
<xul:vbox flex="1" hidden="true"> <!-- 2: choose an email -->
|
||||
<xul:description anonid="chooseidentitydesc"/>
|
||||
<xul:radiogroup anonid="identities">
|
||||
</xul:radiogroup>
|
||||
<xul:label class="text-link custom-link" anonid="newemail"/>
|
||||
</xul:vbox>
|
||||
</xul:vbox>
|
||||
<xul:hbox class="popup-notification-button-container"
|
||||
pack="end" align="center">
|
||||
<xul:label anonid="tos" class="text-link" hidden="true"/>
|
||||
<xul:label anonid="privacypolicy" class="text-link" hidden="true"/>
|
||||
<xul:spacer flex="1"/>
|
||||
<xul:image anonid="throbber" src="chrome://browser/skin/tabbrowser/loading.png"
|
||||
style="visibility:hidden" width="16" height="16"/>
|
||||
<xul:button anonid="button"
|
||||
type="menu-button"
|
||||
class="popup-notification-menubutton"
|
||||
xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
|
||||
<xul:menupopup anonid="menupopup"
|
||||
xbl:inherits="oncommand=menucommand">
|
||||
<children/>
|
||||
<xul:menuitem class="menuitem-iconic popup-notification-closeitem"
|
||||
label="&closeNotificationItem.label;"
|
||||
xbl:inherits="oncommand=closeitemcommand"/>
|
||||
</xul:menupopup>
|
||||
</xul:button>
|
||||
</xul:hbox>
|
||||
</xul:vbox>
|
||||
<xul:vbox pack="start">
|
||||
<xul:toolbarbutton anonid="closebutton"
|
||||
class="messageCloseButton popup-notification-closebutton tabbable"
|
||||
xbl:inherits="oncommand=closebuttoncommand"
|
||||
tooltiptext="&closeNotification.tooltip;"/>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
<implementation>
|
||||
<constructor><![CDATA[
|
||||
// this.notification.options.identity is used to pass identity-specific info to the binding
|
||||
let origin = this.identity.origin
|
||||
|
||||
// Populate text
|
||||
this.emailField.placeholder = gNavigatorBundle.
|
||||
getString("identity.newIdentity.email.placeholder");
|
||||
this.newIdentityDesc.textContent = gNavigatorBundle.getFormattedString(
|
||||
"identity.newIdentity.description", [origin]);
|
||||
this.chooseIdentityDesc.textContent = gNavigatorBundle.getFormattedString(
|
||||
"identity.chooseIdentity.description", [origin]);
|
||||
|
||||
// Show optional terms of service and privacy policy links
|
||||
this._populateLink(this.identity.termsOfService, "tos", "identity.termsOfService");
|
||||
this._populateLink(this.identity.privacyPolicy, "privacypolicy", "identity.privacyPolicy");
|
||||
|
||||
// Populate the list of identities to choose from. The origin is used to provide
|
||||
// better suggestions.
|
||||
let identities = this.SignInToWebsiteUX.getIdentitiesForSite(origin);
|
||||
|
||||
this._populateIdentityList(identities);
|
||||
|
||||
if (typeof this.step == "undefined") {
|
||||
// First opening of this notification
|
||||
// Show the add email pane (0) if there are no existing identities otherwise show the list
|
||||
this.step = "result" in identities && identities.result.length ? 1 : 0;
|
||||
} else {
|
||||
// Already opened so restore previous state
|
||||
if (this.identity.typedEmail) {
|
||||
this.emailField.value = this.identity.typedEmail;
|
||||
}
|
||||
if (this.identity.selected) {
|
||||
// If the user already chose an identity then update the UI to reflect that
|
||||
this.onIdentitySelected();
|
||||
}
|
||||
// Update the view for the step
|
||||
this.step = this.step;
|
||||
}
|
||||
|
||||
// Fire notification with the chosen identity when main button is clicked
|
||||
this.button.addEventListener("command", this._onButtonCommand.bind(this), true);
|
||||
|
||||
// Do the same if enter is pressed in the email field
|
||||
this.emailField.addEventListener("keypress", function emailFieldKeypress(aEvent) {
|
||||
if (aEvent.keyCode != aEvent.DOM_VK_RETURN)
|
||||
return;
|
||||
this._onButtonCommand(aEvent);
|
||||
}.bind(this));
|
||||
|
||||
this.addEmailLink.value = gNavigatorBundle.getString("identity.newIdentity.label");
|
||||
this.addEmailLink.accessKey = gNavigatorBundle.getString("identity.newIdentity.accessKey");
|
||||
this.addEmailLink.addEventListener("click", function addEmailClick(evt) {
|
||||
this.step = 0;
|
||||
}.bind(this));
|
||||
|
||||
this.chooseEmailLink.value = gNavigatorBundle.getString("identity.chooseIdentity.label");
|
||||
this.chooseEmailLink.hidden = !("result" in identities && identities.result.length);
|
||||
this.chooseEmailLink.addEventListener("click", function chooseEmailClick(evt) {
|
||||
this.step = 1;
|
||||
}.bind(this));
|
||||
|
||||
this.emailField.addEventListener("blur", function onEmailBlur() {
|
||||
this.identity.typedEmail = this.emailField.value;
|
||||
}.bind(this));
|
||||
]]></constructor>
|
||||
|
||||
<field name="SignInToWebsiteUX" readonly="true">
|
||||
let sitw = {};
|
||||
Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
|
||||
sitw.SignInToWebsiteUX;
|
||||
</field>
|
||||
|
||||
<field name="newIdentityDesc" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "newidentitydesc");
|
||||
</field>
|
||||
|
||||
<field name="chooseIdentityDesc" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "chooseidentitydesc");
|
||||
</field>
|
||||
|
||||
<field name="identityList" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "identities");
|
||||
</field>
|
||||
|
||||
<field name="emailField" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "email");
|
||||
</field>
|
||||
|
||||
<field name="addEmailLink" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "newemail");
|
||||
</field>
|
||||
|
||||
<field name="chooseEmailLink" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "chooseemail");
|
||||
</field>
|
||||
|
||||
<field name="throbber" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "throbber");
|
||||
</field>
|
||||
|
||||
<field name="identity" readonly="true">
|
||||
this.notification.options.identity;
|
||||
</field>
|
||||
|
||||
<!-- persist the state on the identity object so we can re-create the
|
||||
notification state upon re-opening -->
|
||||
<property name="step">
|
||||
<getter>
|
||||
return this.identity.step;
|
||||
</getter>
|
||||
<setter><![CDATA[
|
||||
let deck = document.getAnonymousElementByAttribute(this, "anonid", "identity-deck");
|
||||
for (let i = 0; i < deck.children.length; i++) {
|
||||
deck.children[i].hidden = (val != i);
|
||||
}
|
||||
this.identity.step = val;
|
||||
switch (val) {
|
||||
case 0:
|
||||
this.emailField.focus();
|
||||
break;
|
||||
}]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<method name="onIdentitySelected">
|
||||
<body><![CDATA[
|
||||
this.throbber.style.visibility = "visible";
|
||||
this.button.disabled = true;
|
||||
this.emailField.value = this.identity.selected
|
||||
this.emailField.disabled = true;
|
||||
this.identityList.disabled = true;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_populateLink">
|
||||
<parameter name="aURL"/>
|
||||
<parameter name="aLinkId"/>
|
||||
<parameter name="aStringId"/>
|
||||
<body><![CDATA[
|
||||
if (aURL) {
|
||||
// Show optional link to aURL
|
||||
let link = document.getAnonymousElementByAttribute(this, "anonid", aLinkId);
|
||||
link.value = gNavigatorBundle.getString(aStringId);
|
||||
link.href = aURL;
|
||||
link.hidden = false;
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_populateIdentityList">
|
||||
<parameter name="aIdentities"/>
|
||||
<body><![CDATA[
|
||||
let foundLastUsed = false;
|
||||
let lastUsed = this.identity.selected || aIdentities.lastUsed;
|
||||
for (let id in aIdentities.result) {
|
||||
let label = aIdentities.result[id];
|
||||
let opt = this.identityList.appendItem(label);
|
||||
if (label == lastUsed) {
|
||||
this.identityList.selectedItem = opt;
|
||||
foundLastUsed = true;
|
||||
}
|
||||
}
|
||||
if (!foundLastUsed) {
|
||||
this.identityList.selectedIndex = -1;
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_onButtonCommand">
|
||||
<parameter name="aEvent"/>
|
||||
<body><![CDATA[
|
||||
if (aEvent.target != aEvent.currentTarget)
|
||||
return;
|
||||
let chosenId;
|
||||
switch (this.step) {
|
||||
case 0:
|
||||
aEvent.stopPropagation();
|
||||
if (!this.emailField.validity.valid) {
|
||||
this.emailField.focus();
|
||||
return;
|
||||
}
|
||||
chosenId = this.emailField.value;
|
||||
break;
|
||||
case 1:
|
||||
aEvent.stopPropagation();
|
||||
let selectedItem = this.identityList.selectedItem
|
||||
chosenId = selectedItem ? selectedItem.label : null;
|
||||
if (!chosenId)
|
||||
return;
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unknown case");
|
||||
return;
|
||||
}
|
||||
// Actually select the identity
|
||||
this.SignInToWebsiteUX.selectIdentity(this.identity.rpId, chosenId);
|
||||
this.identity.selected = chosenId;
|
||||
this.onIdentitySelected();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
|
||||
<binding id="splitmenu">
|
||||
<content>
|
||||
<xul:hbox anonid="menuitem" flex="1"
|
||||
|
@ -12,6 +12,7 @@ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource:///modules/SignInToWebsite.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
@ -25,7 +26,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
|
||||
"resource://gre/modules/BookmarkHTMLUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "webappsUI",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "webappsUI",
|
||||
"resource:///modules/webappsUI.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
|
||||
@ -308,6 +309,7 @@ BrowserGlue.prototype = {
|
||||
if (this._isPlacesShutdownObserver)
|
||||
os.removeObserver(this, "places-shutdown");
|
||||
webappsUI.uninit();
|
||||
SignInToWebsiteUX.uninit();
|
||||
},
|
||||
|
||||
_onAppDefaults: function BG__onAppDefaults() {
|
||||
@ -337,6 +339,8 @@ BrowserGlue.prototype = {
|
||||
|
||||
PageThumbs.init();
|
||||
|
||||
SignInToWebsiteUX.init();
|
||||
|
||||
PdfJs.init();
|
||||
|
||||
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
|
||||
|
@ -383,3 +383,21 @@ social.remove.accesskey=R
|
||||
|
||||
# LOCALIZATION NOTE (social.enabled.message): %1$S is the name of the social provider, %2$S is brandShortName (e.g. Firefox)
|
||||
social.activated.message=%1$S integration with %2$S has been activated.
|
||||
|
||||
# Identity notifications popups
|
||||
identity.termsOfService = Terms of Service
|
||||
identity.privacyPolicy = Privacy Policy
|
||||
identity.chooseIdentity.description = Sign in to %S
|
||||
identity.chooseIdentity.label = Use an existing email
|
||||
identity.newIdentity.label = Use a different email
|
||||
identity.newIdentity.accessKey = e
|
||||
identity.newIdentity.email.placeholder = Email
|
||||
# LOCALIZATION NOTE (identity.newIdentity.description, identity.chooseIdentity.description): %S is the website origin (ie. https://www.mozilla.org) shown in popup notifications.
|
||||
identity.newIdentity.description = Enter your email address to sign in to %S
|
||||
identity.next.label = Next
|
||||
identity.next.accessKey = n
|
||||
# LOCALIZATION NOTE: shown in the popup notification when a user successfully logs into a website
|
||||
# LOCALIZATION NOTE (identity.loggedIn.description): %S is the website origin (ie. https://www.mozilla.org)
|
||||
identity.loggedIn.description = Signed in as: %S
|
||||
identity.loggedIn.signOut.label = Sign Out
|
||||
identity.loggedIn.signOut.accessKey = O
|
||||
|
@ -18,12 +18,13 @@ EXTRA_JS_MODULES = \
|
||||
NetworkPrioritizer.jsm \
|
||||
NewTabUtils.jsm \
|
||||
offlineAppCache.jsm \
|
||||
SignInToWebsite.jsm \
|
||||
TelemetryTimestamps.jsm \
|
||||
Social.jsm \
|
||||
webappsUI.jsm \
|
||||
$(NULL)
|
||||
|
||||
ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
|
||||
ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
|
||||
EXTRA_JS_MODULES += \
|
||||
WindowsPreviewPerTab.jsm \
|
||||
WindowsJumpLists.jsm \
|
||||
|
235
browser/modules/SignInToWebsite.jsm
Normal file
235
browser/modules/SignInToWebsite.jsm
Normal file
@ -0,0 +1,235 @@
|
||||
/* 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 = ["SignInToWebsiteUX"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
|
||||
"resource://gre/modules/identity/Identity.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Logger",
|
||||
"resource://gre/modules/identity/LogUtils.jsm");
|
||||
|
||||
function log(...aMessageArgs) {
|
||||
Logger.log.apply(Logger, ["SignInToWebsiteUX"].concat(aMessageArgs));
|
||||
}
|
||||
|
||||
let SignInToWebsiteUX = {
|
||||
|
||||
init: function SignInToWebsiteUX_init() {
|
||||
Services.obs.addObserver(this, "identity-request", false);
|
||||
Services.obs.addObserver(this, "identity-auth", false);
|
||||
Services.obs.addObserver(this, "identity-auth-complete", false);
|
||||
Services.obs.addObserver(this, "identity-login-state-changed", false);
|
||||
},
|
||||
|
||||
uninit: function SignInToWebsiteUX_uninit() {
|
||||
Services.obs.removeObserver(this, "identity-request");
|
||||
Services.obs.removeObserver(this, "identity-auth");
|
||||
Services.obs.removeObserver(this, "identity-auth-complete");
|
||||
Services.obs.removeObserver(this, "identity-login-state-changed");
|
||||
},
|
||||
|
||||
observe: function SignInToWebsiteUX_observe(aSubject, aTopic, aData) {
|
||||
log("observe: received", aTopic, "with", aData, "for", aSubject);
|
||||
let options = null;
|
||||
if (aSubject) {
|
||||
options = aSubject.wrappedJSObject;
|
||||
}
|
||||
switch(aTopic) {
|
||||
case "identity-request":
|
||||
this.requestLogin(options);
|
||||
break;
|
||||
case "identity-auth":
|
||||
this._openAuthenticationUI(aData, options);
|
||||
break;
|
||||
case "identity-auth-complete":
|
||||
this._closeAuthenticationUI(aData);
|
||||
break;
|
||||
case "identity-login-state-changed":
|
||||
let emailAddress = aData;
|
||||
if (emailAddress) {
|
||||
this._removeRequestUI(options);
|
||||
this._showLoggedInUI(emailAddress, options);
|
||||
} else {
|
||||
this._removeLoggedInUI(options);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Logger.reportError("SignInToWebsiteUX", "Unknown observer notification:", aTopic);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The website is requesting login so the user must choose an identity to use.
|
||||
*/
|
||||
requestLogin: function SignInToWebsiteUX_requestLogin(aOptions) {
|
||||
let windowID = aOptions.rpId;
|
||||
log("requestLogin", aOptions);
|
||||
let [chromeWin, browserEl] = this._getUIForWindowID(windowID);
|
||||
|
||||
// message is not shown in the UI but is required
|
||||
let message = aOptions.origin;
|
||||
let mainAction = {
|
||||
label: chromeWin.gNavigatorBundle.getString("identity.next.label"),
|
||||
accessKey: chromeWin.gNavigatorBundle.getString("identity.next.accessKey"),
|
||||
callback: function() {}, // required
|
||||
};
|
||||
let options = {
|
||||
identity: {
|
||||
origin: aOptions.origin,
|
||||
},
|
||||
};
|
||||
let secondaryActions = [];
|
||||
|
||||
// add some extra properties to the notification to store some identity-related state
|
||||
for (let opt in aOptions) {
|
||||
options.identity[opt] = aOptions[opt];
|
||||
}
|
||||
log("requestLogin: rpId: ", options.identity.rpId);
|
||||
|
||||
chromeWin.PopupNotifications.show(browserEl, "identity-request", message,
|
||||
"identity-notification-icon", mainAction,
|
||||
[], options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the list of possible identities to login to the given origin.
|
||||
*/
|
||||
getIdentitiesForSite: function SignInToWebsiteUX_getIdentitiesForSite(aOrigin) {
|
||||
return IdentityService.RP.getIdentitiesForSite(aOrigin);
|
||||
},
|
||||
|
||||
/**
|
||||
* User chose a new or existing identity from the doorhanger after a request() call
|
||||
*/
|
||||
selectIdentity: function SignInToWebsiteUX_selectIdentity(aRpId, aIdentity) {
|
||||
log("selectIdentity: rpId: ", aRpId, " identity: ", aIdentity);
|
||||
IdentityService.selectIdentity(aRpId, aIdentity);
|
||||
},
|
||||
|
||||
// Private
|
||||
|
||||
/**
|
||||
* Return the chrome window and <browser> for the given outer window ID.
|
||||
*/
|
||||
_getUIForWindowID: function(aWindowID) {
|
||||
let someWindow = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
if (!someWindow) {
|
||||
Logger.reportError("SignInToWebsiteUX", "no window");
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
let windowUtils = someWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
let content = windowUtils.getOuterWindowWithId(aWindowID);
|
||||
|
||||
if (content) {
|
||||
let browser = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell).chromeEventHandler;
|
||||
let chromeWin = browser.ownerDocument.defaultView;
|
||||
return [chromeWin, browser];
|
||||
}
|
||||
Logger.reportError("SignInToWebsiteUX", "no content");
|
||||
|
||||
return [null, null];
|
||||
},
|
||||
|
||||
/**
|
||||
* Open UI with a content frame displaying aAuthURI so that the user can authenticate with their
|
||||
* IDP. Then tell Identity.jsm the identifier for the window so that it knows that the DOM API
|
||||
* calls are for this authentication flow.
|
||||
*/
|
||||
_openAuthenticationUI: function _openAuthenticationUI(aAuthURI, aContext) {
|
||||
// Open a tab/window with aAuthURI with an identifier (aID) attached so that the DOM APIs know this is an auth. window.
|
||||
let chromeWin = Services.wm.getMostRecentWindow('navigator:browser');
|
||||
let features = "chrome=false,width=640,height=480,centerscreen,location=yes,resizable=yes,scrollbars=yes,status=yes";
|
||||
log("aAuthURI: ", aAuthURI);
|
||||
let authWin = Services.ww.openWindow(chromeWin, "about:blank", "", features, null);
|
||||
let windowID = authWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
|
||||
log("authWin outer id: ", windowID);
|
||||
|
||||
let provId = aContext.provId;
|
||||
// Tell the ID service about the id before loading the url
|
||||
IdentityService.IDP.setAuthenticationFlow(windowID, provId);
|
||||
|
||||
authWin.location = aAuthURI;
|
||||
},
|
||||
|
||||
_closeAuthenticationUI: function _closeAuthenticationUI(aAuthId) {
|
||||
log("_closeAuthenticationUI:", aAuthId);
|
||||
let [chromeWin, browserEl] = this._getUIForWindowID(aAuthId);
|
||||
if (chromeWin)
|
||||
chromeWin.close();
|
||||
else
|
||||
Logger.reportError("SignInToWebsite", "Could not close window with ID", aAuthId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show a doorhanger indicating the currently logged-in user.
|
||||
*/
|
||||
_showLoggedInUI: function _showLoggedInUI(aIdentity, aContext) {
|
||||
let windowID = aContext.rpId;
|
||||
log("_showLoggedInUI for ", windowID);
|
||||
let [chromeWin, browserEl] = this._getUIForWindowID(windowID);
|
||||
|
||||
let message = chromeWin.gNavigatorBundle.getFormattedString("identity.loggedIn.description",
|
||||
[aIdentity]);
|
||||
let mainAction = {
|
||||
label: chromeWin.gNavigatorBundle.getString("identity.loggedIn.signOut.label"),
|
||||
accessKey: chromeWin.gNavigatorBundle.getString("identity.loggedIn.signOut.accessKey"),
|
||||
callback: function() {
|
||||
log("sign out callback fired");
|
||||
IdentityService.RP.logout(windowID);
|
||||
},
|
||||
};
|
||||
let secondaryActions = [];
|
||||
let options = {
|
||||
dismissed: true,
|
||||
};
|
||||
let loggedInNot = chromeWin.PopupNotifications.show(browserEl, "identity-logged-in", message,
|
||||
"identity-notification-icon", mainAction,
|
||||
secondaryActions, options);
|
||||
loggedInNot.rpId = windowID;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the doorhanger indicating the currently logged-in user.
|
||||
*/
|
||||
_removeLoggedInUI: function _removeLoggedInUI(aContext) {
|
||||
let windowID = aContext.rpId;
|
||||
log("_removeLoggedInUI for ", windowID);
|
||||
if (!windowID)
|
||||
throw "_removeLoggedInUI: Invalid RP ID";
|
||||
let [chromeWin, browserEl] = this._getUIForWindowID(windowID);
|
||||
|
||||
let loggedInNot = chromeWin.PopupNotifications.getNotification("identity-logged-in", browserEl);
|
||||
if (loggedInNot)
|
||||
chromeWin.PopupNotifications.remove(loggedInNot);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the doorhanger indicating the currently logged-in user.
|
||||
*/
|
||||
_removeRequestUI: function _removeRequestUI(aContext) {
|
||||
let windowID = aContext.rpId;
|
||||
log("_removeRequestUI for ", windowID);
|
||||
let [chromeWin, browserEl] = this._getUIForWindowID(windowID);
|
||||
|
||||
let requestNot = chromeWin.PopupNotifications.getNotification("identity-request", browserEl);
|
||||
if (requestNot)
|
||||
chromeWin.PopupNotifications.remove(requestNot);
|
||||
},
|
||||
|
||||
};
|
@ -14,9 +14,10 @@ include $(topsrcdir)/config/rules.mk
|
||||
_BROWSER_FILES = \
|
||||
browser_NetworkPrioritizer.js \
|
||||
browser_TelemetryTimestamps.js \
|
||||
browser_SignInToWebsite.js \
|
||||
$(NULL)
|
||||
|
||||
ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
|
||||
ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
|
||||
_BROWSER_FILES += \
|
||||
browser_taskbar_preview.js \
|
||||
$(NULL)
|
||||
|
549
browser/modules/test/browser_SignInToWebsite.js
Normal file
549
browser/modules/test/browser_SignInToWebsite.js
Normal file
@ -0,0 +1,549 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* TO TEST:
|
||||
* - test state saved on doorhanger dismissal
|
||||
* - links to switch steps
|
||||
* - TOS and PP link clicks
|
||||
* - identityList is populated correctly
|
||||
*/
|
||||
|
||||
Services.prefs.setBoolPref("toolkit.identity.debug", true);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
|
||||
"resource://gre/modules/identity/Identity.jsm");
|
||||
|
||||
const TEST_ORIGIN = "https://example.com";
|
||||
const TEST_EMAIL = "user@example.com";
|
||||
|
||||
let gTestIndex = 0;
|
||||
let outerWinId = gBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
|
||||
|
||||
function NotificationBase(aNotId) {
|
||||
this.id = aNotId;
|
||||
}
|
||||
NotificationBase.prototype = {
|
||||
message: TEST_ORIGIN,
|
||||
mainAction: {
|
||||
label: "",
|
||||
callback: function() {
|
||||
this.mainActionClicked = true;
|
||||
}.bind(this),
|
||||
},
|
||||
secondaryActions: [],
|
||||
options: {
|
||||
"identity": {
|
||||
origin: TEST_ORIGIN,
|
||||
rpId: outerWinId,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let tests = [
|
||||
{
|
||||
name: "test_request_required_typed",
|
||||
|
||||
run: function() {
|
||||
setupRPFlow();
|
||||
this.notifyOptions = {
|
||||
rpId: outerWinId,
|
||||
origin: TEST_ORIGIN,
|
||||
};
|
||||
this.notifyObj = new NotificationBase("identity-request");
|
||||
Services.obs.notifyObservers({wrappedJSObject: this.notifyOptions},
|
||||
"identity-request", null);
|
||||
},
|
||||
|
||||
onShown: function(popup) {
|
||||
checkPopup(popup, this.notifyObj);
|
||||
let notification = popup.childNodes[0];
|
||||
|
||||
// Check identity popup state
|
||||
let state = notification.identity;
|
||||
ok(!state.typedEmail, "Nothing should be typed yet");
|
||||
ok(!state.selected, "Identity should not be selected yet");
|
||||
ok(!state.termsOfService, "No TOS specified");
|
||||
ok(!state.privacyPolicy, "No PP specified");
|
||||
is(state.step, 0, "Step should be persisted with default value");
|
||||
is(state.rpId, outerWinId, "Check rpId");
|
||||
is(state.origin, TEST_ORIGIN, "Check origin");
|
||||
|
||||
is(notification.step, 0, "Should be on the new email step");
|
||||
is(notification.chooseEmailLink.hidden, true, "Identity list is empty so link to list view should be hidden");
|
||||
is(notification.addEmailLink.parentElement.hidden, true, "We are already on the email input step so choose email pane should be hidden");
|
||||
is(notification.emailField.value, "", "Email field should default to empty on a new notification");
|
||||
let notifDoc = notification.ownerDocument;
|
||||
ok(notifDoc.getAnonymousElementByAttribute(notification, "anonid", "tos").hidden,
|
||||
"TOS link should be hidden");
|
||||
ok(notifDoc.getAnonymousElementByAttribute(notification, "anonid", "privacypolicy").hidden,
|
||||
"PP link should be hidden");
|
||||
|
||||
// Try to continue with a missing email address
|
||||
triggerMainCommand(popup);
|
||||
is(notification.throbber.style.visibility, "hidden", "is throbber visible");
|
||||
ok(!notification.button.disabled, "Button should not be disabled");
|
||||
is(window.gIdentitySelected, null, "Check no identity selected");
|
||||
|
||||
// Fill in an invalid email address and try again
|
||||
notification.emailField.value = "foo";
|
||||
triggerMainCommand(popup);
|
||||
is(notification.throbber.style.visibility, "hidden", "is throbber visible");
|
||||
ok(!notification.button.disabled, "Button should not be disabled");
|
||||
is(window.gIdentitySelected, null, "Check no identity selected");
|
||||
|
||||
// Fill in an email address and try again
|
||||
notification.emailField.value = TEST_EMAIL;
|
||||
triggerMainCommand(popup);
|
||||
is(window.gIdentitySelected.rpId, outerWinId, "Check identity selected rpId");
|
||||
is(window.gIdentitySelected.identity, TEST_EMAIL, "Check identity selected email");
|
||||
is(notification.identity.selected, TEST_EMAIL, "Check persisted email");
|
||||
is(notification.throbber.style.visibility, "visible", "is throbber visible");
|
||||
ok(notification.button.disabled, "Button should be disabled");
|
||||
ok(notification.emailField.disabled, "Email field should be disabled");
|
||||
ok(notification.identityList.disabled, "Identity list should be disabled");
|
||||
|
||||
PopupNotifications.getNotification("identity-request").remove();
|
||||
},
|
||||
|
||||
onHidden: function(popup) { },
|
||||
},
|
||||
{
|
||||
name: "test_request_optional",
|
||||
|
||||
run: function() {
|
||||
this.notifyOptions = {
|
||||
rpId: outerWinId,
|
||||
origin: TEST_ORIGIN,
|
||||
privacyPolicy: TEST_ORIGIN + "/pp.txt",
|
||||
termsOfService: TEST_ORIGIN + "/tos.tzt",
|
||||
};
|
||||
this.notifyObj = new NotificationBase("identity-request");
|
||||
Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
|
||||
"identity-request", null);
|
||||
},
|
||||
|
||||
onShown: function(popup) {
|
||||
checkPopup(popup, this.notifyObj);
|
||||
let notification = popup.childNodes[0];
|
||||
|
||||
// Check identity popup state
|
||||
let state = notification.identity;
|
||||
ok(!state.typedEmail, "Nothing should be typed yet");
|
||||
ok(!state.selected, "Identity should not be selected yet");
|
||||
is(state.termsOfService, this.notifyOptions.termsOfService, "Check TOS URL");
|
||||
is(state.privacyPolicy, this.notifyOptions.privacyPolicy, "Check PP URL");
|
||||
is(state.step, 0, "Step should be persisted with default value");
|
||||
is(state.rpId, outerWinId, "Check rpId");
|
||||
is(state.origin, TEST_ORIGIN, "Check origin");
|
||||
|
||||
is(notification.step, 0, "Should be on the new email step");
|
||||
is(notification.chooseEmailLink.hidden, true, "Identity list is empty so link to list view should be hidden");
|
||||
is(notification.addEmailLink.parentElement.hidden, true, "We are already on the email input step so choose email pane should be hidden");
|
||||
is(notification.emailField.value, "", "Email field should default to empty on a new notification");
|
||||
let notifDoc = notification.ownerDocument;
|
||||
let tosLink = notifDoc.getAnonymousElementByAttribute(notification, "anonid", "tos");
|
||||
ok(!tosLink.hidden, "TOS link should be visible");
|
||||
is(tosLink.href, this.notifyOptions.termsOfService, "Check TOS link URL");
|
||||
let ppLink = notifDoc.getAnonymousElementByAttribute(notification, "anonid", "privacypolicy");
|
||||
ok(!ppLink.hidden, "PP link should be visible");
|
||||
is(ppLink.href, this.notifyOptions.privacyPolicy, "Check PP link URL");
|
||||
|
||||
// Try to continue with a missing email address
|
||||
triggerMainCommand(popup);
|
||||
is(notification.throbber.style.visibility, "hidden", "is throbber visible");
|
||||
ok(!notification.button.disabled, "Button should not be disabled");
|
||||
is(window.gIdentitySelected, null, "Check no identity selected");
|
||||
|
||||
// Fill in an invalid email address and try again
|
||||
notification.emailField.value = "foo";
|
||||
triggerMainCommand(popup);
|
||||
is(notification.throbber.style.visibility, "hidden", "is throbber visible");
|
||||
ok(!notification.button.disabled, "Button should not be disabled");
|
||||
is(window.gIdentitySelected, null, "Check no identity selected");
|
||||
|
||||
// Fill in an email address and try again
|
||||
notification.emailField.value = TEST_EMAIL;
|
||||
triggerMainCommand(popup);
|
||||
is(window.gIdentitySelected.rpId, outerWinId, "Check identity selected rpId");
|
||||
is(window.gIdentitySelected.identity, TEST_EMAIL, "Check identity selected email");
|
||||
is(notification.identity.selected, TEST_EMAIL, "Check persisted email");
|
||||
is(notification.throbber.style.visibility, "visible", "is throbber visible");
|
||||
ok(notification.button.disabled, "Button should be disabled");
|
||||
ok(notification.emailField.disabled, "Email field should be disabled");
|
||||
ok(notification.identityList.disabled, "Identity list should be disabled");
|
||||
|
||||
PopupNotifications.getNotification("identity-request").remove();
|
||||
},
|
||||
|
||||
onHidden: function(popup) {},
|
||||
},
|
||||
{
|
||||
name: "test_login_state_changed",
|
||||
run: function () {
|
||||
this.notifyOptions = {
|
||||
rpId: outerWinId,
|
||||
};
|
||||
this.notifyObj = new NotificationBase("identity-logged-in");
|
||||
this.notifyObj.message = "Signed in as: user@example.com";
|
||||
this.notifyObj.mainAction.label = "Sign Out";
|
||||
this.notifyObj.mainAction.accessKey = "O";
|
||||
Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
|
||||
"identity-login-state-changed", TEST_EMAIL);
|
||||
executeSoon(function() {
|
||||
PopupNotifications.getNotification("identity-logged-in").anchorElement.click();
|
||||
});
|
||||
},
|
||||
|
||||
onShown: function(popup) {
|
||||
checkPopup(popup, this.notifyObj);
|
||||
|
||||
// Fire the notification that the user is no longer logged-in to close the UI.
|
||||
Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
|
||||
"identity-login-state-changed", null);
|
||||
},
|
||||
|
||||
onHidden: function(popup) {},
|
||||
},
|
||||
{
|
||||
name: "test_login_state_changed_logout",
|
||||
run: function () {
|
||||
this.notifyOptions = {
|
||||
rpId: outerWinId,
|
||||
};
|
||||
this.notifyObj = new NotificationBase("identity-logged-in");
|
||||
this.notifyObj.message = "Signed in as: user@example.com";
|
||||
this.notifyObj.mainAction.label = "Sign Out";
|
||||
this.notifyObj.mainAction.accessKey = "O";
|
||||
Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
|
||||
"identity-login-state-changed", TEST_EMAIL);
|
||||
executeSoon(function() {
|
||||
PopupNotifications.getNotification("identity-logged-in").anchorElement.click();
|
||||
});
|
||||
},
|
||||
|
||||
onShown: function(popup) {
|
||||
checkPopup(popup, this.notifyObj);
|
||||
|
||||
// This time trigger the Sign Out button and make sure the UI goes away.
|
||||
triggerMainCommand(popup);
|
||||
},
|
||||
|
||||
onHidden: function(popup) {},
|
||||
},
|
||||
];
|
||||
|
||||
function test_auth() {
|
||||
let notifyOptions = {
|
||||
provId: outerWinId,
|
||||
origin: TEST_ORIGIN,
|
||||
};
|
||||
|
||||
Services.obs.addObserver(function() {
|
||||
// prepare to send auth-complete and close the window
|
||||
let winCloseObs = new WindowObserver(function(closedWin) {
|
||||
info("closed window");
|
||||
finish();
|
||||
}, "domwindowclosed");
|
||||
Services.ww.registerNotification(winCloseObs);
|
||||
Services.obs.notifyObservers(null, "identity-auth-complete", IdentityService.IDP.authenticationFlowSet.authId);
|
||||
|
||||
}, "test-identity-auth-window", false);
|
||||
|
||||
let winObs = new WindowObserver(function(authWin) {
|
||||
ok(authWin, "Authentication window opened");
|
||||
ok(authWin.contentWindow.location);
|
||||
});
|
||||
|
||||
Services.ww.registerNotification(winObs);
|
||||
|
||||
Services.obs.notifyObservers({ wrappedJSObject: notifyOptions },
|
||||
"identity-auth", TEST_ORIGIN + "/auth");
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
registerCleanupFunction(cleanUp);
|
||||
|
||||
let sitw = {};
|
||||
Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
|
||||
|
||||
ok(sitw.SignInToWebsiteUX, "SignInToWebsiteUX object exists");
|
||||
|
||||
// Replace implementation of ID Service functions for testing
|
||||
window.selectIdentity = sitw.SignInToWebsiteUX.selectIdentity;
|
||||
sitw.SignInToWebsiteUX.selectIdentity = function(aRpId, aIdentity) {
|
||||
info("Identity selected: " + aIdentity);
|
||||
window.gIdentitySelected = {rpId: aRpId, identity: aIdentity};
|
||||
};
|
||||
|
||||
window.setAuthenticationFlow = IdentityService.IDP.setAuthenticationFlow;
|
||||
IdentityService.IDP.setAuthenticationFlow = function(aAuthId, aProvId) {
|
||||
info("setAuthenticationFlow: " + aAuthId + " : " + aProvId);
|
||||
this.authenticationFlowSet = { authId: aAuthId, provId: aProvId };
|
||||
Services.obs.notifyObservers(null, "test-identity-auth-window", aAuthId);
|
||||
};
|
||||
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
// Cleanup between tests
|
||||
function resetState() {
|
||||
delete window.gIdentitySelected;
|
||||
delete IdentityService.IDP.authenticationFlowSet;
|
||||
IdentityService.reset();
|
||||
}
|
||||
|
||||
// Cleanup after all tests
|
||||
function cleanUp() {
|
||||
info("cleanup");
|
||||
resetState();
|
||||
|
||||
for (let topic in gActiveObservers)
|
||||
Services.obs.removeObserver(gActiveObservers[topic], topic);
|
||||
for (let eventName in gActiveListeners)
|
||||
PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false);
|
||||
delete IdentityService.RP._rpFlows[outerWinId];
|
||||
|
||||
// Put the JSM functions back to how they were
|
||||
IdentityService.IDP.setAuthenticationFlow = window.setAuthenticationFlow;
|
||||
delete window.setAuthenticationFlow;
|
||||
|
||||
let sitw = {};
|
||||
Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
|
||||
sitw.SignInToWebsiteUX.selectIdentity = window.selectIdentity;
|
||||
delete window.selectIdentity;
|
||||
|
||||
Services.prefs.clearUserPref("toolkit.identity.debug");
|
||||
}
|
||||
|
||||
let gActiveListeners = {};
|
||||
let gActiveObservers = {};
|
||||
let gShownState = {};
|
||||
|
||||
function runNextTest() {
|
||||
let nextTest = tests[gTestIndex];
|
||||
|
||||
function goNext() {
|
||||
resetState();
|
||||
if (++gTestIndex == tests.length)
|
||||
executeSoon(test_auth);
|
||||
else
|
||||
executeSoon(runNextTest);
|
||||
}
|
||||
|
||||
function addObserver(topic) {
|
||||
function observer() {
|
||||
Services.obs.removeObserver(observer, "PopupNotifications-" + topic);
|
||||
delete gActiveObservers["PopupNotifications-" + topic];
|
||||
|
||||
info("[Test #" + gTestIndex + "] observer for " + topic + " called");
|
||||
nextTest[topic]();
|
||||
goNext();
|
||||
}
|
||||
Services.obs.addObserver(observer, "PopupNotifications-" + topic, false);
|
||||
gActiveObservers["PopupNotifications-" + topic] = observer;
|
||||
}
|
||||
|
||||
if (nextTest.backgroundShow) {
|
||||
addObserver("backgroundShow");
|
||||
} else if (nextTest.updateNotShowing) {
|
||||
addObserver("updateNotShowing");
|
||||
} else {
|
||||
doOnPopupEvent("popupshowing", function () {
|
||||
info("[Test #" + gTestIndex + "] popup showing");
|
||||
});
|
||||
doOnPopupEvent("popupshown", function () {
|
||||
gShownState[gTestIndex] = true;
|
||||
info("[Test #" + gTestIndex + "] popup shown");
|
||||
nextTest.onShown(this);
|
||||
});
|
||||
|
||||
// We allow multiple onHidden functions to be defined in an array. They're
|
||||
// called in the order they appear.
|
||||
let onHiddenArray = nextTest.onHidden instanceof Array ?
|
||||
nextTest.onHidden :
|
||||
[nextTest.onHidden];
|
||||
doOnPopupEvent("popuphidden", function () {
|
||||
if (!gShownState[gTestIndex]) {
|
||||
// TODO: needed?
|
||||
info("Popup from test " + gTestIndex + " was hidden before its popupshown fired");
|
||||
}
|
||||
|
||||
let onHidden = onHiddenArray.shift();
|
||||
info("[Test #" + gTestIndex + "] popup hidden (" + onHiddenArray.length + " hides remaining)");
|
||||
executeSoon(function () {
|
||||
onHidden.call(nextTest, this);
|
||||
if (!onHiddenArray.length)
|
||||
goNext();
|
||||
}.bind(this));
|
||||
}, onHiddenArray.length);
|
||||
info("[Test #" + gTestIndex + "] added listeners; panel state: " + PopupNotifications.isPanelOpen);
|
||||
}
|
||||
|
||||
info("[Test #" + gTestIndex + "] running test");
|
||||
nextTest.run();
|
||||
}
|
||||
|
||||
function doOnPopupEvent(eventName, callback, numExpected) {
|
||||
gActiveListeners[eventName] = function (event) {
|
||||
if (event.target != PopupNotifications.panel)
|
||||
return;
|
||||
if (typeof(numExpected) === "number")
|
||||
numExpected--;
|
||||
if (!numExpected) {
|
||||
PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false);
|
||||
delete gActiveListeners[eventName];
|
||||
}
|
||||
|
||||
callback.call(PopupNotifications.panel);
|
||||
};
|
||||
PopupNotifications.panel.addEventListener(eventName, gActiveListeners[eventName], false);
|
||||
}
|
||||
|
||||
function checkPopup(popup, notificationObj) {
|
||||
info("[Test #" + gTestIndex + "] checking popup");
|
||||
|
||||
let notifications = popup.childNodes;
|
||||
is(notifications.length, 1, "only one notification displayed");
|
||||
let notification = notifications[0];
|
||||
let icon = document.getAnonymousElementByAttribute(notification, "class", "popup-notification-icon");
|
||||
is(notification.getAttribute("label"), notificationObj.message, "message matches");
|
||||
is(notification.id, notificationObj.id + "-notification", "id matches");
|
||||
if (notificationObj.id != "identity-request" && notificationObj.mainAction) {
|
||||
is(notification.getAttribute("buttonlabel"), notificationObj.mainAction.label, "main action label matches");
|
||||
is(notification.getAttribute("buttonaccesskey"), notificationObj.mainAction.accessKey, "main action accesskey matches");
|
||||
}
|
||||
let actualSecondaryActions = notification.childNodes;
|
||||
let secondaryActions = notificationObj.secondaryActions || [];
|
||||
let actualSecondaryActionsCount = actualSecondaryActions.length;
|
||||
if (secondaryActions.length) {
|
||||
let lastChild = actualSecondaryActions.item(actualSecondaryActions.length - 1);
|
||||
is(lastChild.tagName, "menuseparator", "menuseparator exists");
|
||||
actualSecondaryActionsCount--;
|
||||
}
|
||||
is(actualSecondaryActionsCount, secondaryActions.length, actualSecondaryActions.length + " secondary actions");
|
||||
secondaryActions.forEach(function (a, i) {
|
||||
is(actualSecondaryActions[i].getAttribute("label"), a.label, "label for secondary action " + i + " matches");
|
||||
is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey, "accessKey for secondary action " + i + " matches");
|
||||
});
|
||||
}
|
||||
|
||||
function triggerMainCommand(popup) {
|
||||
info("[Test #" + gTestIndex + "] triggering main command");
|
||||
let notifications = popup.childNodes;
|
||||
ok(notifications.length > 0, "at least one notification displayed");
|
||||
let notification = notifications[0];
|
||||
|
||||
// 20, 10 so that the inner button is hit
|
||||
EventUtils.synthesizeMouse(notification.button, 20, 10, {});
|
||||
}
|
||||
|
||||
function triggerSecondaryCommand(popup, index) {
|
||||
info("[Test #" + gTestIndex + "] triggering secondary command");
|
||||
let notifications = popup.childNodes;
|
||||
ok(notifications.length > 0, "at least one notification displayed");
|
||||
let notification = notifications[0];
|
||||
|
||||
notification.button.focus();
|
||||
|
||||
popup.addEventListener("popupshown", function () {
|
||||
popup.removeEventListener("popupshown", arguments.callee, false);
|
||||
|
||||
// Press down until the desired command is selected
|
||||
for (let i = 0; i <= index; i++)
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
|
||||
// Activate
|
||||
EventUtils.synthesizeKey("VK_ENTER", {});
|
||||
}, false);
|
||||
|
||||
// One down event to open the popup
|
||||
EventUtils.synthesizeKey("VK_DOWN", { altKey: (navigator.platform.indexOf("Mac") == -1) });
|
||||
}
|
||||
|
||||
function dismissNotification(popup) {
|
||||
info("[Test #" + gTestIndex + "] dismissing notification");
|
||||
executeSoon(function () {
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
});
|
||||
}
|
||||
|
||||
function partial(fn) {
|
||||
let args = Array.prototype.slice.call(arguments, 1);
|
||||
return function() {
|
||||
return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));
|
||||
};
|
||||
}
|
||||
|
||||
// create a mock "doc" object, which the Identity Service
|
||||
// uses as a pointer back into the doc object
|
||||
function mock_doc(aIdentity, aOrigin, aDoFunc) {
|
||||
let mockedDoc = {};
|
||||
mockedDoc.id = outerWinId;
|
||||
mockedDoc.loggedInEmail = aIdentity;
|
||||
mockedDoc.origin = aOrigin;
|
||||
mockedDoc['do'] = aDoFunc;
|
||||
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');
|
||||
|
||||
return mockedDoc;
|
||||
}
|
||||
|
||||
// takes a list of functions and returns a function that
|
||||
// when called the first time, calls the first func,
|
||||
// then the next time the second, etc.
|
||||
function call_sequentially() {
|
||||
let numCalls = 0;
|
||||
let funcs = arguments;
|
||||
|
||||
return function() {
|
||||
if (!funcs[numCalls]) {
|
||||
let argString = Array.prototype.slice.call(arguments).join(",");
|
||||
ok(false, "Too many calls: " + argString);
|
||||
return;
|
||||
}
|
||||
funcs[numCalls].apply(funcs[numCalls], arguments);
|
||||
numCalls += 1;
|
||||
};
|
||||
}
|
||||
|
||||
function setupRPFlow(aIdentity) {
|
||||
IdentityService.RP.watch(mock_doc(aIdentity, TEST_ORIGIN, call_sequentially(
|
||||
function(action, params) {
|
||||
is(action, "ready", "1st callback");
|
||||
is(params, null);
|
||||
},
|
||||
function(action, params) {
|
||||
is(action, "logout", "2nd callback");
|
||||
is(params, null);
|
||||
},
|
||||
function(action, params) {
|
||||
is(action, "ready", "3rd callback");
|
||||
is(params, null);
|
||||
}
|
||||
)));
|
||||
}
|
||||
|
||||
function WindowObserver(aCallback, aObserveTopic = "domwindowopened") {
|
||||
this.observe = function(aSubject, aTopic, aData) {
|
||||
if (aTopic != aObserveTopic) {
|
||||
return;
|
||||
}
|
||||
info(aObserveTopic);
|
||||
Services.ww.unregisterNotification(this);
|
||||
|
||||
SimpleTest.executeSoon(function() {
|
||||
let domWin = aSubject.QueryInterface(Ci.nsIDOMWindow);
|
||||
aCallback(domWin);
|
||||
});
|
||||
};
|
||||
}
|
@ -1240,6 +1240,10 @@ toolbar[iconsize="small"] #feed-button {
|
||||
list-style-image: url(chrome://global/skin/icons/information-16.png);
|
||||
}
|
||||
|
||||
#identity-notification-icon {
|
||||
list-style-image: url(chrome://mozapps/skin/profile/profileicon.png);
|
||||
}
|
||||
|
||||
#geo-notification-icon {
|
||||
list-style-image: url(chrome://browser/skin/Geolocation-16.png);
|
||||
}
|
||||
|
@ -2369,12 +2369,19 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
||||
list-style-image: url(chrome://global/skin/icons/information-16.png);
|
||||
}
|
||||
|
||||
#identity-notification-icon {
|
||||
list-style-image: url(chrome://mozapps/skin/profile/profileicon.png);
|
||||
}
|
||||
|
||||
#geo-notification-icon {
|
||||
list-style-image: url(chrome://browser/skin/Geolocation-16.png);
|
||||
}
|
||||
|
||||
.geolocation-text-link {
|
||||
#notification-popup .text-link {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.geolocation-text-link {
|
||||
-moz-margin-start: 0; /* override default label margin to match description margin */
|
||||
}
|
||||
|
||||
|
@ -2375,6 +2375,10 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
|
||||
list-style-image: url(chrome://global/skin/icons/information-16.png);
|
||||
}
|
||||
|
||||
#identity-notification-icon {
|
||||
list-style-image: url(chrome://mozapps/skin/profile/profileicon.png);
|
||||
}
|
||||
|
||||
#geo-notification-icon {
|
||||
list-style-image: url(chrome://browser/skin/Geolocation-16.png);
|
||||
}
|
||||
|
@ -210,13 +210,13 @@ IdentityRelyingParty.prototype = {
|
||||
*/
|
||||
request: function request(aRPId, aOptions) {
|
||||
log("request: rpId:", aRPId);
|
||||
let rp = this._rpFlows[aRPId];
|
||||
|
||||
// Notify UX to display identity picker.
|
||||
// Pass the doc id to UX so it can pass it back to us later.
|
||||
let options = {rpId: aRPId};
|
||||
let options = {rpId: aRPId, origin: rp.origin};
|
||||
|
||||
// Append URLs after resolving
|
||||
let rp = this._rpFlows[aRPId];
|
||||
let baseURI = Services.io.newURI(rp.origin, null, null);
|
||||
for (let optionName of ["privacyPolicy", "termsOfService"]) {
|
||||
if (aOptions[optionName]) {
|
||||
|
@ -217,7 +217,7 @@ function WindowObserver(aCallback) {
|
||||
|
||||
let domWin = aSubject.QueryInterface(Ci.nsIDOMWindow);
|
||||
ok(!domWin, "No window should be opened");
|
||||
SimpleTest.executesoon(function() {
|
||||
SimpleTest.executeSoon(function() {
|
||||
info("Closing opened window");
|
||||
domWin.close();
|
||||
aCallback();
|
||||
|
@ -29,6 +29,17 @@ const Services = Cu.import("resource://gre/modules/Services.jsm").Services;
|
||||
SpecialPowers.setBoolPref("toolkit.identity.debug", true);
|
||||
SpecialPowers.setBoolPref("dom.identity.enabled", true);
|
||||
|
||||
// Shutdown the UX if it exists so that it won't interfere with tests by also responding to
|
||||
// observer notifications.
|
||||
try {
|
||||
const SignInToWebsiteUX = Cu.import("resource:///modules/SignInToWebsite.jsm").SignInToWebsiteUX;
|
||||
if (SignInToWebsiteUX) {
|
||||
SignInToWebsiteUX.uninit();
|
||||
}
|
||||
} catch (ex) {
|
||||
// The module doesn't exist
|
||||
}
|
||||
|
||||
const jwcrypto = Cu.import("resource://gre/modules/identity/jwcrypto.jsm").jwcrypto;
|
||||
const IdentityStore = Cu.import("resource://gre/modules/identity/IdentityStore.jsm").IdentityStore;
|
||||
const RelyingParty = Cu.import("resource://gre/modules/identity/RelyingParty.jsm").RelyingParty;
|
||||
@ -175,6 +186,15 @@ function cleanup() {
|
||||
resetState();
|
||||
SpecialPowers.clearUserPref("toolkit.identity.debug");
|
||||
SpecialPowers.clearUserPref("dom.identity.enabled");
|
||||
// Re-init the UX that we uninit
|
||||
try {
|
||||
const SignInToWebsiteUX = Cu.import("resource:///modules/SignInToWebsite.jsm").SignInToWebsiteUX;
|
||||
if (SignInToWebsiteUX) {
|
||||
SignInToWebsiteUX.init();
|
||||
}
|
||||
} catch (ex) {
|
||||
// The module doesn't exist
|
||||
}
|
||||
}
|
||||
|
||||
var TESTS = [];
|
||||
|
@ -29,7 +29,7 @@ SimpleTest.waitForExplicitFinish();
|
||||
const DOMIdentity = Cu.import("resource://gre/modules/DOMIdentity.jsm")
|
||||
.DOMIdentity;
|
||||
let outerWinId = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
|
||||
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
|
||||
|
||||
// Reset the DOM state then run the next test
|
||||
function run_next_rp_test() {
|
||||
|
Loading…
Reference in New Issue
Block a user