Bug 1026072 - this.getContextForMM(...).RP is undefined. r=MattN, a=lmandel
authorJed Parsons <jed+bmo@jedparsons.com>
Fri, 20 Jun 2014 16:59:37 -0700
changeset 208019 883f4666206ee53f5834be6d1361e264f62bd487
parent 208018 e7360cef9de1eb14033b10b5172254170cdcdc05
child 208020 9234fae30e387995cbd70ce44043729926bdd9c2
push id3741
push userasasaki@mozilla.com
push dateMon, 21 Jul 2014 20:25:18 +0000
treeherdermozilla-beta@4d6f46f5af68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN, lmandel
bugs1026072
milestone32.0a2
Bug 1026072 - this.getContextForMM(...).RP is undefined. r=MattN, a=lmandel
dom/identity/DOMIdentity.jsm
--- a/dom/identity/DOMIdentity.jsm
+++ b/dom/identity/DOMIdentity.jsm
@@ -1,34 +1,39 @@
 /* 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 {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+const {classes: Cc,
+       interfaces: Ci,
+       utils: Cu,
+       results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const PREF_FXA_ENABLED = "identity.fxaccounts.enabled";
 const FXA_PERMISSION = "firefox-accounts";
 
 // This is the parent process corresponding to nsDOMIdentity.
 this.EXPORTED_SYMBOLS = ["DOMIdentity"];
 
 XPCOMUtils.defineLazyModuleGetter(this, "objectCopy",
                                   "resource://gre/modules/identity/IdentityUtils.jsm");
 
+/* jshint ignore:start */
 XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
 #ifdef MOZ_B2G_VERSION
                                   "resource://gre/modules/identity/MinimalIdentity.jsm");
 #else
                                   "resource://gre/modules/identity/Identity.jsm");
 #endif
+/* jshint ignore:end */
 
 XPCOMUtils.defineLazyModuleGetter(this, "FirefoxAccounts",
                                   "resource://gre/modules/identity/FirefoxAccounts.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject",
                                   "resource://gre/modules/identity/IdentityUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this,
@@ -46,39 +51,58 @@ XPCOMUtils.defineLazyServiceGetter(this,
 function log(...aMessageArgs) {
   Logger.log.apply(Logger, ["DOMIdentity"].concat(aMessageArgs));
 }
 
 function IDDOMMessage(aOptions) {
   objectCopy(aOptions, this);
 }
 
+function _sendAsyncMessage(identifier, message) {
+  if (this._mm) {
+    try {
+      this._mm.sendAsyncMessage(identifier, message);
+    } catch(err) {
+      // We may receive a NS_ERROR_NOT_INITIALIZED if the target window has
+      // been closed.  This can legitimately happen if an app has been killed
+      // while we are in the midst of a sign-in flow.
+      if (err.result == Cr.NS_ERROR_NOT_INITIALIZED) {
+        log("Cannot sendAsyncMessage because the recipient frame has closed");
+        return;
+      }
+      log("ERROR: sendAsyncMessage: " + err);
+    }
+  }
+};
+
 function IDPProvisioningContext(aID, aOrigin, aTargetMM) {
   this._id = aID;
   this._origin = aOrigin;
   this._mm = aTargetMM;
 }
 
 IDPProvisioningContext.prototype = {
   get id() this._id,
   get origin() this._origin,
 
+  sendAsyncMessage: _sendAsyncMessage,
+
   doBeginProvisioningCallback: function IDPPC_doBeginProvCB(aID, aCertDuration) {
     let message = new IDDOMMessage({id: this.id});
     message.identity = aID;
     message.certDuration = aCertDuration;
-    this._mm.sendAsyncMessage("Identity:IDP:CallBeginProvisioningCallback",
-                              message);
+    this.sendAsyncMessage("Identity:IDP:CallBeginProvisioningCallback",
+                          message);
   },
 
   doGenKeyPairCallback: function IDPPC_doGenKeyPairCallback(aPublicKey) {
     log("doGenKeyPairCallback");
     let message = new IDDOMMessage({id: this.id});
     message.publicKey = aPublicKey;
-    this._mm.sendAsyncMessage("Identity:IDP:CallGenKeyPairCallback", message);
+    this.sendAsyncMessage("Identity:IDP:CallGenKeyPairCallback", message);
   },
 
   doError: function(msg) {
     log("Provisioning ERROR: " + msg);
   }
 };
 
 function IDPAuthenticationContext(aID, aOrigin, aTargetMM) {
@@ -86,21 +110,23 @@ function IDPAuthenticationContext(aID, a
   this._origin = aOrigin;
   this._mm = aTargetMM;
 }
 
 IDPAuthenticationContext.prototype = {
   get id() this._id,
   get origin() this._origin,
 
+  sendAsyncMessage: _sendAsyncMessage,
+
   doBeginAuthenticationCallback: function IDPAC_doBeginAuthCB(aIdentity) {
     let message = new IDDOMMessage({id: this.id});
     message.identity = aIdentity;
-    this._mm.sendAsyncMessage("Identity:IDP:CallBeginAuthenticationCallback",
-                              message);
+    this.sendAsyncMessage("Identity:IDP:CallBeginAuthenticationCallback",
+                          message);
   },
 
   doError: function IDPAC_doError(msg) {
     log("Authentication ERROR: " + msg);
   }
 };
 
 function RPWatchContext(aOptions, aTargetMM, aPrincipal) {
@@ -118,47 +144,49 @@ function RPWatchContext(aOptions, aTarge
 
   // Maybe internal.  For hosted b2g identity shim.
   this._internal = aOptions._internal;
 
   this._mm = aTargetMM;
 }
 
 RPWatchContext.prototype = {
+  sendAsyncMessage: _sendAsyncMessage,
+
   doLogin: function RPWatchContext_onlogin(aAssertion, aMaybeInternalParams) {
     log("doLogin: " + this.id);
     let message = new IDDOMMessage({id: this.id, assertion: aAssertion});
     if (aMaybeInternalParams) {
       message._internalParams = aMaybeInternalParams;
     }
-    this._mm.sendAsyncMessage("Identity:RP:Watch:OnLogin", message);
+    this.sendAsyncMessage("Identity:RP:Watch:OnLogin", message);
   },
 
   doLogout: function RPWatchContext_onlogout() {
     log("doLogout: " + this.id);
     let message = new IDDOMMessage({id: this.id});
-    this._mm.sendAsyncMessage("Identity:RP:Watch:OnLogout", message);
+    this.sendAsyncMessage("Identity:RP:Watch:OnLogout", message);
   },
 
   doReady: function RPWatchContext_onready() {
     log("doReady: " + this.id);
     let message = new IDDOMMessage({id: this.id});
-    this._mm.sendAsyncMessage("Identity:RP:Watch:OnReady", message);
+    this.sendAsyncMessage("Identity:RP:Watch:OnReady", message);
   },
 
   doCancel: function RPWatchContext_oncancel() {
     log("doCancel: " + this.id);
     let message = new IDDOMMessage({id: this.id});
-    this._mm.sendAsyncMessage("Identity:RP:Watch:OnCancel", message);
+    this.sendAsyncMessage("Identity:RP:Watch:OnCancel", message);
   },
 
   doError: function RPWatchContext_onerror(aMessage) {
     log("doError: " + this.id + ": " + JSON.stringify(aMessage));
     let message = new IDDOMMessage({id: this.id, message: aMessage});
-    this._mm.sendAsyncMessage("Identity:RP:Watch:OnError", message);
+    this.sendAsyncMessage("Identity:RP:Watch:OnError", message);
   }
 };
 
 this.DOMIdentity = {
   /*
    * When relying parties (RPs) invoke the watch() method, they can request
    * to use Firefox Accounts as their auth service or BrowserID (the default).
    * For each RP, we create an RPWatchContext to store the parameters given to
@@ -202,17 +230,18 @@ this.DOMIdentity = {
    * @object message
    *         A message received from an RP.  Will include the id of the window
    *         whence the message originated.
    *
    * Returns FirefoxAccounts or IdentityService
    */
   getService: function(message) {
     if (!this._serviceContexts.has(message.id)) {
-      throw new Error("getService called before newContext for " + message.id);
+      log("ERROR: getService called before newContext for " + message.id);
+      return null;
     }
 
     let context = this._serviceContexts.get(message.id);
     if (context.wantIssuer == "firefox-accounts") {
       if (Services.prefs.getPrefType(PREF_FXA_ENABLED) === Ci.nsIPrefBranch.PREF_BOOL
           && Services.prefs.getBoolPref(PREF_FXA_ENABLED)) {
         return FirefoxAccounts;
       }
@@ -343,17 +372,19 @@ this.DOMIdentity = {
   // Private.
   _init: function DOMIdentity__init() {
     Services.ww.registerNotification(this);
     Services.obs.addObserver(this, "xpcom-shutdown", false);
     this._subscribeListeners();
   },
 
   _subscribeListeners: function DOMIdentity__subscribeListeners() {
-    if (!ppmm) return;
+    if (!ppmm) {
+      return;
+    }
     for (let message of this.messages) {
       ppmm.addMessageListener(message, this);
     }
   },
 
   _unsubscribeListeners: function DOMIdentity__unsubscribeListeners() {
     for (let message of this.messages) {
       ppmm.removeMessageListener(message, this);
@@ -368,38 +399,53 @@ this.DOMIdentity = {
   },
 
   _unwatch: function DOMIdentity_unwatch(message, targetMM) {
     log("DOMIDentity__unwatch: " + message.id);
     // If watch failed for some reason (e.g., exception thrown because RP did
     // not have the right callbacks, we don't want unwatch to throw, because it
     // will break the process of releasing the page's resources and leak
     // memory.
-    try {
-      this.getService(message).RP.unwatch(message.id, targetMM);
-    } catch(ex) {
-      log("ERROR: can't unwatch " + message.id + ": " + ex);
+    let service = this.getService(message);
+    if (service && service.RP) {
+      service.RP.unwatch(message.id, targetMM);
+      this.deleteContextForMM(targetMM);
+      return;
     }
+    log("Can't find a service to unwatch() for " + message.id);
   },
 
   _request: function DOMIdentity__request(message) {
-    this.getService(message).RP.request(message.id, message);
+    let service = this.getService(message);
+    if (service && service.RP) {
+      service.RP.request(message.id, message);
+      return;
+    }
+    log("No context in which to call request(); Did you call watch() first?");
   },
 
   _logout: function DOMIdentity__logout(message) {
-    log("logout " + message + "\n");
-    this.getService(message).RP.logout(message.id, message.origin, message);
+    let service = this.getService(message);
+    if (service && service.RP) {
+      service.RP.logout(message.id, message.origin, message);
+      return;
+    }
+    log("No context in which to call logout(); Did you call watch() first?");
   },
 
   _childProcessShutdown: function DOMIdentity__childProcessShutdown(targetMM) {
     if (!this.hasContextForMM(targetMM)) {
       return;
     }
 
-    this.getContextForMM(targetMM).RP.childProcessShutdown(targetMM);
+    let service = this.getContextForMM(targetMM);
+    if (service && service.RP) {
+      service.RP.childProcessShutdown(targetMM);
+    }
+
     this.deleteContextForMM(targetMM);
 
     let options = makeMessageObject({messageManager: targetMM, id: null, origin: null});
     Services.obs.notifyObservers({wrappedJSObject: options}, "identity-child-process-shutdown", null);
   },
 
   _beginProvisioning: function DOMIdentity__beginProvisioning(message, targetMM) {
     let context = new IDPProvisioningContext(message.id, message.origin,