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
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
9 const PREF_DEBUG = "toolkit.identity.debug";
10 const PREF_ENABLED = "dom.identity.enabled";
12 // Bug 822450: Workaround for Bug 821740. When testing with marionette,
13 // relax navigator.id.request's requirement that it be handling native
14 // events. Synthetic marionette events are ok.
15 const PREF_SYNTHETIC_EVENTS_OK = "dom.identity.syntheticEventsOk";
17 // Maximum length of a string that will go through IPC
18 const MAX_STRING_LENGTH = 2048;
19 // Maximum number of times navigator.id.request can be called for a document
20 const MAX_RP_CALLS = 100;
22 Cu.import("resource://gre/modules/Services.jsm");
23 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
25 XPCOMUtils.defineLazyModuleGetter(this, "checkDeprecated",
26 "resource://gre/modules/identity/IdentityUtils.jsm");
27 XPCOMUtils.defineLazyModuleGetter(this, "checkRenamed",
28 "resource://gre/modules/identity/IdentityUtils.jsm");
29 XPCOMUtils.defineLazyModuleGetter(this, "objectCopy",
30 "resource://gre/modules/identity/IdentityUtils.jsm");
32 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
33 "@mozilla.org/uuid-generator;1",
34 "nsIUUIDGenerator");
36 // This is the child process corresponding to nsIDOMIdentity
37 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
38 "@mozilla.org/childprocessmessagemanager;1",
39 "nsIMessageSender");
42 const ERRORS = {
43 "ERROR_NOT_AUTHORIZED_FOR_FIREFOX_ACCOUNTS":
44 "Only privileged and certified apps may use Firefox Accounts",
45 "ERROR_INVALID_ASSERTION_AUDIENCE":
46 "Assertion audience may not differ from origin",
47 "ERROR_REQUEST_WHILE_NOT_HANDLING_USER_INPUT":
48 "The request() method may only be invoked when handling user input",
49 };
51 function nsDOMIdentity(aIdentityInternal) {
52 this._identityInternal = aIdentityInternal;
53 }
54 nsDOMIdentity.prototype = {
55 __exposedProps__: {
56 // Relying Party (RP)
57 watch: 'r',
58 request: 'r',
59 logout: 'r',
60 get: 'r',
61 getVerifiedEmail: 'r',
63 // Provisioning
64 beginProvisioning: 'r',
65 genKeyPair: 'r',
66 registerCertificate: 'r',
67 raiseProvisioningFailure: 'r',
69 // Authentication
70 beginAuthentication: 'r',
71 completeAuthentication: 'r',
72 raiseAuthenticationFailure: 'r'
73 },
75 // require native events unless syntheticEventsOk is set
76 get nativeEventsRequired() {
77 if (Services.prefs.prefHasUserValue(PREF_SYNTHETIC_EVENTS_OK) &&
78 (Services.prefs.getPrefType(PREF_SYNTHETIC_EVENTS_OK) ===
79 Ci.nsIPrefBranch.PREF_BOOL)) {
80 return !Services.prefs.getBoolPref(PREF_SYNTHETIC_EVENTS_OK);
81 }
82 return true;
83 },
85 reportErrors: function(message) {
86 let onerror = function() {};
87 if (this._rpWatcher && this._rpWatcher.onerror) {
88 onerror = this._rpWatcher.onerror;
89 }
91 message.errors.forEach((error) => {
92 // Report an error string to content
93 Cu.reportError(ERRORS[error]);
95 // Report error code to RP callback, if available
96 onerror(error);
97 });
98 },
100 /**
101 * Relying Party (RP) APIs
102 */
104 watch: function nsDOMIdentity_watch(aOptions = {}) {
105 if (this._rpWatcher) {
106 // For the initial release of Firefox Accounts, we support callers who
107 // invoke watch() either for Firefox Accounts, or Persona, but not both.
108 // In the future, we may wish to support the dual invocation (say, for
109 // packaged apps so they can sign users in who reject the app's request
110 // to sign in with their Firefox Accounts identity).
111 throw new Error("navigator.id.watch was already called");
112 }
114 assertCorrectCallbacks(aOptions);
116 let message = this.DOMIdentityMessage(aOptions);
118 // loggedInUser vs loggedInEmail
119 // https://developer.mozilla.org/en-US/docs/DOM/navigator.id.watch
120 // This parameter, loggedInUser, was renamed from loggedInEmail in early
121 // September, 2012. Both names will continue to work for the time being,
122 // but code should be changed to use loggedInUser instead.
123 checkRenamed(aOptions, "loggedInEmail", "loggedInUser");
124 message["loggedInUser"] = aOptions["loggedInUser"];
126 let emailType = typeof(aOptions["loggedInUser"]);
127 if (aOptions["loggedInUser"] && aOptions["loggedInUser"] !== "undefined") {
128 if (emailType !== "string") {
129 throw new Error("loggedInUser must be a String or null");
130 }
132 // TODO: Bug 767610 - check email format.
133 // See HTMLInputElement::IsValidEmailAddress
134 if (aOptions["loggedInUser"].indexOf("@") == -1
135 || aOptions["loggedInUser"].length > MAX_STRING_LENGTH) {
136 throw new Error("loggedInUser is not valid");
137 }
138 // Set loggedInUser in this block that "undefined" doesn't get through.
139 message.loggedInUser = aOptions.loggedInUser;
140 }
141 this._log("loggedInUser: " + message.loggedInUser);
143 this._rpWatcher = aOptions;
144 this._rpWatcher.audience = message.audience;
146 if (message.errors.length) {
147 this.reportErrors(message);
148 // We don't delete the rpWatcher object, because we don't want the
149 // broken client to be able to call watch() any more. It's broken.
150 return;
151 }
152 this._identityInternal._mm.sendAsyncMessage("Identity:RP:Watch", message);
153 },
155 request: function nsDOMIdentity_request(aOptions = {}) {
156 this._log("request: " + JSON.stringify(aOptions));
158 // Has the caller called watch() before this?
159 if (!this._rpWatcher) {
160 throw new Error("navigator.id.request called before navigator.id.watch");
161 }
162 if (this._rpCalls > MAX_RP_CALLS) {
163 throw new Error("navigator.id.request called too many times");
164 }
166 let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
167 .getInterface(Ci.nsIDOMWindowUtils);
169 let message = this.DOMIdentityMessage(aOptions);
171 // We permit calling of request() outside of a user input handler only when
172 // a certified or privileged app is calling, or when we are handling the
173 // (deprecated) get() or getVerifiedEmail() calls, which make use of an RP
174 // context marked as _internal.
176 if (!aOptions._internal &&
177 this._appStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED &&
178 this._appStatus !== Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
180 // If the caller is not special in one of those ways, see if the user has
181 // preffed on 'syntheticEventsOk' (useful for testing); otherwise, if
182 // this is a non-native event, reject it.
183 let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
184 .getInterface(Ci.nsIDOMWindowUtils);
186 if (!util.isHandlingUserInput && this.nativeEventsRequired) {
187 message.errors.push("ERROR_REQUEST_WHILE_NOT_HANDLING_USER_INPUT");
188 }
189 }
191 // Report and fail hard on any errors.
192 if (message.errors.length) {
193 this.reportErrors(message);
194 return;
195 }
197 if (aOptions) {
198 // Optional string properties
199 let optionalStringProps = ["privacyPolicy", "termsOfService"];
200 for (let propName of optionalStringProps) {
201 if (!aOptions[propName] || aOptions[propName] === "undefined")
202 continue;
203 if (typeof(aOptions[propName]) !== "string") {
204 throw new Error(propName + " must be a string representing a URL.");
205 }
206 if (aOptions[propName].length > MAX_STRING_LENGTH) {
207 throw new Error(propName + " is invalid.");
208 }
209 message[propName] = aOptions[propName];
210 }
212 if (aOptions["oncancel"]
213 && typeof(aOptions["oncancel"]) !== "function") {
214 throw new Error("oncancel is not a function");
215 } else {
216 // Store optional cancel callback for later.
217 this._onCancelRequestCallback = aOptions.oncancel;
218 }
219 }
221 this._rpCalls++;
222 this._identityInternal._mm.sendAsyncMessage("Identity:RP:Request", message);
223 },
225 logout: function nsDOMIdentity_logout() {
226 if (!this._rpWatcher) {
227 throw new Error("navigator.id.logout called before navigator.id.watch");
228 }
229 if (this._rpCalls > MAX_RP_CALLS) {
230 throw new Error("navigator.id.logout called too many times");
231 }
233 this._rpCalls++;
234 let message = this.DOMIdentityMessage();
236 // Report and fail hard on any errors.
237 if (message.errors.length) {
238 this.reportErrors(message);
239 return;
240 }
242 this._identityInternal._mm.sendAsyncMessage("Identity:RP:Logout", message);
243 },
245 /*
246 * Get an assertion. This function is deprecated. RPs are
247 * encouraged to use the observer API instead (watch + request).
248 */
249 get: function nsDOMIdentity_get(aCallback, aOptions) {
250 var opts = {};
251 aOptions = aOptions || {};
253 // We use the observer API (watch + request) to implement get().
254 // Because the caller can call get() and getVerifiedEmail() as
255 // many times as they want, we lift the restriction that watch() can
256 // only be called once.
257 this._rpWatcher = null;
259 // This flag tells internal_api.js (in the shim) to record in the
260 // login parameters whether the assertion was acquired silently or
261 // with user interaction.
262 opts._internal = true;
264 opts.privacyPolicy = aOptions.privacyPolicy || undefined;
265 opts.termsOfService = aOptions.termsOfService || undefined;
266 opts.privacyURL = aOptions.privacyURL || undefined;
267 opts.tosURL = aOptions.tosURL || undefined;
268 opts.siteName = aOptions.siteName || undefined;
269 opts.siteLogo = aOptions.siteLogo || undefined;
271 opts.oncancel = function get_oncancel() {
272 if (aCallback) {
273 aCallback(null);
274 aCallback = null;
275 }
276 };
278 if (checkDeprecated(aOptions, "silent")) {
279 // Silent has been deprecated, do nothing. Placing the check here
280 // prevents the callback from being called twice, once with null and
281 // once after internalWatch has been called. See issue #1532:
282 // https://github.com/mozilla/browserid/issues/1532
283 if (aCallback) {
284 setTimeout(function() { aCallback(null); }, 0);
285 }
286 return;
287 }
289 // Get an assertion by using our observer api: watch + request.
290 var self = this;
291 this.watch({
292 _internal: true,
293 onlogin: function get_onlogin(assertion, internalParams) {
294 if (assertion && aCallback && internalParams && !internalParams.silent) {
295 aCallback(assertion);
296 aCallback = null;
297 }
298 },
299 onlogout: function get_onlogout() {},
300 onready: function get_onready() {
301 self.request(opts);
302 }
303 });
304 },
306 getVerifiedEmail: function nsDOMIdentity_getVerifiedEmail(aCallback) {
307 Cu.reportError("WARNING: getVerifiedEmail has been deprecated");
308 this.get(aCallback, {});
309 },
311 /**
312 * Identity Provider (IDP) Provisioning APIs
313 */
315 beginProvisioning: function nsDOMIdentity_beginProvisioning(aCallback) {
316 this._log("beginProvisioning");
317 if (this._beginProvisioningCallback) {
318 throw new Error("navigator.id.beginProvisioning already called.");
319 }
320 if (!aCallback || typeof(aCallback) !== "function") {
321 throw new Error("beginProvisioning callback is required.");
322 }
324 this._beginProvisioningCallback = aCallback;
325 this._identityInternal._mm.sendAsyncMessage("Identity:IDP:BeginProvisioning",
326 this.DOMIdentityMessage());
327 },
329 genKeyPair: function nsDOMIdentity_genKeyPair(aCallback) {
330 this._log("genKeyPair");
331 if (!this._beginProvisioningCallback) {
332 throw new Error("navigator.id.genKeyPair called outside of provisioning");
333 }
334 if (this._genKeyPairCallback) {
335 throw new Error("navigator.id.genKeyPair already called.");
336 }
337 if (!aCallback || typeof(aCallback) !== "function") {
338 throw new Error("genKeyPair callback is required.");
339 }
341 this._genKeyPairCallback = aCallback;
342 this._identityInternal._mm.sendAsyncMessage("Identity:IDP:GenKeyPair",
343 this.DOMIdentityMessage());
344 },
346 registerCertificate: function nsDOMIdentity_registerCertificate(aCertificate) {
347 this._log("registerCertificate");
348 if (!this._genKeyPairCallback) {
349 throw new Error("navigator.id.registerCertificate called outside of provisioning");
350 }
351 if (this._provisioningEnded) {
352 throw new Error("Provisioning already ended");
353 }
354 this._provisioningEnded = true;
356 let message = this.DOMIdentityMessage();
357 message.cert = aCertificate;
358 this._identityInternal._mm.sendAsyncMessage("Identity:IDP:RegisterCertificate", message);
359 },
361 raiseProvisioningFailure: function nsDOMIdentity_raiseProvisioningFailure(aReason) {
362 this._log("raiseProvisioningFailure '" + aReason + "'");
363 if (this._provisioningEnded) {
364 throw new Error("Provisioning already ended");
365 }
366 if (!aReason || typeof(aReason) != "string") {
367 throw new Error("raiseProvisioningFailure reason is required");
368 }
369 this._provisioningEnded = true;
371 let message = this.DOMIdentityMessage();
372 message.reason = aReason;
373 this._identityInternal._mm.sendAsyncMessage("Identity:IDP:ProvisioningFailure", message);
374 },
376 /**
377 * Identity Provider (IDP) Authentication APIs
378 */
380 beginAuthentication: function nsDOMIdentity_beginAuthentication(aCallback) {
381 this._log("beginAuthentication");
382 if (this._beginAuthenticationCallback) {
383 throw new Error("navigator.id.beginAuthentication already called.");
384 }
385 if (typeof(aCallback) !== "function") {
386 throw new Error("beginAuthentication callback is required.");
387 }
388 if (!aCallback || typeof(aCallback) !== "function") {
389 throw new Error("beginAuthentication callback is required.");
390 }
392 this._beginAuthenticationCallback = aCallback;
393 this._identityInternal._mm.sendAsyncMessage("Identity:IDP:BeginAuthentication",
394 this.DOMIdentityMessage());
395 },
397 completeAuthentication: function nsDOMIdentity_completeAuthentication() {
398 if (this._authenticationEnded) {
399 throw new Error("Authentication already ended");
400 }
401 if (!this._beginAuthenticationCallback) {
402 throw new Error("navigator.id.completeAuthentication called outside of authentication");
403 }
404 this._authenticationEnded = true;
406 this._identityInternal._mm.sendAsyncMessage("Identity:IDP:CompleteAuthentication",
407 this.DOMIdentityMessage());
408 },
410 raiseAuthenticationFailure: function nsDOMIdentity_raiseAuthenticationFailure(aReason) {
411 if (this._authenticationEnded) {
412 throw new Error("Authentication already ended");
413 }
414 if (!aReason || typeof(aReason) != "string") {
415 throw new Error("raiseProvisioningFailure reason is required");
416 }
418 let message = this.DOMIdentityMessage();
419 message.reason = aReason;
420 this._identityInternal._mm.sendAsyncMessage("Identity:IDP:AuthenticationFailure", message);
421 },
423 // Private.
424 _init: function nsDOMIdentity__init(aWindow) {
426 this._initializeState();
428 // Store window and origin URI.
429 this._window = aWindow;
430 this._origin = aWindow.document.nodePrincipal.origin;
431 this._appStatus = aWindow.document.nodePrincipal.appStatus;
432 this._appId = aWindow.document.nodePrincipal.appId;
434 // Setup identifiers for current window.
435 let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
436 .getInterface(Ci.nsIDOMWindowUtils);
438 // We need to inherit the id from the internalIdentity service.
439 // See comments below in that service's init.
440 this._id = this._identityInternal._id;
441 },
443 /**
444 * Called during init and shutdown.
445 */
446 _initializeState: function nsDOMIdentity__initializeState() {
447 // Some state to prevent abuse
448 // Limit the number of calls to .request
449 this._rpCalls = 0;
450 this._provisioningEnded = false;
451 this._authenticationEnded = false;
453 this._rpWatcher = null;
454 this._onCancelRequestCallback = null;
455 this._beginProvisioningCallback = null;
456 this._genKeyPairCallback = null;
457 this._beginAuthenticationCallback = null;
458 },
460 _receiveMessage: function nsDOMIdentity_receiveMessage(aMessage) {
461 let msg = aMessage.json;
463 switch (aMessage.name) {
464 case "Identity:ResetState":
465 if (!this._identityInternal._debug) {
466 return;
467 }
468 this._initializeState();
469 Services.obs.notifyObservers(null, "identity-DOM-state-reset", this._id);
470 break;
471 case "Identity:RP:Watch:OnLogin":
472 // Do we have a watcher?
473 if (!this._rpWatcher) {
474 this._log("WARNING: Received OnLogin message, but there is no RP watcher");
475 return;
476 }
478 if (this._rpWatcher.onlogin) {
479 if (this._rpWatcher._internal) {
480 this._rpWatcher.onlogin(msg.assertion, msg._internalParams);
481 } else {
482 this._rpWatcher.onlogin(msg.assertion);
483 }
484 }
485 break;
486 case "Identity:RP:Watch:OnLogout":
487 // Do we have a watcher?
488 if (!this._rpWatcher) {
489 this._log("WARNING: Received OnLogout message, but there is no RP watcher");
490 return;
491 }
493 if (this._rpWatcher.onlogout) {
494 this._rpWatcher.onlogout();
495 }
496 break;
497 case "Identity:RP:Watch:OnReady":
498 // Do we have a watcher?
499 if (!this._rpWatcher) {
500 this._log("WARNING: Received OnReady message, but there is no RP watcher");
501 return;
502 }
504 if (this._rpWatcher.onready) {
505 this._rpWatcher.onready();
506 }
507 break;
508 case "Identity:RP:Watch:OnCancel":
509 // Do we have a watcher?
510 if (!this._rpWatcher) {
511 this._log("WARNING: Received OnCancel message, but there is no RP watcher");
512 return;
513 }
515 if (this._onCancelRequestCallback) {
516 this._onCancelRequestCallback();
517 }
518 break;
519 case "Identity:RP:Watch:OnError":
520 if (!this._rpWatcher) {
521 this._log("WARNING: Received OnError message, but there is no RP watcher");
522 return;
523 }
525 if (this._rpWatcher.onerror) {
526 this._rpWatcher.onerror(JSON.stringify({name: msg.message.error}));
527 }
528 break;
529 case "Identity:IDP:CallBeginProvisioningCallback":
530 this._callBeginProvisioningCallback(msg);
531 break;
532 case "Identity:IDP:CallGenKeyPairCallback":
533 this._callGenKeyPairCallback(msg);
534 break;
535 case "Identity:IDP:CallBeginAuthenticationCallback":
536 this._callBeginAuthenticationCallback(msg);
537 break;
538 }
539 },
541 _log: function nsDOMIdentity__log(msg) {
542 this._identityInternal._log(msg);
543 },
545 _callGenKeyPairCallback: function nsDOMIdentity__callGenKeyPairCallback(message) {
546 // create a pubkey object that works
547 let chrome_pubkey = JSON.parse(message.publicKey);
549 // bunch of stuff to create a proper object in window context
550 function genPropDesc(value) {
551 return {
552 enumerable: true, configurable: true, writable: true, value: value
553 };
554 }
556 let propList = {};
557 for (let k in chrome_pubkey) {
558 propList[k] = genPropDesc(chrome_pubkey[k]);
559 }
561 let pubkey = Cu.createObjectIn(this._window);
562 Object.defineProperties(pubkey, propList);
563 Cu.makeObjectPropsNormal(pubkey);
565 // do the callback
566 this._genKeyPairCallback(pubkey);
567 },
569 _callBeginProvisioningCallback:
570 function nsDOMIdentity__callBeginProvisioningCallback(message) {
571 let identity = message.identity;
572 let certValidityDuration = message.certDuration;
573 this._beginProvisioningCallback(identity,
574 certValidityDuration);
575 },
577 _callBeginAuthenticationCallback:
578 function nsDOMIdentity__callBeginAuthenticationCallback(message) {
579 let identity = message.identity;
580 this._beginAuthenticationCallback(identity);
581 },
583 /**
584 * Helper to create messages to send using a message manager.
585 * Pass through user options if they are not functions. Always
586 * overwrite id, origin, audience, and appStatus. The caller
587 * does not get to set those.
588 */
589 DOMIdentityMessage: function DOMIdentityMessage(aOptions) {
590 aOptions = aOptions || {};
591 let message = {
592 errors: []
593 };
594 let principal = Ci.nsIPrincipal;
596 objectCopy(aOptions, message);
598 // outer window id
599 message.id = this._id;
601 // window origin
602 message.origin = this._origin;
604 // On b2g, an app's status can be NOT_INSTALLED, INSTALLED, PRIVILEGED, or
605 // CERTIFIED. Compare the appStatus value to the constants enumerated in
606 // Ci.nsIPrincipal.APP_STATUS_*.
607 message.appStatus = this._appStatus;
609 // Currently, we only permit certified and privileged apps to use
610 // Firefox Accounts.
611 if (aOptions.wantIssuer == "firefox-accounts" &&
612 this._appStatus !== principal.APP_STATUS_PRIVILEGED &&
613 this._appStatus !== principal.APP_STATUS_CERTIFIED) {
614 message.errors.push("ERROR_NOT_AUTHORIZED_FOR_FIREFOX_ACCOUNTS");
615 }
617 // Normally the window origin will be the audience in assertions. On b2g,
618 // certified apps have the power to override this and declare any audience
619 // the want. Privileged apps can also declare a different audience, as
620 // long as it is the same as the origin specified in their manifest files.
621 // All other apps are stuck with b2g origins of the form app://{guid}.
622 // Since such an origin is meaningless for the purposes of verification,
623 // they will have to jump through some hoops to sign in: Specifically, they
624 // will have to host their sign-in flows and DOM API requests in an iframe,
625 // have the iframe xhr post assertions up to their server for verification,
626 // and then post-message the results down to their app.
627 let _audience = message.origin;
628 if (message.audience && message.audience != message.origin) {
629 if (this._appStatus === principal.APP_STATUS_CERTIFIED) {
630 _audience = message.audience;
631 this._log("Certified app setting assertion audience: " + _audience);
632 } else {
633 message.errors.push("ERROR_INVALID_ASSERTION_AUDIENCE");
634 }
635 }
637 // Replace any audience supplied by the RP with one that has been sanitised
638 message.audience = _audience;
640 this._log("DOMIdentityMessage: " + JSON.stringify(message));
642 return message;
643 },
645 uninit: function DOMIdentity_uninit() {
646 this._log("nsDOMIdentity uninit() " + this._id);
647 this._identityInternal._mm.sendAsyncMessage(
648 "Identity:RP:Unwatch",
649 { id: this._id }
650 );
651 }
653 };
655 /**
656 * Internal functions that shouldn't be exposed to content.
657 */
658 function nsDOMIdentityInternal() {
659 }
660 nsDOMIdentityInternal.prototype = {
662 // nsIMessageListener
663 receiveMessage: function nsDOMIdentityInternal_receiveMessage(aMessage) {
664 let msg = aMessage.json;
665 // Is this message intended for this window?
666 if (msg.id != this._id) {
667 return;
668 }
669 this._identity._receiveMessage(aMessage);
670 },
672 // nsIObserver
673 observe: function nsDOMIdentityInternal_observe(aSubject, aTopic, aData) {
674 let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
675 if (wId != this._innerWindowID) {
676 return;
677 }
679 this._identity.uninit();
681 Services.obs.removeObserver(this, "inner-window-destroyed");
682 this._identity._initializeState();
683 this._identity = null;
685 // TODO: Also send message to DOMIdentity notifiying window is no longer valid
686 // ie. in the case that the user closes the auth. window and we need to know.
688 try {
689 for (let msgName of this._messages) {
690 this._mm.removeMessageListener(msgName, this);
691 }
692 } catch (ex) {
693 // Avoid errors when removing more than once.
694 }
696 this._mm = null;
697 },
699 // nsIDOMGlobalPropertyInitializer
700 init: function nsDOMIdentityInternal_init(aWindow) {
701 if (Services.prefs.getPrefType(PREF_ENABLED) != Ci.nsIPrefBranch.PREF_BOOL
702 || !Services.prefs.getBoolPref(PREF_ENABLED)) {
703 return null;
704 }
706 this._debug =
707 Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
708 && Services.prefs.getBoolPref(PREF_DEBUG);
710 let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
711 .getInterface(Ci.nsIDOMWindowUtils);
713 // To avoid cross-process windowId collisions, use a uuid as an
714 // almost certainly unique identifier.
715 //
716 // XXX Bug 869182 - use a combination of child process id and
717 // innerwindow id to construct the unique id.
718 this._id = uuidgen.generateUUID().toString();
719 this._innerWindowID = util.currentInnerWindowID;
721 // nsDOMIdentity needs to know our _id, so this goes after
722 // its creation.
723 this._identity = new nsDOMIdentity(this);
724 this._identity._init(aWindow);
726 this._log("init was called from " + aWindow.document.location);
728 this._mm = cpmm;
730 // Setup listeners for messages from parent process.
731 this._messages = [
732 "Identity:ResetState",
733 "Identity:RP:Watch:OnLogin",
734 "Identity:RP:Watch:OnLogout",
735 "Identity:RP:Watch:OnReady",
736 "Identity:RP:Watch:OnCancel",
737 "Identity:RP:Watch:OnError",
738 "Identity:IDP:CallBeginProvisioningCallback",
739 "Identity:IDP:CallGenKeyPairCallback",
740 "Identity:IDP:CallBeginAuthenticationCallback"
741 ];
742 this._messages.forEach(function(msgName) {
743 this._mm.addMessageListener(msgName, this);
744 }, this);
746 // Setup observers so we can remove message listeners.
747 Services.obs.addObserver(this, "inner-window-destroyed", false);
749 return this._identity;
750 },
752 // Private.
753 _log: function nsDOMIdentityInternal__log(msg) {
754 if (!this._debug) {
755 return;
756 }
757 dump("nsDOMIdentity (" + this._id + "): " + msg + "\n");
758 },
760 // Component setup.
761 classID: Components.ID("{210853d9-2c97-4669-9761-b1ab9cbf57ef}"),
763 QueryInterface: XPCOMUtils.generateQI(
764 [Ci.nsIDOMGlobalPropertyInitializer, Ci.nsIMessageListener]
765 ),
767 classInfo: XPCOMUtils.generateCI({
768 classID: Components.ID("{210853d9-2c97-4669-9761-b1ab9cbf57ef}"),
769 contractID: "@mozilla.org/dom/identity;1",
770 interfaces: [],
771 classDescription: "Identity DOM Implementation"
772 })
773 };
775 function assertCorrectCallbacks(aOptions) {
776 // The relying party (RP) provides callbacks on watch().
777 //
778 // In the future, BrowserID will probably only require an onlogin()
779 // callback, lifting the requirement that BrowserID handle logged-in
780 // state management for RPs. See
781 // https://github.com/mozilla/id-specs/blob/greenfield/browserid/api-rp.md
782 //
783 // However, Firefox Accounts requires callers to provide onlogout(),
784 // onready(), and also supports an onerror() callback.
786 let requiredCallbacks = ["onlogin"];
787 let optionalCallbacks = ["onlogout", "onready", "onerror"];
789 if (aOptions.wantIssuer == "firefox-accounts") {
790 requiredCallbacks = ["onlogin", "onlogout", "onready"];
791 optionalCallbacks = ["onerror"];
792 }
794 for (let cbName of requiredCallbacks) {
795 if (typeof(aOptions[cbName]) != "function") {
796 throw new Error(cbName + " callback is required.");
797 }
798 }
800 for (let cbName of optionalCallbacks) {
801 if (aOptions[cbName] && typeof(aOptions[cbName]) != "function") {
802 throw new Error(cbName + " must be a function");
803 }
804 }
805 }
807 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDOMIdentityInternal]);