Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | this.EXPORTED_SYMBOLS = ["SignInToWebsiteUX"]; |
michael@0 | 8 | |
michael@0 | 9 | const Cc = Components.classes; |
michael@0 | 10 | const Ci = Components.interfaces; |
michael@0 | 11 | const Cu = Components.utils; |
michael@0 | 12 | |
michael@0 | 13 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 14 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 15 | |
michael@0 | 16 | XPCOMUtils.defineLazyModuleGetter(this, "IdentityService", |
michael@0 | 17 | "resource://gre/modules/identity/Identity.jsm"); |
michael@0 | 18 | |
michael@0 | 19 | XPCOMUtils.defineLazyModuleGetter(this, "Logger", |
michael@0 | 20 | "resource://gre/modules/identity/LogUtils.jsm"); |
michael@0 | 21 | |
michael@0 | 22 | function log(...aMessageArgs) { |
michael@0 | 23 | Logger.log.apply(Logger, ["SignInToWebsiteUX"].concat(aMessageArgs)); |
michael@0 | 24 | } |
michael@0 | 25 | |
michael@0 | 26 | this.SignInToWebsiteUX = { |
michael@0 | 27 | |
michael@0 | 28 | init: function SignInToWebsiteUX_init() { |
michael@0 | 29 | |
michael@0 | 30 | Services.obs.addObserver(this, "identity-request", false); |
michael@0 | 31 | Services.obs.addObserver(this, "identity-auth", false); |
michael@0 | 32 | Services.obs.addObserver(this, "identity-auth-complete", false); |
michael@0 | 33 | Services.obs.addObserver(this, "identity-login-state-changed", false); |
michael@0 | 34 | }, |
michael@0 | 35 | |
michael@0 | 36 | uninit: function SignInToWebsiteUX_uninit() { |
michael@0 | 37 | Services.obs.removeObserver(this, "identity-request"); |
michael@0 | 38 | Services.obs.removeObserver(this, "identity-auth"); |
michael@0 | 39 | Services.obs.removeObserver(this, "identity-auth-complete"); |
michael@0 | 40 | Services.obs.removeObserver(this, "identity-login-state-changed"); |
michael@0 | 41 | }, |
michael@0 | 42 | |
michael@0 | 43 | observe: function SignInToWebsiteUX_observe(aSubject, aTopic, aData) { |
michael@0 | 44 | log("observe: received", aTopic, "with", aData, "for", aSubject); |
michael@0 | 45 | let options = null; |
michael@0 | 46 | if (aSubject) { |
michael@0 | 47 | options = aSubject.wrappedJSObject; |
michael@0 | 48 | } |
michael@0 | 49 | switch(aTopic) { |
michael@0 | 50 | case "identity-request": |
michael@0 | 51 | this.requestLogin(options); |
michael@0 | 52 | break; |
michael@0 | 53 | case "identity-auth": |
michael@0 | 54 | this._openAuthenticationUI(aData, options); |
michael@0 | 55 | break; |
michael@0 | 56 | case "identity-auth-complete": |
michael@0 | 57 | this._closeAuthenticationUI(aData); |
michael@0 | 58 | break; |
michael@0 | 59 | case "identity-login-state-changed": |
michael@0 | 60 | let emailAddress = aData; |
michael@0 | 61 | if (emailAddress) { |
michael@0 | 62 | this._removeRequestUI(options); |
michael@0 | 63 | this._showLoggedInUI(emailAddress, options); |
michael@0 | 64 | } else { |
michael@0 | 65 | this._removeLoggedInUI(options); |
michael@0 | 66 | } |
michael@0 | 67 | break; |
michael@0 | 68 | default: |
michael@0 | 69 | Logger.reportError("SignInToWebsiteUX", "Unknown observer notification:", aTopic); |
michael@0 | 70 | break; |
michael@0 | 71 | } |
michael@0 | 72 | }, |
michael@0 | 73 | |
michael@0 | 74 | /** |
michael@0 | 75 | * The website is requesting login so the user must choose an identity to use. |
michael@0 | 76 | */ |
michael@0 | 77 | requestLogin: function SignInToWebsiteUX_requestLogin(aOptions) { |
michael@0 | 78 | let windowID = aOptions.rpId; |
michael@0 | 79 | log("requestLogin", aOptions); |
michael@0 | 80 | let [chromeWin, browserEl] = this._getUIForWindowID(windowID); |
michael@0 | 81 | |
michael@0 | 82 | // message is not shown in the UI but is required |
michael@0 | 83 | let message = aOptions.origin; |
michael@0 | 84 | let mainAction = { |
michael@0 | 85 | label: chromeWin.gNavigatorBundle.getString("identity.next.label"), |
michael@0 | 86 | accessKey: chromeWin.gNavigatorBundle.getString("identity.next.accessKey"), |
michael@0 | 87 | callback: function() {}, // required |
michael@0 | 88 | }; |
michael@0 | 89 | let options = { |
michael@0 | 90 | identity: { |
michael@0 | 91 | origin: aOptions.origin, |
michael@0 | 92 | }, |
michael@0 | 93 | }; |
michael@0 | 94 | let secondaryActions = []; |
michael@0 | 95 | |
michael@0 | 96 | // add some extra properties to the notification to store some identity-related state |
michael@0 | 97 | for (let opt in aOptions) { |
michael@0 | 98 | options.identity[opt] = aOptions[opt]; |
michael@0 | 99 | } |
michael@0 | 100 | log("requestLogin: rpId: ", options.identity.rpId); |
michael@0 | 101 | |
michael@0 | 102 | chromeWin.PopupNotifications.show(browserEl, "identity-request", message, |
michael@0 | 103 | "identity-notification-icon", mainAction, |
michael@0 | 104 | [], options); |
michael@0 | 105 | }, |
michael@0 | 106 | |
michael@0 | 107 | /** |
michael@0 | 108 | * Get the list of possible identities to login to the given origin. |
michael@0 | 109 | */ |
michael@0 | 110 | getIdentitiesForSite: function SignInToWebsiteUX_getIdentitiesForSite(aOrigin) { |
michael@0 | 111 | return IdentityService.RP.getIdentitiesForSite(aOrigin); |
michael@0 | 112 | }, |
michael@0 | 113 | |
michael@0 | 114 | /** |
michael@0 | 115 | * User chose a new or existing identity from the doorhanger after a request() call |
michael@0 | 116 | */ |
michael@0 | 117 | selectIdentity: function SignInToWebsiteUX_selectIdentity(aRpId, aIdentity) { |
michael@0 | 118 | log("selectIdentity: rpId: ", aRpId, " identity: ", aIdentity); |
michael@0 | 119 | IdentityService.selectIdentity(aRpId, aIdentity); |
michael@0 | 120 | }, |
michael@0 | 121 | |
michael@0 | 122 | // Private |
michael@0 | 123 | |
michael@0 | 124 | /** |
michael@0 | 125 | * Return the chrome window and <browser> for the given outer window ID. |
michael@0 | 126 | */ |
michael@0 | 127 | _getUIForWindowID: function(aWindowID) { |
michael@0 | 128 | let content = Services.wm.getOuterWindowWithId(aWindowID); |
michael@0 | 129 | if (content) { |
michael@0 | 130 | let browser = content.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 131 | .getInterface(Ci.nsIWebNavigation) |
michael@0 | 132 | .QueryInterface(Ci.nsIDocShell).chromeEventHandler; |
michael@0 | 133 | let chromeWin = browser.ownerDocument.defaultView; |
michael@0 | 134 | return [chromeWin, browser]; |
michael@0 | 135 | } |
michael@0 | 136 | |
michael@0 | 137 | Logger.reportError("SignInToWebsiteUX", "no content"); |
michael@0 | 138 | return [null, null]; |
michael@0 | 139 | }, |
michael@0 | 140 | |
michael@0 | 141 | /** |
michael@0 | 142 | * Open UI with a content frame displaying aAuthURI so that the user can authenticate with their |
michael@0 | 143 | * IDP. Then tell Identity.jsm the identifier for the window so that it knows that the DOM API |
michael@0 | 144 | * calls are for this authentication flow. |
michael@0 | 145 | */ |
michael@0 | 146 | _openAuthenticationUI: function _openAuthenticationUI(aAuthURI, aContext) { |
michael@0 | 147 | // Open a tab/window with aAuthURI with an identifier (aID) attached so that the DOM APIs know this is an auth. window. |
michael@0 | 148 | let chromeWin = Services.wm.getMostRecentWindow('navigator:browser'); |
michael@0 | 149 | let features = "chrome=false,width=640,height=480,centerscreen,location=yes,resizable=yes,scrollbars=yes,status=yes"; |
michael@0 | 150 | log("aAuthURI: ", aAuthURI); |
michael@0 | 151 | let authWin = Services.ww.openWindow(chromeWin, "about:blank", "", features, null); |
michael@0 | 152 | let windowID = authWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID; |
michael@0 | 153 | log("authWin outer id: ", windowID); |
michael@0 | 154 | |
michael@0 | 155 | let provId = aContext.provId; |
michael@0 | 156 | // Tell the ID service about the id before loading the url |
michael@0 | 157 | IdentityService.IDP.setAuthenticationFlow(windowID, provId); |
michael@0 | 158 | |
michael@0 | 159 | authWin.location = aAuthURI; |
michael@0 | 160 | }, |
michael@0 | 161 | |
michael@0 | 162 | _closeAuthenticationUI: function _closeAuthenticationUI(aAuthId) { |
michael@0 | 163 | log("_closeAuthenticationUI:", aAuthId); |
michael@0 | 164 | let [chromeWin, browserEl] = this._getUIForWindowID(aAuthId); |
michael@0 | 165 | if (chromeWin) |
michael@0 | 166 | chromeWin.close(); |
michael@0 | 167 | else |
michael@0 | 168 | Logger.reportError("SignInToWebsite", "Could not close window with ID", aAuthId); |
michael@0 | 169 | }, |
michael@0 | 170 | |
michael@0 | 171 | /** |
michael@0 | 172 | * Show a doorhanger indicating the currently logged-in user. |
michael@0 | 173 | */ |
michael@0 | 174 | _showLoggedInUI: function _showLoggedInUI(aIdentity, aContext) { |
michael@0 | 175 | let windowID = aContext.rpId; |
michael@0 | 176 | log("_showLoggedInUI for ", windowID); |
michael@0 | 177 | let [chromeWin, browserEl] = this._getUIForWindowID(windowID); |
michael@0 | 178 | |
michael@0 | 179 | let message = chromeWin.gNavigatorBundle.getFormattedString("identity.loggedIn.description", |
michael@0 | 180 | [aIdentity]); |
michael@0 | 181 | let mainAction = { |
michael@0 | 182 | label: chromeWin.gNavigatorBundle.getString("identity.loggedIn.signOut.label"), |
michael@0 | 183 | accessKey: chromeWin.gNavigatorBundle.getString("identity.loggedIn.signOut.accessKey"), |
michael@0 | 184 | callback: function() { |
michael@0 | 185 | log("sign out callback fired"); |
michael@0 | 186 | IdentityService.RP.logout(windowID); |
michael@0 | 187 | }, |
michael@0 | 188 | }; |
michael@0 | 189 | let secondaryActions = []; |
michael@0 | 190 | let options = { |
michael@0 | 191 | dismissed: true, |
michael@0 | 192 | }; |
michael@0 | 193 | let loggedInNot = chromeWin.PopupNotifications.show(browserEl, "identity-logged-in", message, |
michael@0 | 194 | "identity-notification-icon", mainAction, |
michael@0 | 195 | secondaryActions, options); |
michael@0 | 196 | loggedInNot.rpId = windowID; |
michael@0 | 197 | }, |
michael@0 | 198 | |
michael@0 | 199 | /** |
michael@0 | 200 | * Remove the doorhanger indicating the currently logged-in user. |
michael@0 | 201 | */ |
michael@0 | 202 | _removeLoggedInUI: function _removeLoggedInUI(aContext) { |
michael@0 | 203 | let windowID = aContext.rpId; |
michael@0 | 204 | log("_removeLoggedInUI for ", windowID); |
michael@0 | 205 | if (!windowID) |
michael@0 | 206 | throw "_removeLoggedInUI: Invalid RP ID"; |
michael@0 | 207 | let [chromeWin, browserEl] = this._getUIForWindowID(windowID); |
michael@0 | 208 | |
michael@0 | 209 | let loggedInNot = chromeWin.PopupNotifications.getNotification("identity-logged-in", browserEl); |
michael@0 | 210 | if (loggedInNot) |
michael@0 | 211 | chromeWin.PopupNotifications.remove(loggedInNot); |
michael@0 | 212 | }, |
michael@0 | 213 | |
michael@0 | 214 | /** |
michael@0 | 215 | * Remove the doorhanger indicating the currently logged-in user. |
michael@0 | 216 | */ |
michael@0 | 217 | _removeRequestUI: function _removeRequestUI(aContext) { |
michael@0 | 218 | let windowID = aContext.rpId; |
michael@0 | 219 | log("_removeRequestUI for ", windowID); |
michael@0 | 220 | let [chromeWin, browserEl] = this._getUIForWindowID(windowID); |
michael@0 | 221 | |
michael@0 | 222 | let requestNot = chromeWin.PopupNotifications.getNotification("identity-request", browserEl); |
michael@0 | 223 | if (requestNot) |
michael@0 | 224 | chromeWin.PopupNotifications.remove(requestNot); |
michael@0 | 225 | }, |
michael@0 | 226 | |
michael@0 | 227 | }; |