Merge b2g-inbound to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 07 Apr 2014 22:29:35 -0400
changeset 196969 8883360b1edbcc78c21830d5190d9aaecb8a2052
parent 196916 488caa16c617edb0bef00719f426eb001247ee46 (current diff)
parent 196968 3a0099215f2d9a890957c8e5bb28c9bcfae8c202 (diff)
child 196983 d519f142881bffaf56082b920147335fe188d945
child 197030 80ed52d37cfe7d661061a094a9fff796a826f2d3
child 197050 e39b09e7a3a892eaac625a2954210eec05ac3bd9
push id486
push userasasaki@mozilla.com
push dateMon, 14 Jul 2014 18:39:42 +0000
treeherdermozilla-release@d33428174ff1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone31.0a1
first release with
nightly linux32
8883360b1edb / 31.0a1 / 20140408030205 / files
nightly linux64
8883360b1edb / 31.0a1 / 20140408030205 / files
nightly mac
8883360b1edb / 31.0a1 / 20140408030205 / files
nightly win32
8883360b1edb / 31.0a1 / 20140408030205 / files
nightly win64
8883360b1edb / 31.0a1 / 20140408030205 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge b2g-inbound to m-c.
--- a/b2g/chrome/content/payment.js
+++ b/b2g/chrome/content/payment.js
@@ -41,16 +41,19 @@ if (_debug) {
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+                                  "resource://gre/modules/SystemAppProxy.jsm");
+
 #ifdef MOZ_B2G_RIL
 XPCOMUtils.defineLazyServiceGetter(this, "gRil",
                                    "@mozilla.org/ril;1",
                                    "nsIRadioInterfaceLayer");
 
 XPCOMUtils.defineLazyServiceGetter(this, "iccProvider",
                                    "@mozilla.org/ril/content-helper;1",
                                    "nsIIccProvider");
@@ -192,18 +195,16 @@ PaymentSettings.prototype = {
   }
 };
 #endif
 
 const kClosePaymentFlowEvent = "close-payment-flow-dialog";
 
 let gRequestId;
 
-let gBrowser = Services.wm.getMostRecentWindow("navigator:browser");
-
 let PaymentProvider = {
 #ifdef MOZ_B2G_RIL
   __exposedProps__: {
     paymentSuccess: "r",
     paymentFailed: "r",
     paymentServiceId: "rw",
     iccInfo: "r",
     sendSilentSms: "r",
@@ -224,46 +225,41 @@ let PaymentProvider = {
   },
 
   _closePaymentFlowDialog: function _closePaymentFlowDialog(aCallback) {
     // After receiving the payment provider confirmation about the
     // successful or failed payment flow, we notify the UI to close the
     // payment flow dialog and return to the caller application.
     let id = kClosePaymentFlowEvent + "-" + uuidgen.generateUUID().toString();
 
-    let content = gBrowser.getContentWindow();
-    if (!content) {
-      return;
-    }
-
     let detail = {
       type: kClosePaymentFlowEvent,
       id: id,
       requestId: gRequestId
     };
 
     // In order to avoid race conditions, we wait for the UI to notify that
     // it has successfully closed the payment flow and has recovered the
     // caller app, before notifying the parent process to fire the success
     // or error event over the DOMRequest.
-    content.addEventListener("mozContentEvent",
-                             function closePaymentFlowReturn(evt) {
+    SystemAppProxy.addEventListener("mozContentEvent",
+                               function closePaymentFlowReturn(evt) {
       if (evt.detail.id == id && aCallback) {
         aCallback();
       }
 
-      content.removeEventListener("mozContentEvent",
+      SystemAppProxy.removeEventListener("mozContentEvent",
                                   closePaymentFlowReturn);
 
       let glue = Cc["@mozilla.org/payment/ui-glue;1"]
                    .createInstance(Ci.nsIPaymentUIGlue);
       glue.cleanup();
     });
 
-    gBrowser.shell.sendChromeEvent(detail);
+    SystemAppProxy.dispatchEvent(detail);
 
 #ifdef MOZ_B2G_RIL
     this._cleanUp();
 #endif
   },
 
   paymentSuccess: function paymentSuccess(aResult) {
     if (_debug) {
@@ -473,14 +469,14 @@ addMessageListener("Payment:LoadShim", f
 addEventListener("DOMWindowCreated", function(e) {
   content.wrappedJSObject.mozPaymentProvider = PaymentProvider;
 });
 
 #ifdef MOZ_B2G_RIL
 // If the trusted dialog is not closed via paymentSuccess or paymentFailed
 // a mozContentEvent with type 'cancel' is sent from the UI. We need to listen
 // for this event to clean up the silent sms observers if any exists.
-gBrowser.getContentWindow().addEventListener("mozContentEvent", function(e) {
+SystemAppProxy.addEventListener("mozContentEvent", function(e) {
   if (e.detail.type === "cancel") {
     PaymentProvider._cleanUp();
   }
 });
 #endif
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -25,16 +25,19 @@ Cu.import('resource://gre/modules/SignIn
 SignInToWebsiteController.init();
 
 #ifdef MOZ_SERVICES_FXACCOUNTS
 Cu.import('resource://gre/modules/FxAccountsMgmtService.jsm');
 #endif
 
 Cu.import('resource://gre/modules/DownloadsAPI.jsm');
 
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+                                  "resource://gre/modules/SystemAppProxy.jsm");
+
 Cu.import('resource://gre/modules/Webapps.jsm');
 DOMApplicationRegistry.allAppsLaunchable = true;
 
 XPCOMUtils.defineLazyServiceGetter(Services, 'env',
                                    '@mozilla.org/process/environment;1',
                                    'nsIEnvironment');
 
 XPCOMUtils.defineLazyServiceGetter(Services, 'ss',
@@ -335,16 +338,18 @@ var shell = {
 
     window.addEventListener('MozApplicationManifest', this);
     window.addEventListener('mozfullscreenchange', this);
     window.addEventListener('MozAfterPaint', this);
     window.addEventListener('sizemodechange', this);
     window.addEventListener('unload', this);
     this.contentBrowser.addEventListener('mozbrowserloadstart', this, true);
 
+    SystemAppProxy.registerFrame(this.contentBrowser);
+
     CustomEventManager.init();
     WebappsHelper.init();
     UserAgentOverrides.init();
     IndexedDBPromptHelper.init();
     CaptivePortalLoginHelper.init();
 
     this.contentBrowser.src = homeURL;
     this.isHomeLoaded = false;
@@ -661,16 +666,17 @@ var shell = {
       shell.isHomeLoaded = true;
 
 #ifdef MOZ_WIDGET_GONK
       libcutils.property_set('sys.boot_completed', '1');
 #endif
 
       Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
 
+      SystemAppProxy.setIsReady();
       if ('pendingChromeEvents' in shell) {
         shell.pendingChromeEvents.forEach((shell.sendChromeEvent).bind(shell));
       }
       delete shell.pendingChromeEvents;
     });
   }
 };
 
--- a/b2g/chrome/content/test/mochitest/RecordingStatusChromeScript.js
+++ b/b2g/chrome/content/test/mochitest/RecordingStatusChromeScript.js
@@ -1,30 +1,30 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 const { Services } = Cu.import('resource://gre/modules/Services.jsm');
+const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm');
 
 var processId;
 
 function peekChildId(aSubject, aTopic, aData) {
   Services.obs.removeObserver(peekChildId, 'recording-device-events');
   Services.obs.removeObserver(peekChildId, 'recording-device-ipc-events');
   let props = aSubject.QueryInterface(Ci.nsIPropertyBag2);
   if (props.hasKey('childID')) {
     processId = props.get('childID');
   }
 }
 
 addMessageListener('init-chrome-event', function(message) {
   // listen mozChromeEvent and forward to content process.
-  let browser = Services.wm.getMostRecentWindow('navigator:browser');
   let type = message.type;
-  browser.addEventListener('mozChromeEvent', function(event) {
+  SystemAppProxy.addEventListener('mozChromeEvent', function(event) {
     let details = event.detail;
     if (details.type === type) {
       sendAsyncMessage('chrome-event', details);
     }
   }, true);
 
   Services.obs.addObserver(peekChildId, 'recording-device-events', false);
   Services.obs.addObserver(peekChildId, 'recording-device-ipc-events', false);
--- a/b2g/components/ActivitiesGlue.js
+++ b/b2g/components/ActivitiesGlue.js
@@ -6,16 +6,19 @@
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+                                  "resource://gre/modules/SystemAppProxy.jsm");
+
 function ActivitiesDialog() {
   this._id = 0;
 
   this.activities = [];
 }
 
 ActivitiesDialog.prototype = {
   run: function ap_run() {
@@ -26,38 +29,36 @@ ActivitiesDialog.prototype = {
     activity.list.forEach(function(item) {
       choices.push({ manifest: item.manifest, icon: item.icon });
     });
 
 
     // Keep up the frond-end of an activity choice. The messages contains
     // a list of {names, icons} for applications able to handle this particular
     // activity. The front-end should display a UI to pick one.
-    let browser = Services.wm.getMostRecentWindow("navigator:browser");
-    let content = browser.getContentWindow();
     let detail = {
       type: "activity-choice",
       id: id,
       name: activity.name,
       choices: choices
     };
 
     // Listen the resulting choice from the front-end. If there is no choice,
     // let's return -1, which means the user has cancelled the dialog.
-    content.addEventListener("mozContentEvent", function act_getChoice(evt) {
+    SystemAppProxy.addEventListener("mozContentEvent", function act_getChoice(evt) {
       if (evt.detail.id != id)
         return;
 
-      content.removeEventListener("mozContentEvent", act_getChoice);
+      SystemAppProxy.removeEventListener("mozContentEvent", act_getChoice);
       activity.callback.handleEvent(evt.detail.value !== undefined
                                       ? evt.detail.value
                                       : -1);
     });
 
-    browser.shell.sendChromeEvent(detail);
+    SystemAppProxy.dispatchEvent(detail);
   },
 
   chooseActivity: function ap_chooseActivity(aName, aActivities, aCallback) {
     this.activities.push({
       name: aName,
       list: aActivities,
       callback: aCallback
     });
--- a/b2g/components/ContentPermissionPrompt.js
+++ b/b2g/components/ContentPermissionPrompt.js
@@ -34,16 +34,19 @@ var secMan = Cc["@mozilla.org/scriptsecu
 
 let permissionSpecificChecker = {};
 
 XPCOMUtils.defineLazyServiceGetter(this,
                                    "AudioManager",
                                    "@mozilla.org/telephony/audiomanager;1",
                                    "nsIAudioManager");
 
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+                                  "resource://gre/modules/SystemAppProxy.jsm");
+
 /**
  * aTypesInfo is an array of {permission, access, action, deny} which keeps
  * the information of each permission. This arrary is initialized in
  * ContentPermissionPrompt.prompt and used among functions.
  *
  * aTypesInfo[].permission : permission name
  * aTypesInfo[].access     : permission name + request.access
  * aTypesInfo[].action     : the default action of this permission
@@ -341,27 +344,22 @@ ContentPermissionPrompt.prototype = {
       if (callback) {
         callback();
       }
       request.cancel();
     });
   },
 
   sendToBrowserWindow: function(type, request, requestId, typesInfo, callback) {
-    let browser = Services.wm.getMostRecentWindow("navigator:browser");
-    let content = browser.getContentWindow();
-    if (!content)
-      return;
-
     if (callback) {
-      content.addEventListener("mozContentEvent", function contentEvent(evt) {
+      SystemAppProxy.addEventListener("mozContentEvent", function contentEvent(evt) {
         let detail = evt.detail;
         if (detail.id != requestId)
           return;
-        evt.target.removeEventListener(evt.type, contentEvent);
+        SystemAppProxy.removeEventListener("mozContentEvent", contentEvent);
 
         callback(detail.type, detail.remember, detail.choices);
       })
     }
 
     let principal = request.principal;
     let isApp = principal.appStatus != Ci.nsIPrincipal.APP_STATUS_NOT_INSTALLED;
     let remember = (principal.appStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
@@ -378,23 +376,20 @@ ContentPermissionPrompt.prototype = {
       type: type,
       permissions: permissions,
       id: requestId,
       origin: principal.origin,
       isApp: isApp,
       remember: remember
     };
 
-    if (!isApp) {
-      browser.shell.sendChromeEvent(details);
-      return;
+    if (isApp) {
+      details.manifestURL = DOMApplicationRegistry.getManifestURLByLocalId(principal.appId);
     }
-
-    details.manifestURL = DOMApplicationRegistry.getManifestURLByLocalId(principal.appId);
-    browser.shell.sendChromeEvent(details);
+    SystemAppProxy.dispatchEvent(details);
   },
 
   classID: Components.ID("{8c719f03-afe0-4aac-91ff-6c215895d467}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt])
 };
 
 (function() {
--- a/b2g/components/FxAccountsMgmtService.jsm
+++ b/b2g/components/FxAccountsMgmtService.jsm
@@ -24,62 +24,54 @@ const { classes: Cc, interfaces: Ci, uti
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/FxAccountsCommon.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsManager",
   "resource://gre/modules/FxAccountsManager.jsm");
 
-this.FxAccountsMgmtService = {
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+                                  "resource://gre/modules/SystemAppProxy.jsm");
 
-  _sendChromeEvent: function(aEventName, aMsg) {
-    if (!this._shell) {
-      return;
-    }
-    log.debug("Chrome event " + JSON.stringify(aMsg));
-    this._shell.sendCustomEvent(aEventName, aMsg);
-  },
-
+this.FxAccountsMgmtService = {
   _onFulfill: function(aMsgId, aData) {
-    this._sendChromeEvent("mozFxAccountsChromeEvent", {
+    SystemAppProxy._sendCustomEvent("mozFxAccountsChromeEvent", {
       id: aMsgId,
       data: aData ? aData : null
     });
   },
 
   _onReject: function(aMsgId, aReason) {
-    this._sendChromeEvent("mozFxAccountsChromeEvent", {
+    SystemAppProxy._sendCustomEvent("mozFxAccountsChromeEvent", {
       id: aMsgId,
       error: aReason ? aReason : null
     });
   },
 
   init: function() {
     Services.obs.addObserver(this, "content-start", false);
     Services.obs.addObserver(this, ONLOGIN_NOTIFICATION, false);
     Services.obs.addObserver(this, ONVERIFIED_NOTIFICATION, false);
     Services.obs.addObserver(this, ONLOGOUT_NOTIFICATION, false);
   },
 
   observe: function(aSubject, aTopic, aData) {
     log.debug("Observed " + aTopic);
     switch (aTopic) {
       case "content-start":
-        this._shell = Services.wm.getMostRecentWindow("navigator:browser").shell;
-        let content = this._shell.contentBrowser.contentWindow;
-        content.addEventListener("mozFxAccountsContentEvent",
-                                 FxAccountsMgmtService);
+        SystemAppProxy.addEventListener("mozFxAccountsContentEvent",
+                                        FxAccountsMgmtService);
         Services.obs.removeObserver(this, "content-start");
         break;
       case ONLOGIN_NOTIFICATION:
       case ONVERIFIED_NOTIFICATION:
       case ONLOGOUT_NOTIFICATION:
         // FxAccounts notifications have the form of fxaccounts:*
-        this._sendChromeEvent("mozFxAccountsUnsolChromeEvent", {
+        SystemAppProxy._sendCustomEvent("mozFxAccountsUnsolChromeEvent", {
           eventName: aTopic.substring(aTopic.indexOf(":") + 1)
         });
         break;
     }
   },
 
   handleEvent: function(aEvent) {
     let msg = aEvent.detail;
--- a/b2g/components/FxAccountsUIGlue.js
+++ b/b2g/components/FxAccountsUIGlue.js
@@ -10,62 +10,57 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/FxAccountsCommon.js");
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+                                  "resource://gre/modules/SystemAppProxy.jsm");
+
 function FxAccountsUIGlue() {
 }
 
 FxAccountsUIGlue.prototype = {
 
-  _browser: Services.wm.getMostRecentWindow("navigator:browser"),
-
   _contentRequest: function(aEventName, aData) {
     let deferred = Promise.defer();
 
-    let content = this._browser.getContentWindow();
-    if (!content) {
-      deferred.reject("InternalErrorNoContent");
-      return;
-    }
-
     let id = uuidgen.generateUUID().toString();
 
-    content.addEventListener("mozFxAccountsRPContentEvent",
-                             function onContentEvent(result) {
+    SystemAppProxy.addEventListener("mozFxAccountsRPContentEvent",
+                                    function onContentEvent(result) {
       let msg = result.detail;
       if (!msg || !msg.id || msg.id != id) {
         deferred.reject("InternalErrorWrongContentEvent");
-        content.removeEventListener("mozFxAccountsRPContentEvent",
-                                    onContentEvent);
+        SystemAppProxy.removeEventListener("mozFxAccountsRPContentEvent",
+                                           onContentEvent);
         return;
       }
 
       log.debug("Got content event " + JSON.stringify(msg));
 
       if (msg.error) {
         deferred.reject(msg);
       } else {
         deferred.resolve(msg.result);
       }
-      content.removeEventListener("mozFxAccountsRPContentEvent",
-                                  onContentEvent);
+      SystemAppProxy.removeEventListener("mozFxAccountsRPContentEvent",
+                                         onContentEvent);
     });
 
     let detail = {
        eventName: aEventName,
        id: id,
        data: aData
     };
     log.debug("Send chrome event " + JSON.stringify(detail));
-    this._browser.shell.sendCustomEvent("mozFxAccountsUnsolChromeEvent", detail);
+    SystemAppProxy._sendCustomEvent("mozFxAccountsUnsolChromeEvent", detail);
 
     return deferred.promise;
   },
 
   signInFlow: function() {
     return this._contentRequest("openFlow");
   },
 
--- a/b2g/components/PaymentGlue.js
+++ b/b2g/components/PaymentGlue.js
@@ -18,16 +18,19 @@ const kOpenPaymentConfirmationEvent = "o
 const kOpenPaymentFlowEvent = "open-payment-flow-dialog";
 
 const PREF_DEBUG = "dom.payment.debug";
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+                                  "resource://gre/modules/SystemAppProxy.jsm");
+
 function PaymentUI() {
   try {
     this._debug =
       Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
       && Services.prefs.getBoolPref(PREF_DEBUG);
   } catch(e) {
     this._debug = false;
   }
@@ -40,23 +43,16 @@ PaymentUI.prototype = {
                                                         aSuccessCb,
                                                         aErrorCb) {
     let _error = function _error(errorMsg) {
       if (aErrorCb) {
         aErrorCb.onresult(aRequestId, errorMsg);
       }
     };
 
-    let browser = Services.wm.getMostRecentWindow("navigator:browser");
-    let content = browser.getContentWindow();
-    if (!content) {
-      _error("NO_CONTENT_WINDOW");
-      return;
-    }
-
     // The UI should listen for mozChromeEvent 'open-payment-confirmation-dialog'
     // type in order to create and show the payment request confirmation frame
     // embeded within a trusted dialog.
     let id = kOpenPaymentConfirmationEvent + "-" + this.getRandomId();
     let detail = {
       type: kOpenPaymentConfirmationEvent,
       id: id,
       requestId: aRequestId,
@@ -73,41 +69,34 @@ PaymentUI.prototype = {
       }
 
       if (msg.userSelection && aSuccessCb) {
         aSuccessCb.onresult(aRequestId, msg.userSelection);
       } else if (msg.errorMsg) {
         _error(msg.errorMsg);
       }
 
-      content.removeEventListener("mozContentEvent", this._handleSelection);
+      SystemAppProxy.removeEventListener("mozContentEvent", this._handleSelection);
       this._handleSelection = null;
     }).bind(this);
-    content.addEventListener("mozContentEvent", this._handleSelection);
+    SystemAppProxy.addEventListener("mozContentEvent", this._handleSelection);
 
-    browser.shell.sendChromeEvent(detail);
+    SystemAppProxy.dispatchEvent(detail);
   },
 
   showPaymentFlow: function showPaymentFlow(aRequestId,
                                             aPaymentFlowInfo,
                                             aErrorCb) {
     let _error = function _error(errorMsg) {
       if (aErrorCb) {
         aErrorCb.onresult(aRequestId, errorMsg);
       }
     };
 
     // We ask the UI to browse to the selected payment flow.
-    let browser = Services.wm.getMostRecentWindow("navigator:browser");
-    let content = browser.getContentWindow();
-    if (!content) {
-      _error("NO_CONTENT_WINDOW");
-      return;
-    }
-
     let id = kOpenPaymentFlowEvent + "-" + this.getRandomId();
     let detail = {
       type: kOpenPaymentFlowEvent,
       id: id,
       requestId: aRequestId,
       uri: aPaymentFlowInfo.uri,
       method: aPaymentFlowInfo.requestMethod,
       jwt: aPaymentFlowInfo.jwt
@@ -118,24 +107,24 @@ PaymentUI.prototype = {
     // content.
     this._loadPaymentShim = (function _loadPaymentShim(evt) {
       let msg = evt.detail;
       if (msg.id != id) {
         return;
       }
 
       if (msg.errorMsg) {
-        content.removeEventListener("mozContentEvent", this._loadPaymentShim);
+        SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
         this._loadPaymentShim = null;
         _error("ERROR_LOADING_PAYMENT_SHIM: " + msg.errorMsg);
         return;
       }
 
       if (!msg.frame) {
-        content.removeEventListener("mozContentEvent", this._loadPaymentShim);
+        SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
         this._loadPaymentShim = null;
         _error("ERROR_LOADING_PAYMENT_SHIM");
         return;
       }
 
       // Try to load the payment shim file containing the payment callbacks
       // in the content script.
       let frame = msg.frame;
@@ -147,66 +136,60 @@ PaymentUI.prototype = {
         mm.sendAsyncMessage("Payment:LoadShim", { requestId: aRequestId });
       } catch (e) {
         if (this._debug) {
           this.LOG("Error loading " + kPaymentShimFile + " as a frame script: "
                     + e);
         }
         _error("ERROR_LOADING_PAYMENT_SHIM");
       } finally {
-        content.removeEventListener("mozContentEvent", this._loadPaymentShim);
+        SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
         this._loadPaymentShim = null;
       }
     }).bind(this);
-    content.addEventListener("mozContentEvent", this._loadPaymentShim);
+    SystemAppProxy.addEventListener("mozContentEvent", this._loadPaymentShim);
 
     // We also listen for UI notifications about a closed payment flow. The UI
     // should provide the reason of the closure within the 'errorMsg' parameter
     this._notifyPayFlowClosed = (function _notifyPayFlowClosed(evt) {
       let msg = evt.detail;
       if (msg.id != id) {
         return;
       }
 
       if (msg.type != 'cancel') {
         return;
       }
 
       if (msg.errorMsg) {
         _error(msg.errorMsg);
       }
-      content.removeEventListener("mozContentEvent",
-                                  this._notifyPayFlowClosed);
+      SystemAppProxy.removeEventListener("mozContentEvent",
+                                         this._notifyPayFlowClosed);
       this._notifyPayFlowClosed = null;
     }).bind(this);
-    content.addEventListener("mozContentEvent",
-                             this._notifyPayFlowClosed);
+    SystemAppProxy.addEventListener("mozContentEvent",
+                               this._notifyPayFlowClosed);
 
-    browser.shell.sendChromeEvent(detail);
+    SystemAppProxy.dispatchEvent(detail);
   },
 
   cleanup: function cleanup() {
-    let browser = Services.wm.getMostRecentWindow("navigator:browser");
-    let content = browser.getContentWindow();
-    if (!content) {
-      return;
-    }
-
     if (this._handleSelection) {
-      content.removeEventListener("mozContentEvent", this._handleSelection);
+      SystemAppProxy.removeEventListener("mozContentEvent", this._handleSelection);
       this._handleSelection = null;
     }
 
     if (this._notifyPayFlowClosed) {
-      content.removeEventListener("mozContentEvent", this._notifyPayFlowClosed);
+      SystemAppProxy.removeEventListener("mozContentEvent", this._notifyPayFlowClosed);
       this._notifyPayFlowClosed = null;
     }
 
     if (this._loadPaymentShim) {
-      content.removeEventListener("mozContentEvent", this._loadPaymentShim);
+      SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
       this._loadPaymentShim = null;
     }
   },
 
   getRandomId: function getRandomId() {
     return uuidgen.generateUUID().toString();
   },
 
--- a/b2g/components/SignInToWebsite.jsm
+++ b/b2g/components/SignInToWebsite.jsm
@@ -81,16 +81,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/identity/IdentityUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
                                   "resource://gre/modules/identity/MinimalIdentity.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Logger",
                                   "resource://gre/modules/identity/LogUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+                                  "resource://gre/modules/SystemAppProxy.jsm");
+
 // The default persona uri; can be overwritten with toolkit.identity.uri pref.
 // Do this if you want to repoint to a different service for testing.
 // There's no point in setting up an observer to monitor the pref, as b2g prefs
 // can only be overwritten when the profie is recreated.  So just get the value
 // on start-up.
 let kPersonaUri = "https://firefoxos.persona.org";
 try {
   kPersonaUri = Services.prefs.getCharPref("toolkit.identity.uri");
@@ -117,36 +120,20 @@ const kIdentityDelegateReady = "identity
 const kIdentityControllerDoMethod = "identity-controller-doMethod";
 
 function log(...aMessageArgs) {
   Logger.log.apply(Logger, ["SignInToWebsiteController"].concat(aMessageArgs));
 }
 
 log("persona uri =", kPersonaUri);
 
-/*
- * ContentInterface encapsulates the our content functions.  There are only two:
- *
- * getContent       - return the current content window
- * sendChromeEvent  - send a chromeEvent from the browser shell
- */
-let ContentInterface = {
-  _getBrowser: function SignInToWebsiteController__getBrowser() {
-    return Services.wm.getMostRecentWindow("navigator:browser");
-  },
-
-  getContent: function SignInToWebsiteController_getContent() {
-    return this._getBrowser().getContentWindow();
-  },
-
-  sendChromeEvent: function SignInToWebsiteController_sendChromeEvent(detail) {
-    detail.uri = kPersonaUri;
-    this._getBrowser().shell.sendChromeEvent(detail);
-  }
-};
+function sendChromeEvent(details) {
+  details.uri = kPersonaUri;
+  SystemAppProxy.dispatchEvent(details);
+}
 
 function Pipe() {
   this._watchers = [];
 }
 
 Pipe.prototype = {
   init: function pipe_init() {
     Services.obs.addObserver(this, "identity-child-process-shutdown", false);
@@ -212,43 +199,36 @@ Pipe.prototype = {
 
     if (this._watchers.length === 0) {
       log("No more watchers; clean up persona host iframe");
       let detail = {
         type: kCloseIdentityDialog
       };
       log('telling content to close the dialog');
       // tell content to close the dialog
-      ContentInterface.sendChromeEvent(detail);
+      sendChromeEvent(detail);
     }
   },
 
   communicate: function(aRpOptions, aContentOptions, aMessageCallback) {
     let rpID = aRpOptions.id;
     let rpMM = aRpOptions.mm;
     if (rpMM) {
       this._addWatcher(rpID, rpMM);
     }
 
     log("RP options:", aRpOptions, "\n  content options:", aContentOptions);
 
     // This content variable is injected into the scope of
     // kIdentityShimFile, where it is used to access the BrowserID object
     // and its internal API.
-    let content = ContentInterface.getContent();
     let mm = null;
     let uuid = getRandomId();
     let self = this;
 
-    if (!content) {
-      log("ERROR: what the what? no content window?");
-      // aErrorCb.onresult("NO_CONTENT_WINDOW");
-      return;
-    }
-
     function removeMessageListeners() {
       if (mm) {
         mm.removeMessageListener(kIdentityDelegateFinished, identityDelegateFinished);
         mm.removeMessageListener(kIdentityControllerDoMethod, aMessageCallback);
       }
     }
 
     function identityDelegateFinished() {
@@ -256,31 +236,31 @@ Pipe.prototype = {
 
       let detail = {
         type: kDoneIdentityDialog,
         showUI: aContentOptions.showUI || false,
         id: kDoneIdentityDialog + "-" + uuid,
         requestId: aRpOptions.id
       };
       log('received delegate finished; telling content to close the dialog');
-      ContentInterface.sendChromeEvent(detail);
+      sendChromeEvent(detail);
       self._removeWatchers(rpID, rpMM);
     }
 
-    content.addEventListener("mozContentEvent", function getAssertion(evt) {
+    SystemAppProxy.addEventListener("mozContentEvent", function getAssertion(evt) {
       let msg = evt.detail;
       if (!msg.id.match(uuid)) {
         return;
       }
 
       switch (msg.id) {
         case kOpenIdentityDialog + '-' + uuid:
           if (msg.type === 'cancel') {
             // The user closed the dialog.  Clean up and call cancel.
-            content.removeEventListener("mozContentEvent", getAssertion);
+            SystemAppProxy.removeEventListener("mozContentEvent", getAssertion);
             removeMessageListeners();
             aMessageCallback({json: {method: "cancel"}});
           } else {
             // The window has opened.  Inject the identity shim file containing
             // the callbacks in the content script.  This could be either the
             // visible popup that the user interacts with, or it could be an
             // invisible frame.
             let frame = evt.detail.frame;
@@ -304,17 +284,17 @@ Pipe.prototype = {
             mm.sendAsyncMessage(aContentOptions.message, aRpOptions);
           }
           break;
 
         case kDoneIdentityDialog + '-' + uuid:
           // Received our assertion.  The message manager callbacks will handle
           // communicating back to the IDService.  All we have to do is remove
           // this listener.
-          content.removeEventListener("mozContentEvent", getAssertion);
+          SystemAppProxy.removeEventListener("mozContentEvent", getAssertion);
           break;
 
         default:
           log("ERROR - Unexpected message: id=" + msg.id + ", type=" + msg.type + ", errorMsg=" + msg.errorMsg);
           break;
       }
 
     });
@@ -325,17 +305,17 @@ Pipe.prototype = {
     // available in the context.
     let detail = {
       type: kOpenIdentityDialog,
       showUI: aContentOptions.showUI || false,
       id: kOpenIdentityDialog + "-" + uuid,
       requestId: aRpOptions.id
     };
 
-    ContentInterface.sendChromeEvent(detail);
+    sendChromeEvent(detail);
   }
 
 };
 
 /*
  * The controller sits between the IdentityService used by DOMIdentity
  * and a content process launches an (invisible) iframe or (visible)
  * trusty UI.  Using an injected js script (identity.js), the
new file mode 100644
--- /dev/null
+++ b/b2g/components/SystemAppProxy.jsm
@@ -0,0 +1,114 @@
+/* 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;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+
+this.EXPORTED_SYMBOLS = ['SystemAppProxy'];
+
+let SystemAppProxy = {
+  _frame: null,
+  _isReady: false,
+  _pendingEvents: [],
+  _pendingListeners: [],
+
+  // To call when a new system app iframe is created
+  registerFrame: function (frame) {
+    this._isReady = false;
+    this._frame = frame;
+
+    // Register all DOM event listeners added before we got a ref to the app iframe
+    this._pendingListeners
+        .forEach((args) =>
+                 this.addEventListener.apply(this, args));
+    this._pendingListeners = [];
+  },
+
+  // To call when it is ready to receive events
+  setIsReady: function () {
+    if (this._isReady) {
+      Cu.reportError('SystemApp has already been declared as being ready.');
+    }
+    this._isReady = true;
+
+    // Dispatch all events being queued while the system app was still loading
+    this._pendingEvents
+        .forEach(([type, details]) =>
+                 this._sendCustomEvent(type, details));
+    this._pendingEvents = [];
+  },
+
+  /*
+   * Common way to send an event to the system app.
+   *
+   * // In gecko code:
+   *   SystemAppProxy.sendCustomEvent('foo', { data: 'bar' });
+   * // In system app:
+   *   window.addEventListener('foo', function (event) {
+   *     event.details == 'bar'
+   *   });
+   */
+  _sendCustomEvent: function systemApp_sendCustomEvent(type, details) {
+    let content = this._frame ? this._frame.contentWindow : null;
+
+    // If the system app isn't ready yet,
+    // queue events until someone calls setIsLoaded
+    if (!this._isReady || !content) {
+      this._pendingEvents.push([type, details]);
+      return null;
+    }
+
+    let event = content.document.createEvent('CustomEvent');
+
+    let payload;
+    // If the root object already has __exposedProps__,
+    // we consider the caller already wrapped (correctly) the object.
+    if ('__exposedProps__' in details) {
+      payload = details;
+    } else {
+      payload = details ? Cu.cloneInto(details, content) : {};
+    }
+
+    event.initCustomEvent(type, true, false, payload);
+    content.dispatchEvent(event);
+
+    return event;
+  },
+
+  // Now deprecated, use sendCustomEvent with a custom event name
+  dispatchEvent: function systemApp_sendChromeEvent(details) {
+    return this._sendCustomEvent('mozChromeEvent', details);
+  },
+
+  // Listen for dom events on the system app
+  addEventListener: function systemApp_addEventListener() {
+    let content = this._frame ? this._frame.contentWindow : null;
+    if (!content) {
+      this._pendingListeners.push(arguments);
+      return false;
+    }
+
+    content.addEventListener.apply(content, arguments);
+    return true;
+  },
+
+  removeEventListener: function systemApp_removeEventListener(name, listener) {
+    let content = this._frame ? this._frame.contentWindow : null;
+    if (content) {
+      content.removeEventListener.apply(content, arguments);
+    } else {
+      let idx = this._pendingListeners.indexOf(listener);
+      if (idx != -1) {
+        this._pendingListeners.splice(idx, 1);
+      }
+    }
+  }
+
+};
+this.SystemAppProxy = SystemAppProxy;
+
--- a/b2g/components/UpdatePrompt.js
+++ b/b2g/components/UpdatePrompt.js
@@ -56,16 +56,19 @@ function useSettings() {
   // and trying to use settings in this scenario causes lots of weird
   // assertions at shutdown time.
   if (typeof useSettings.result === "undefined") {
     useSettings.result = !Services.env.get("XPCSHELL_TEST_PROFILE_DIR");
   }
   return useSettings.result;
 }
 
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+                                  "resource://gre/modules/SystemAppProxy.jsm");
+
 function UpdateCheckListener(updatePrompt) {
   this._updatePrompt = updatePrompt;
 }
 
 UpdateCheckListener.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateCheckListener]),
 
   _updatePrompt: null,
@@ -128,34 +131,27 @@ UpdatePrompt.prototype = {
                                          Ci.nsIProgressEventSink,
                                          Ci.nsIObserver]),
   _xpcom_factory: XPCOMUtils.generateSingletonFactory(UpdatePrompt),
 
   _update: null,
   _applyPromptTimer: null,
   _waitingForIdle: false,
   _updateCheckListner: null,
-  _pendingEvents: [],
 
   get applyPromptTimeout() {
     return Services.prefs.getIntPref(PREF_APPLY_PROMPT_TIMEOUT);
   },
 
   get applyIdleTimeout() {
     return Services.prefs.getIntPref(PREF_APPLY_IDLE_TIMEOUT);
   },
 
-  handleContentStart: function UP_handleContentStart(shell) {
-    let content = shell.contentBrowser.contentWindow;
-    content.addEventListener("mozContentEvent", this);
-
-    for (let i = 0; i < this._pendingEvents.length; i++) {
-      shell.sendChromeEvent(this._pendingEvents[i]);
-    }
-    this._pendingEvents.length = 0;
+  handleContentStart: function UP_handleContentStart() {
+    SystemAppProxy.addEventListener("mozContentEvent", this);
   },
 
   // nsIUpdatePrompt
 
   // FIXME/bug 737601: we should have users opt-in to downloading
   // updates when on a billed pipe.  Initially, opt-in for 3g, but
   // that doesn't cover all cases.
   checkForUpdates: function UP_checkForUpdates() { },
@@ -285,25 +281,22 @@ UpdatePrompt.prototype = {
     this._update = aUpdate;
     return this.sendChromeEvent(aType, detail);
   },
 
   sendChromeEvent: function UP_sendChromeEvent(aType, aDetail) {
     let detail = aDetail || {};
     detail.type = aType;
 
-    let browser = Services.wm.getMostRecentWindow("navigator:browser");
-    if (!browser) {
-      this._pendingEvents.push(detail);
+    let sent = SystemAppProxy.dispatchEvent(detail);
+    if (!sent) {
       log("Warning: Couldn't send update event " + aType +
           ": no content browser. Will send again when content becomes available.");
       return false;
     }
-
-    browser.shell.sendChromeEvent(detail);
     return true;
   },
 
   handleAvailableResult: function UP_handleAvailableResult(aDetail) {
     // If the user doesn't choose "download", the updater will implicitly call
     // showUpdateAvailable again after a certain period of time
     switch (aDetail.result) {
       case "download":
--- a/b2g/components/WebappsUpdater.jsm
+++ b/b2g/components/WebappsUpdater.jsm
@@ -11,44 +11,40 @@ const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "settings",
                                    "@mozilla.org/settingsService;1",
                                    "nsISettingsService");
 
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+                                  "resource://gre/modules/SystemAppProxy.jsm");
+
 function debug(aStr) {
   //dump("--*-- WebappsUpdater: " + aStr);
 }
 
 this.WebappsUpdater = {
   _checkingApps: false,
-  _pendingEvents: [],
 
-  handleContentStart: function(aShell) {
-    let content = aShell.contentBrowser.contentWindow;
-    this._pendingEvents.forEach(aShell.sendChromeEvent);
-
-    this._pendingEvents.length = 0;
+  handleContentStart: function() {
   },
 
   sendChromeEvent: function(aType, aDetail) {
     let detail = aDetail || {};
     detail.type = aType;
 
-    let browser = Services.wm.getMostRecentWindow("navigator:browser");
-    if (!browser) {
-      this._pendingEvents.push(detail);
+    let sent = SystemAppProxy.dispatchEvent(detail);
+    if (!sent) {
       debug("Warning: Couldn't send update event " + aType +
           ": no content browser. Will send again when content becomes available.");
       return false;
     }
 
-    browser.shell.sendChromeEvent(detail);
     return true;
   },
 
   _appsUpdated: function(aApps) {
     debug("appsUpdated: " + aApps.length + " apps to update");
     let lock = settings.createLock();
     lock.set("apps.updateStatus", "check-complete", null);
     this.sendChromeEvent("apps-update-check", { apps: aApps });
--- a/b2g/components/moz.build
+++ b/b2g/components/moz.build
@@ -1,15 +1,15 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-TEST_DIRS += ['test']
+DIRS += ['test']
 
 EXTRA_COMPONENTS += [
     'ActivitiesGlue.js',
     'AlertsService.js',
     'B2GAboutRedirector.js',
     'ContentHandler.js',
     'ContentPermissionPrompt.js',
     'FilePicker.js',
@@ -37,16 +37,17 @@ EXTRA_PP_COMPONENTS += [
 if CONFIG['MOZ_UPDATER']:
     EXTRA_PP_COMPONENTS += [
         'UpdatePrompt.js',
     ]
 
 EXTRA_JS_MODULES += [
     'ErrorPage.jsm',
     'SignInToWebsite.jsm',
+    'SystemAppProxy.jsm',
     'TelURIParser.jsm',
     'WebappsUpdater.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk':
     EXTRA_JS_MODULES += [
       'GlobalSimulatorScreen.jsm'
     ]
--- a/b2g/components/test/mochitest/mochitest.ini
+++ b/b2g/components/test/mochitest/mochitest.ini
@@ -1,10 +1,14 @@
 [DEFAULT]
-run-if = toolkit == "gonk"
 support-files =
   permission_handler_chrome.js
   SandboxPromptTest.html
   filepicker_path_handler_chrome.js
+  systemapp_helper.js
 
 [test_sandbox_permission.html]
+run-if = toolkit == "gonk"
 [test_filepicker_path.html]
+run-if = toolkit == "gonk"
 [test_permission_deny.html]
+run-if = toolkit == "gonk"
+[test_systemapp.html]
--- a/b2g/components/test/mochitest/permission_handler_chrome.js
+++ b/b2g/components/test/mochitest/permission_handler_chrome.js
@@ -8,49 +8,29 @@ function debug(str) {
   dump("CHROME PERMISSON HANDLER -- " + str + "\n");
 }
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const { Services } = Cu.import("resource://gre/modules/Services.jsm");
-
-let browser = Services.wm.getMostRecentWindow("navigator:browser");
-let shell;
+const { SystemAppProxy } = Cu.import("resource://gre/modules/SystemAppProxy.jsm");
 
-function loadShell() {
-  if (!browser) {
-    debug("no browser");
-    return false;
+let eventHandler = function(evt) {
+  if (!evt.detail || evt.detail.type !== "permission-prompt") {
+    return;
   }
-  shell = browser.shell;
-  return true;
-}
 
-function getContentWindow() {
-  return shell.contentBrowser.contentWindow;
-}
+  sendAsyncMessage("permission-request", evt.detail);
+};
 
-if (loadShell()) {
-  let content = getContentWindow();
-  let eventHandler = function(evt) {
-    if (!evt.detail || evt.detail.type !== "permission-prompt") {
-      return;
-    }
-
-    sendAsyncMessage("permission-request", evt.detail);
-  };
-
-  content.addEventListener("mozChromeEvent", eventHandler);
+SystemAppProxy.addEventListener("mozChromeEvent", eventHandler);
 
-  // need to remove ChromeEvent listener after test finished.
-  addMessageListener("teardown", function() {
-    content.removeEventListener("mozChromeEvent", eventHandler);
-  });
+// need to remove ChromeEvent listener after test finished.
+addMessageListener("teardown", function() {
+  SystemAppProxy.removeEventListener("mozChromeEvent", eventHandler);
+});
 
-  addMessageListener("permission-response", function(detail) {
-    let event = content.document.createEvent('CustomEvent');
-    event.initCustomEvent('mozContentEvent', true, true, detail);
-    content.dispatchEvent(event);
-  });
-}
+addMessageListener("permission-response", function(detail) {
+  SystemAppProxy._sendCustomEvent('mozContentEvent', detail);
+});
 
new file mode 100644
--- /dev/null
+++ b/b2g/components/test/mochitest/systemapp_helper.js
@@ -0,0 +1,141 @@
+const Cu = Components.utils;
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm");
+
+// Load a duplicated copy of the jsm to prevent messing with the currently running one
+let scope = {};
+Services.scriptloader.loadSubScript("resource://gre/modules/SystemAppProxy.jsm", scope);
+const { SystemAppProxy } = scope;
+
+let frame;
+
+let index = -1;
+function next() {
+  index++;
+  if (index >= steps.length) {
+    assert.ok(false, "Shouldn't get here!");
+    return;
+  }
+  try {
+    steps[index]();
+  } catch(ex) {
+    assert.ok(false, "Caught exception: " + ex);
+  }
+}
+
+// Listen for events received by the system app document
+// to ensure that we receive all of them, in an expected order and time
+let isLoaded = false;
+let n = 0;
+function listener(event) {
+  if (!isLoaded) {
+    assert.ok(false, "Received event before the iframe is ready");
+    return;
+  }
+  n++;
+  if (n == 1) {
+    assert.equal(event.type, "mozChromeEvent");
+    assert.equal(event.detail.name, "first");
+  } else if (n == 2) {
+    assert.equal(event.type, "custom");
+    assert.equal(event.detail.name, "second");
+
+    next(); // call checkEventDispatching
+  } else if (n == 3) {
+    assert.equal(event.type, "custom");
+    assert.equal(event.detail.name, "third");
+  } else if (n == 4) {
+    assert.equal(event.type, "mozChromeEvent");
+    assert.equal(event.detail.name, "fourth");
+
+    next(); // call checkEventListening();
+  } else {
+    assert.ok(false, "Unexpected event of type " + event.type);
+  }
+}
+
+
+let steps = [
+  function waitForWebapps() {
+    // We are using webapps API later in this test and we need to ensure
+    // it is fully initialized before trying to use it
+    let { DOMApplicationRegistry } =  Cu.import('resource://gre/modules/Webapps.jsm', {});
+    DOMApplicationRegistry.registryReady.then(function () {
+      next();
+    });
+  },
+
+  function earlyEvents() {
+    // Immediately try to send events
+    SystemAppProxy.dispatchEvent({ name: "first" });
+    SystemAppProxy._sendCustomEvent("custom", { name: "second" });
+    next();
+  },
+
+  function createFrame() {
+    // Create a fake system app frame
+    let win = Services.wm.getMostRecentWindow("navigator:browser");
+    let doc = win.document;
+    frame = doc.createElement("iframe");
+    doc.documentElement.appendChild(frame);
+
+    // Ensure that events are correctly sent to the frame.
+    // `listener` is going to call next()
+    frame.contentWindow.addEventListener("mozChromeEvent", listener);
+    frame.contentWindow.addEventListener("custom", listener);
+
+    // Ensure that listener being registered before the system app is ready
+    // are correctly removed from the pending list
+    function removedListener() {
+      assert(false, "Listener isn't correctly removed from the pending list");
+    }
+    SystemAppProxy.addEventListener("mozChromeEvent", removedListener);
+    SystemAppProxy.removeEventListener("mozChromeEvent", removedListener);
+
+    // Register it to the JSM
+    SystemAppProxy.registerFrame(frame);
+    assert.ok(true, "Frame created and registered");
+
+    frame.contentWindow.addEventListener("load", function onload() {
+      frame.contentWindow.removeEventListener("load", onload);
+      assert.ok(true, "Frame document loaded");
+
+      // Declare that the iframe is now loaded.
+      // That should dispatch early events
+      isLoaded = true;
+      SystemAppProxy.setIsReady();
+      assert.ok(true, "Frame declared as loaded");
+
+      // Once pending events are received,
+      // we will run checkEventDispatching from `listener` function
+    });
+
+    frame.setAttribute("src", "data:text/html,system app");
+  },
+
+  function checkEventDispatching() {
+    // Send events after the iframe is ready,
+    // they should be dispatched right away
+    SystemAppProxy._sendCustomEvent("custom", { name: "third" });
+    SystemAppProxy.dispatchEvent({ name: "fourth" });
+    // Once this 4th event is received, we will run checkEventListening
+  },
+
+  function checkEventListening() {
+    SystemAppProxy.addEventListener("mozContentEvent", function onContentEvent(event) {
+      assert.equal(event.detail.name, "first-content", "received a system app event");
+      SystemAppProxy.removeEventListener("mozContentEvent", onContentEvent);
+
+      next();
+    });
+    let win = frame.contentWindow;
+    win.dispatchEvent(new win.CustomEvent("mozContentEvent", { detail: {name: "first-content"} }));
+  },
+
+  function endOfTest() {
+    frame.remove();
+    sendAsyncMessage("finish");
+  }
+];
+
+next();
new file mode 100644
--- /dev/null
+++ b/b2g/components/test/mochitest/test_systemapp.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=963239
+-->
+<head>
+  <meta charset="utf-8">
+  <title>SystemAppProxy Test</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=963239">SystemAppProxy.jsm</a>
+<script type="application/javascript">
+
+"use strict";
+
+var gUrl = SimpleTest.getTestFileURL("systemapp_helper.js");
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+
+SimpleTest.waitForExplicitFinish();
+gScript.addMessageListener("finish", function () {
+  SimpleTest.ok(true, "chrome test script finished");
+  gScript.destroy();
+  SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
--- a/b2g/components/test/unit/test_fxaccounts.js
+++ b/b2g/components/test/unit/test_fxaccounts.js
@@ -11,30 +11,29 @@ Cu.import("resource://services-common/ut
 Cu.import("resource://testing-common/httpd.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsMgmtService",
                                   "resource://gre/modules/FxAccountsMgmtService.jsm",
                                   "FxAccountsMgmtService");
 
 // At end of test, restore original state
 const ORIGINAL_AUTH_URI = Services.prefs.getCharPref("identity.fxaccounts.auth.uri");
-const ORIGINAL_SHELL = FxAccountsMgmtService._shell;
+let { SystemAppProxy } = Cu.import("resource://gre/modules/FxAccountsMgmtService.jsm");
+const ORIGINAL_SENDCUSTOM = SystemAppProxy._sendCustomEvent;
 do_register_cleanup(function() {
   Services.prefs.setCharPref("identity.fxaccounts.auth.uri", ORIGINAL_AUTH_URI);
-  FxAccountsMgmtService._shell = ORIGINAL_SHELL;
+  SystemAppProxy._sendCustomEvent = ORIGINAL_SENDCUSTOM;
 });
 
 // Make profile available so that fxaccounts can store user data
 do_get_profile();
 
-// Mock the b2g shell; make message passing possible
-let mockShell = {
-  sendCustomEvent: function(aEventName, aMsg) {
-    Services.obs.notifyObservers({wrappedJSObject: aMsg}, aEventName, null);
-  },
+// Mock the system app proxy; make message passing possible
+let mockSendCustomEvent = function(aEventName, aMsg) {
+  Services.obs.notifyObservers({wrappedJSObject: aMsg}, aEventName, null);
 };
 
 function run_test() {
   run_next_test();
 }
 
 add_task(function test_overall() {
   do_check_neq(FxAccountsMgmtService, null);
@@ -139,17 +138,17 @@ add_test(function test_invalidEmailCase_
       default:
         do_throw("wat!");
         break;
     }
   }
 
   Services.obs.addObserver(onMessage, "mozFxAccountsChromeEvent", false);
 
-  FxAccountsMgmtService._shell = mockShell;
+  SystemAppProxy._sendCustomEvent = mockSendCustomEvent;
 
   // Trigger signIn using an email with incorrect capitalization
   FxAccountsMgmtService.handleEvent({
     detail: {
       id: "signIn",
       data: {
         method: "signIn",
         accountId: clientEmail,
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,21 +14,21 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3796021a52948a2997d9e449ab2c83279d3e1074"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1958454595b1fa0e061f0652ae965629993f5708"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="12408eb142739c7de87ab7ee0d0d2854d5c298f3"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="9b393a454feef2967ff164ee33fcad3905baedef"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="70b698c2e8d1764a1e27527a102df6452e405b9a"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="89c5816399e71bda92a8959b5b771c04d6672ea3"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="f6a198295f65ac38f8511803654a3583a1c666af">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3796021a52948a2997d9e449ab2c83279d3e1074"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="1958454595b1fa0e061f0652ae965629993f5708"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="70b698c2e8d1764a1e27527a102df6452e405b9a"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="89c5816399e71bda92a8959b5b771c04d6672ea3"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
@@ -123,14 +123,14 @@
   <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/>
   <project name="platform/system/vold" path="system/vold" revision="8de05d4a52b5a91e7336e6baa4592f945a6ddbea"/>
   <default remote="caf" revision="refs/tags/android-4.3_r2.1" sync-j="4"/>
   <!-- Emulator specific things -->
   <project name="android-development" path="development" remote="b2g" revision="dab55669da8f48b6e57df95d5af9f16b4a87b0b1"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="09485b73629856b21b2ed6073e327ab0e69a1189"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="5586bf43f8d2e61abcd701e8b7a63c6842dd4f87"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="a3467d423c2ed54f2b4efbc5b47c06af85c3bdc3"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="72e3a520e3c700839f07ba0113fd527b923c3330"/>
   <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="3b002889eca9e47f77ef10af44619c8c56df069b"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
 </manifest>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="a9e08b91e9cd1f0930f16cfc49ec72f63575d5fe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3796021a52948a2997d9e449ab2c83279d3e1074"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="1958454595b1fa0e061f0652ae965629993f5708"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="70b698c2e8d1764a1e27527a102df6452e405b9a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="89c5816399e71bda92a8959b5b771c04d6672ea3"/>
   <!-- Stock Android things -->
@@ -119,14 +119,14 @@
   <project name="platform/system/security" path="system/security" revision="583374f69f531ba68fc3dcbff1f74893d2a96406"/>
   <project name="platform/system/vold" path="system/vold" revision="d4455b8cf361f8353e8aebac15ffd64b4aedd2b9"/>
   <project name="platform_frameworks_av" path="frameworks/av" remote="b2g" revision="ee814270f52127febfcf29bacf9f1b327d7c4c29"/>
   <project name="platform_system_core" path="system/core" remote="b2g" revision="9395eb5aa885cf6d305a202de6e9694a58a89717"/>
   <default remote="caf" revision="refs/tags/android-4.4.2_r1" sync-j="4"/>
   <!-- Emulator specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="72ffdf71c68a96309212eb13d63560d66db14c9e"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="a706bf388e30ec1f5273cf72cd0156e429f23fac"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="5ba6bcbdba30842b02f33f595660e32e3b9182d8"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="0909f27aaa6bf55314edddb48bb1cd6296a2d55c"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="694cecf256122d0cb3b6a1a4efb4b5c7401db223"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="e5dba2723b3e417b107cf9546fb8e10b98e977ae"/>
   <project name="platform/development" path="development" revision="5968ff4e13e0d696ad8d972281fc27ae5a12829b"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="0951179277915335251c5e11d242e4e1a8c2236f"/>
 </manifest>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,21 +14,21 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3796021a52948a2997d9e449ab2c83279d3e1074"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1958454595b1fa0e061f0652ae965629993f5708"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="12408eb142739c7de87ab7ee0d0d2854d5c298f3"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="9b393a454feef2967ff164ee33fcad3905baedef"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="70b698c2e8d1764a1e27527a102df6452e405b9a"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="89c5816399e71bda92a8959b5b771c04d6672ea3"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -13,17 +13,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="f6a198295f65ac38f8511803654a3583a1c666af">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3796021a52948a2997d9e449ab2c83279d3e1074"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="1958454595b1fa0e061f0652ae965629993f5708"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="70b698c2e8d1764a1e27527a102df6452e405b9a"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="89c5816399e71bda92a8959b5b771c04d6672ea3"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "138a50b765612b5469efbd27f64f6b06ceb0ee2b", 
+    "revision": "65cb0c455f058c49aead7de9a245d90aa890276c", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3796021a52948a2997d9e449ab2c83279d3e1074"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1958454595b1fa0e061f0652ae965629993f5708"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="70b698c2e8d1764a1e27527a102df6452e405b9a"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="89c5816399e71bda92a8959b5b771c04d6672ea3"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3796021a52948a2997d9e449ab2c83279d3e1074"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1958454595b1fa0e061f0652ae965629993f5708"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="70b698c2e8d1764a1e27527a102df6452e405b9a"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3796021a52948a2997d9e449ab2c83279d3e1074"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1958454595b1fa0e061f0652ae965629993f5708"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="70b698c2e8d1764a1e27527a102df6452e405b9a"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="89c5816399e71bda92a8959b5b771c04d6672ea3"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3796021a52948a2997d9e449ab2c83279d3e1074"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1958454595b1fa0e061f0652ae965629993f5708"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="70b698c2e8d1764a1e27527a102df6452e405b9a"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="89c5816399e71bda92a8959b5b771c04d6672ea3"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="f6a198295f65ac38f8511803654a3583a1c666af">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3796021a52948a2997d9e449ab2c83279d3e1074"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="1958454595b1fa0e061f0652ae965629993f5708"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="70b698c2e8d1764a1e27527a102df6452e405b9a"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="89c5816399e71bda92a8959b5b771c04d6672ea3"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3796021a52948a2997d9e449ab2c83279d3e1074"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1958454595b1fa0e061f0652ae965629993f5708"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="70b698c2e8d1764a1e27527a102df6452e405b9a"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="89c5816399e71bda92a8959b5b771c04d6672ea3"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/dom/inputmethod/Keyboard.jsm
+++ b/dom/inputmethod/Keyboard.jsm
@@ -11,16 +11,19 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
   "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster");
 
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+                                  "resource://gre/modules/SystemAppProxy.jsm");
+
 this.Keyboard = {
   _formMM: null,     // The current web page message manager.
   _keyboardMM: null, // The keyboard app message manager.
   _systemMessageName: [
     'SetValue', 'RemoveFocus', 'SetSelectedOption', 'SetSelectedOptions'
   ],
 
   _messageNames: [
@@ -226,17 +229,17 @@ this.Keyboard = {
     this.sendToKeyboard(newEventName, msg.data);
   },
 
   handleFocusChange: function keyboardHandleFocusChange(msg) {
     this.forwardEvent('Keyboard:FocusChange', msg);
 
     // Chrome event, used also to render value selectors; that's why we need
     // the info about choices / min / max here as well...
-    this.sendChromeEvent({
+    SystemAppProxy.dispatchEvent({
       type: 'inputmethod-contextchange',
       inputType: msg.data.type,
       value: msg.data.value,
       choices: JSON.stringify(msg.data.choices),
       min: msg.data.min,
       max: msg.data.max
     });
   },
@@ -261,23 +264,23 @@ this.Keyboard = {
     this.sendToForm('Forms:Select:Blur', {});
   },
 
   replaceSurroundingText: function keyboardReplaceSurroundingText(msg) {
     this.sendToForm('Forms:ReplaceSurroundingText', msg.data);
   },
 
   showInputMethodPicker: function keyboardShowInputMethodPicker() {
-    this.sendChromeEvent({
+    SystemAppProxy.dispatchEvent({
       type: "inputmethod-showall"
     });
   },
 
   switchToNextInputMethod: function keyboardSwitchToNextInputMethod() {
-    this.sendChromeEvent({
+    SystemAppProxy.dispatchEvent({
       type: "inputmethod-next"
     });
   },
 
   getText: function keyboardGetText(msg) {
     this.sendToForm('Forms:GetText', msg.data);
   },
 
@@ -307,19 +310,12 @@ this.Keyboard = {
   _layouts: null,
   setLayouts: function keyboardSetLayoutCount(layouts) {
     // The input method plugins may not have loaded yet,
     // cache the layouts so on init we can respond immediately instead
     // of going back and forth between keyboard_manager
     this._layouts = layouts;
 
     this.sendToKeyboard('Keyboard:LayoutsChange', layouts);
-  },
-
-  sendChromeEvent: function(event) {
-    let browser = Services.wm.getMostRecentWindow("navigator:browser");
-    if (browser && browser.shell) {
-      browser.shell.sendChromeEvent(event);;
-    }
   }
 };
 
 this.Keyboard.init();
--- a/dom/system/gonk/NetworkUtils.cpp
+++ b/dom/system/gonk/NetworkUtils.cpp
@@ -70,16 +70,17 @@ static const uint32_t NETD_COMMAND_UNSOL
 static const uint32_t NETD_COMMAND_INTERFACE_CHANGE     = 600;
 static const uint32_t NETD_COMMAND_BANDWIDTH_CONTROLLER = 601;
 
 static const char* INTERFACE_DELIMIT = ",";
 static const char* USB_CONFIG_DELIMIT = ",";
 static const char* NETD_MESSAGE_DELIMIT = " ";
 
 static const uint32_t BUF_SIZE = 1024;
+static const uint32_t MAX_SSID_SIZE = 33;
 
 static uint32_t SDK_VERSION;
 
 struct IFProperties {
   char gateway[PROPERTY_VALUE_MAX];
   char dns1[PROPERTY_VALUE_MAX];
   char dns2[PROPERTY_VALUE_MAX];
 };
@@ -262,19 +263,44 @@ static void split(char* str, const char*
   char *s = strtok(str, sep);
   while (s != nullptr) {
     result.AppendElement(NS_ConvertUTF8toUTF16(s));
     s = strtok(nullptr, sep);
   }
 }
 
 /**
+ * Helper function to do string search and replace.
+ */
+static void replace(const char* src,
+                    const char* strold,
+                    const char* strnew,
+                    char* dst)
+{
+  const char *p, *q;
+  char *r;
+  uint32_t oldlen = strlen(strold);
+  uint32_t newlen = strlen(strnew);
+
+  for (p = src, r = dst; (q = strstr(p, strold)) != nullptr; p = q + oldlen) {
+    strncpy(r, p, q - p);
+    r +=  q - p;
+    strncpy(r, strnew, newlen);
+    r += newlen;
+  }
+  strcpy(r, p);
+}
+
+/**
  * Helper function that implement join function.
  */
-static void join(nsTArray<nsCString>& array, const char* sep, const uint32_t maxlen, char* result)
+static void join(nsTArray<nsCString>& array,
+                 const char* sep,
+                 const uint32_t maxlen,
+                 char* result)
 {
 #define CHECK_LENGTH(len, add, max)  len += add;          \
                                      if (len > max - 1)   \
                                        return;            \
 
   uint32_t len = 0;
   uint32_t seplen = strlen(sep);
 
@@ -509,35 +535,41 @@ void NetworkUtils::stopAccessPointDriver
  *      argv[6] - Security
  *      argv[7] - Key
  */
 void NetworkUtils::setAccessPoint(CommandChain* aChain,
                                   CommandCallback aCallback,
                                   NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
+  char ssid[MAX_SSID_SIZE];
+  char key[MAX_COMMAND_SIZE];
+
+  escapeQuote(GET_CHAR(mSsid), ssid);
+  escapeQuote(GET_CHAR(mKey), key);
+
   if (SDK_VERSION >= 19) {
     snprintf(command, MAX_COMMAND_SIZE - 1, "softap set %s \"%s\" broadcast 6 %s \"%s\"",
                      GET_CHAR(mIfname),
-                     GET_CHAR(mSsid),
+                     ssid,
                      GET_CHAR(mSecurity),
-                     GET_CHAR(mKey));
+                     key);
   } else if (SDK_VERSION >= 16) {
     snprintf(command, MAX_COMMAND_SIZE - 1, "softap set %s \"%s\" %s \"%s\"",
                      GET_CHAR(mIfname),
-                     GET_CHAR(mSsid),
+                     ssid,
                      GET_CHAR(mSecurity),
-                     GET_CHAR(mKey));
+                     key);
   } else {
     snprintf(command, MAX_COMMAND_SIZE - 1, "softap set %s %s \"%s\" %s \"%s\" 6 0 8",
                      GET_CHAR(mIfname),
                      GET_CHAR(mWifictrlinterfacename),
-                     GET_CHAR(mSsid),
+                     ssid,
                      GET_CHAR(mSecurity),
-                     GET_CHAR(mKey));
+                     key);
   }
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::cleanUpStream(CommandChain* aChain,
                                  CommandCallback aCallback,
                                  NetworkResultOptions& aResult)
@@ -1533,16 +1565,22 @@ bool NetworkUtils::setUSBTethering(Netwo
   } else {
     DEBUG("Stopping USB Tethering on %s <-> %s",
            GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
     RUN_CHAIN(aOptions, sUSBDisableChain, usbTetheringFail)
   }
   return true;
 }
 
+void NetworkUtils::escapeQuote(const char* src, char* dst)
+{
+  replace(src, "\\", "\\\\", dst);
+  replace(src, "\"", "\\\"", dst);
+}
+
 void NetworkUtils::checkUsbRndisState(NetworkParams& aOptions)
 {
   static uint32_t retry = 0;
 
   char currentState[PROPERTY_VALUE_MAX];
   property_get(SYS_USB_STATE_PROPERTY, currentState, nullptr);
 
   nsTArray<nsCString> stateFuncs;
--- a/dom/system/gonk/NetworkUtils.h
+++ b/dom/system/gonk/NetworkUtils.h
@@ -370,16 +370,17 @@ private:
   void sendBroadcastMessage(uint32_t code, char* reason);
 
   /**
    * Utility functions.
    */
   void checkUsbRndisState(NetworkParams& aOptions);
   void dumpParams(NetworkParams& aOptions, const char* aType);
 
+  static void escapeQuote(const char* src, char* dst);
   inline uint32_t netdResponseType(uint32_t code);
   inline bool isBroadcastMessage(uint32_t code);
   inline bool isError(uint32_t code);
   inline bool isComplete(uint32_t code);
   inline bool isProceeding(uint32_t code);
   void Shutdown();
   /**
    * Callback function to send netd result to main thread.