Merge b2g-inbound to Mozilla-Central
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 29 Oct 2013 15:14:46 +0100
changeset 152652 4aefc2ca4861a857c788247f7a233ae212236286
parent 152628 bc3916e4f4c5b022bbb13e47e75eb94756383994 (current diff)
parent 152651 ac5de341a26b2a05ebe5e66e0b3938efd97f6993 (diff)
child 152653 b03c963cabd71d53211f9aba1b57d0433fc695e2
push id25554
push usercbook@mozilla.com
push dateTue, 29 Oct 2013 14:15:07 +0000
treeherdermozilla-central@4aefc2ca4861 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone28.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge b2g-inbound to Mozilla-Central
b2g/config/gaia.json
configure.in
dom/webidl/moz.build
netwerk/protocol/app/AppProtocolHandler.js
netwerk/protocol/app/AppProtocolHandler.manifest
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,5 @@
 {
-    "revision": "166023c341cca944563c410f05dff37a992cb05b", 
+
+    "revision": "d2dbad943faf566fe36dbe79086127da837af6a3", 
     "repo_path": "/integration/gaia-central"
-}
+}
\ No newline at end of file
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -540,19 +540,16 @@
 @BINPATH@/components/ActivityWrapper.js
 @BINPATH@/components/ActivityMessageConfigurator.js
 
 @BINPATH@/components/TCPSocket.js
 @BINPATH@/components/TCPServerSocket.js
 @BINPATH@/components/TCPSocketParentIntermediary.js
 @BINPATH@/components/TCPSocket.manifest
 
-@BINPATH@/components/AppProtocolHandler.js
-@BINPATH@/components/AppProtocolHandler.manifest
-
 @BINPATH@/components/Payment.js
 @BINPATH@/components/PaymentFlowInfo.js
 @BINPATH@/components/PaymentRequestInfo.js
 @BINPATH@/components/Payment.manifest
 
 ; InputMethod API
 @BINPATH@/components/MozKeyboard.js
 @BINPATH@/components/InputMethod.manifest
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -516,19 +516,16 @@
 @BINPATH@/components/Push.js
 @BINPATH@/components/Push.manifest
 @BINPATH@/components/PushServiceLauncher.js
 @BINPATH@/components/TCPSocket.js
 @BINPATH@/components/TCPServerSocket.js
 @BINPATH@/components/TCPSocketParentIntermediary.js
 @BINPATH@/components/TCPSocket.manifest
 
-@BINPATH@/components/AppProtocolHandler.js
-@BINPATH@/components/AppProtocolHandler.manifest
-
 @BINPATH@/components/Payment.js
 @BINPATH@/components/PaymentFlowInfo.js
 @BINPATH@/components/PaymentRequestInfo.js
 @BINPATH@/components/Payment.manifest
 
 #ifdef MOZ_WEBRTC
 @BINPATH@/components/PeerConnection.js
 @BINPATH@/components/PeerConnection.manifest
--- a/configure.in
+++ b/configure.in
@@ -3980,17 +3980,17 @@ MOZ_UNIVERSALCHARDET=1
 MOZ_URL_CLASSIFIER=
 MOZ_XUL=1
 MOZ_ZIPWRITER=1
 NS_PRINTING=1
 MOZ_PDF_PRINTING=
 MOZ_DISABLE_CRYPTOLEGACY=
 NSS_DISABLE_DBM=
 NECKO_COOKIES=1
-NECKO_PROTOCOLS_DEFAULT="about data file ftp http res viewsource websocket wyciwyg device"
+NECKO_PROTOCOLS_DEFAULT="about app data file ftp http res viewsource websocket wyciwyg device"
 USE_ARM_KUSER=
 BUILD_CTYPES=1
 MOZ_USE_NATIVE_POPUP_WINDOWS=
 MOZ_ANDROID_HISTORY=
 MOZ_WEBSMS_BACKEND=
 MOZ_ANDROID_BEAM=
 ACCESSIBILITY=1
 MOZ_TIME_MANAGER=
--- a/dom/cellbroadcast/interfaces/nsIDOMMozCellBroadcastMessage.idl
+++ b/dom/cellbroadcast/interfaces/nsIDOMMozCellBroadcastMessage.idl
@@ -5,26 +5,26 @@
 #include "nsISupports.idl"
 
 interface nsIDOMMozCellBroadcastEtwsInfo;
 
 /**
  * MozCellBroadcastMessage encapsulates Cell Broadcast short message service
  * (CBS) messages.
  */
-[scriptable, uuid(cb210e8b-ad9c-4052-b70c-02ec0c25d669)]
+[scriptable, uuid(6abe65de-6729-41f7-906a-3f3a2dbe30ae)]
 interface nsIDOMMozCellBroadcastMessage : nsISupports
 {
   /**
    * Indication of the geographical area over which the Message Code is unique,
    * and the display mode.
    *
    * Possible values are: "cell-immediate", "plmn", "location-area" and "cell".
    */
-  readonly attribute DOMString geographicalScope;
+  readonly attribute DOMString gsmGeographicalScope;
 
   /**
    * The Message Code differentiates between messages from the same source and
    * type (e.g., with the same Message Identifier).
    */
   readonly attribute unsigned short messageCode;
 
   /**
@@ -54,16 +54,21 @@ interface nsIDOMMozCellBroadcastMessage 
    * System time stamp at receival.
    */
   readonly attribute jsval timestamp; // jsval is for Date.
 
   /**
    * Additional ETWS-specific info.
    */
   readonly attribute nsIDOMMozCellBroadcastEtwsInfo etws;
+
+  /**
+   * Service Category.
+   */
+  readonly attribute long cdmaServiceCategory;
 };
 
 /**
  * ETWS (Earthquake and Tsunami Warning service) Primary Notification message
  * specific information.
  */
 [scriptable, uuid(af009d9a-f5e8-4573-a6ee-a85118465bed)]
 interface nsIDOMMozCellBroadcastEtwsInfo : nsISupports
--- a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_etws.js
+++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_etws.js
@@ -97,30 +97,31 @@ function testEtwsMessageAttributes() {
     // ok(event instanceof MozCellBroadcastEvent,
     //    "event is instanceof " + event.constructor)
     ok(event, "event is valid");
 
     let message = event.message;
     ok(message, "event.message is valid");
 
     // Attributes other than `language` and `body` should always be assigned.
-    ok(message.geographicalScope != null, "message.geographicalScope");
+    ok(message.gsmGeographicalScope != null, "message.gsmGeographicalScope");
     ok(message.messageCode != null, "message.messageCode");
     ok(message.messageId != null, "message.messageId");
     ok('language' in message, "message.language");
     ok(message.language == null, "message.language");
     ok('body' in message, "message.body");
     ok(message.body == null, "message.body");
     is(message.messageClass, "normal", "message.messageClass");
     ok(message.timestamp != null, "message.timestamp");
     ok(message.etws != null, "message.etws");
     ok(message.etws.warningType != null, "message.etws.warningType");
     ok(message.etws.emergencyUserAlert != null,
        "message.etws.emergencyUserAlert");
     ok(message.etws.popup != null, "message.etws.popup");
+    ok(message.cdmaServiceCategory != null, "message.cdmaServiceCategory");
 
     window.setTimeout(testReceiving_ETWS_GeographicalScope, 0);
   });
 
   // Here we use a simple ETWS message for test.
   let pdu = buildHexStr(0, CB_MESSAGE_SIZE_ETWS * 2); // 6 octets
   sendCellBroadcastMessage(pdu);
 }
@@ -129,18 +130,18 @@ function testReceiving_ETWS_Geographical
   log("Test receiving ETWS Primary Notification - Geographical Scope");
 
   function do_test(gs, nextTest) {
     // Here we use a simple ETWS message for test.
     let pdu = buildHexStr(((gs & 0x03) << 14), 4)
             + buildHexStr(0, (CB_MESSAGE_SIZE_ETWS - 2) * 2);
 
     doTestHelper(pdu, nextTest, function (message) {
-      is(message.geographicalScope, CB_GSM_GEOGRAPHICAL_SCOPE_NAMES[gs],
-         "message.geographicalScope");
+      is(message.gsmGeographicalScope, CB_GSM_GEOGRAPHICAL_SCOPE_NAMES[gs],
+         "message.gsmGeographicalScope");
     });
   }
 
   repeat(do_test, seq(CB_GSM_GEOGRAPHICAL_SCOPE_NAMES.length),
          testReceiving_ETWS_MessageCode);
 }
 
 function testReceiving_ETWS_MessageCode() {
--- a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm.js
+++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm.js
@@ -152,29 +152,30 @@ function testGsmMessageAttributes() {
     // ok(event instanceof MozCellBroadcastEvent,
     //    "event is instanceof " + event.constructor)
     ok(event, "event is valid");
 
     let message = event.message;
     ok(message, "event.message is valid");
 
     // Attributes other than `language` and `body` should always be assigned.
-    ok(message.geographicalScope != null, "message.geographicalScope");
+    ok(message.gsmGeographicalScope != null, "message.gsmGeographicalScope");
     ok(message.messageCode != null, "message.messageCode");
     ok(message.messageId != null, "message.messageId");
     ok(message.language != null, "message.language");
     ok(message.body != null, "message.body");
     ok(message.messageClass != null, "message.messageClass");
     ok(message.timestamp != null, "message.timestamp");
     ok('etws' in message, "message.etws");
     if (message.etws) {
       ok('warningType' in message.etws, "message.etws.warningType");
       ok(message.etws.emergencyUserAlert != null, "message.etws.emergencyUserAlert");
       ok(message.etws.popup != null, "message.etws.popup");
     }
+    ok(message.cdmaServiceCategory != null, "message.cdmaServiceCategory");
 
     window.setTimeout(testReceiving_GSM_GeographicalScope, 0);
   });
 
   // Here we use a simple GSM message for test.
   let pdu = buildHexStr(0, CB_MESSAGE_SIZE_GSM * 2);
   sendCellBroadcastMessage(pdu);
 }
@@ -182,18 +183,18 @@ function testGsmMessageAttributes() {
 function testReceiving_GSM_GeographicalScope() {
   log("Test receiving GSM Cell Broadcast - Geographical Scope");
 
   function do_test(gs, nextTest) {
     let pdu = buildHexStr(((gs & 0x03) << 14), 4)
             + buildHexStr(0, (CB_MESSAGE_SIZE_GSM - 2) * 2);
 
     doTestHelper(pdu, nextTest, function (message) {
-      is(message.geographicalScope, CB_GSM_GEOGRAPHICAL_SCOPE_NAMES[gs],
-         "message.geographicalScope");
+      is(message.gsmGeographicalScope, CB_GSM_GEOGRAPHICAL_SCOPE_NAMES[gs],
+         "message.gsmGeographicalScope");
     });
   }
 
   repeat(do_test, seq(CB_GSM_GEOGRAPHICAL_SCOPE_NAMES.length),
          testReceiving_GSM_MessageCode);
 }
 
 function testReceiving_GSM_MessageCode() {
@@ -435,17 +436,36 @@ function testReceiving_GSM_Multipart() {
     }
 
     doTestHelper(pdus, nextTest, function (message) {
       is(message.body.length, (numParts * CB_MAX_CONTENT_7BIT),
          "message.body");
     });
   }
 
-  repeat(do_test, seq(16, 1), cleanUp);
+  repeat(do_test, seq(16, 1), testReceiving_GSM_ServiceCategory);
+}
+
+function testReceiving_GSM_ServiceCategory() {
+  log("Test receiving GSM Cell Broadcast - Service Category");
+
+  cbs.addEventListener("received", function onreceived(event) {
+    cbs.removeEventListener("received", onreceived);
+
+    let message = event.message;
+
+    // Bug 910091
+    // "Service Category" is not defined in GSM.  We should always get '0' here.
+    is(message.cdmaServiceCategory, 0, "message.cdmaServiceCategory");
+
+    window.setTimeout(cleanUp, 0);
+  });
+
+  let pdu = buildHexStr(0, CB_MESSAGE_SIZE_GSM * 2);
+  sendCellBroadcastMessage(pdu);
 }
 
 function cleanUp() {
   if (pendingEmulatorCmdCount > 0) {
     window.setTimeout(cleanUp, 100);
     return;
   }
 
--- a/dom/system/gonk/NetworkManager.js
+++ b/dom/system/gonk/NetworkManager.js
@@ -239,17 +239,19 @@ NetworkManager.prototype = {
             // Add extra host route. For example, mms proxy or mmsc.
             this.setExtraHostRoute(network);
             // Remove pre-created default route and let setAndConfigureActive()
             // to set default route only on preferred network
             this.removeDefaultRoute(network.name);
             this.setAndConfigureActive();
             // Update data connection when Wifi connected/disconnected
             if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
-              this.mRIL.getRadioInterface(0).updateRILNetworkInterface();
+              for (let i = 0; i < this.mRIL.numRadioInterfaces; i++) {
+                this.mRIL.getRadioInterface(i).updateRILNetworkInterface();
+              }
             }
 
             this.onConnectionChanged(network);
 
             // Probing the public network accessibility after routing table is ready
             CaptivePortalDetectionHelper.notify(CaptivePortalDetectionHelper.EVENT_CONNECT, this.active);
             break;
           case Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED:
@@ -267,17 +269,19 @@ NetworkManager.prototype = {
             } else if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) {
               this.removeDefaultRoute(network.name);
             }
             // Abort ongoing captive portal detection on the wifi interface
             CaptivePortalDetectionHelper.notify(CaptivePortalDetectionHelper.EVENT_DISCONNECT, network);
             this.setAndConfigureActive();
             // Update data connection when Wifi connected/disconnected
             if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
-              this.mRIL.getRadioInterface(0).updateRILNetworkInterface();
+              for (let i = 0; i < this.mRIL.numRadioInterfaces; i++) {
+                this.mRIL.getRadioInterface(i).updateRILNetworkInterface();
+              }
             }
             break;
         }
         break;
       case TOPIC_INTERFACE_REGISTERED:
         let regNetwork = subject.QueryInterface(Ci.nsINetworkInterface);
         // Add extra host route. For example, mms proxy or mmsc.
         this.setExtraHostRoute(regNetwork);
--- a/dom/system/gonk/RILContentHelper.js
+++ b/dom/system/gonk/RILContentHelper.js
@@ -313,49 +313,52 @@ MobileCFInfo.prototype = {
   action: -1,
   reason: -1,
   number: null,
   timeSeconds: 0,
   serviceClass: -1
 };
 
 function CellBroadcastMessage(pdu) {
-  this.geographicalScope = RIL.CB_GSM_GEOGRAPHICAL_SCOPE_NAMES[pdu.geographicalScope];
+  this.gsmGeographicalScope = RIL.CB_GSM_GEOGRAPHICAL_SCOPE_NAMES[pdu.geographicalScope];
   this.messageCode = pdu.messageCode;
   this.messageId = pdu.messageId;
   this.language = pdu.language;
   this.body = pdu.fullBody;
   this.messageClass = pdu.messageClass;
   this.timestamp = new Date(pdu.timestamp);
 
   if (pdu.etws != null) {
     this.etws = new CellBroadcastEtwsInfo(pdu.etws);
   }
+
+  this.cdmaServiceCategory = pdu.serviceCategory;
 }
 CellBroadcastMessage.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMMozCellBroadcastMessage]),
   classID:        CELLBROADCASTMESSAGE_CID,
   classInfo:      XPCOMUtils.generateCI({
     classID:          CELLBROADCASTMESSAGE_CID,
     classDescription: "CellBroadcastMessage",
     flags:            Ci.nsIClassInfo.DOM_OBJECT,
     interfaces:       [Ci.nsIDOMMozCellBroadcastMessage]
   }),
 
   // nsIDOMMozCellBroadcastMessage
 
-  geographicalScope: null,
+  gsmGeographicalScope: null,
   messageCode: null,
   messageId: null,
   language: null,
   body: null,
   messageClass: null,
   timestamp: null,
 
-  etws: null
+  etws: null,
+  cdmaServiceCategory: null
 };
 
 function CellBroadcastEtwsInfo(etwsInfo) {
   if (etwsInfo.warningType != null) {
     this.warningType = RIL.CB_ETWS_WARNING_TYPE_NAMES[etwsInfo.warningType];
   }
   this.emergencyUserAlert = etwsInfo.emergencyUserAlert;
   this.popup = etwsInfo.popup;
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -1055,16 +1055,17 @@ RadioInterface.prototype = {
                                        this.clientId, message);
         break;
       case "sms-received":
         let ackOk = this.handleSmsReceived(message);
         if (ackOk) {
           this.workerMessenger.send("ackSMS", { result: RIL.PDU_FCS_OK });
         }
         return;
+      case "broadcastsms-received":
       case "cellbroadcast-received":
         message.timestamp = Date.now();
         gMessageManager.sendCellBroadcastMessage("RIL:CellBroadcastReceived",
                                                  this.clientId, message);
         break;
       case "datacallstatechange":
         this.handleDataCallState(message);
         break;
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -2758,18 +2758,25 @@ this.PDU_CDMA_MSG_CODING_GSM_DCS = 0x0A;
 this.PDU_CDMA_MSG_TYPE_DELIVER = 0x01;        // Receive
 this.PDU_CDMA_MSG_TYPE_SUBMIT = 0x02;         // Send
 
 // SMS User Data Subparameters, as defined in 3GPP2 C.S0015-A v2.0, Table 4.5-1
 this.PDU_CDMA_MSG_USERDATA_MSG_ID = 0x00;           // Message Identifier
 this.PDU_CDMA_MSG_USERDATA_BODY = 0x01;             // User Data Body
 this.PDU_CDMA_MSG_USERDATA_TIMESTAMP = 0x03;        // Message Center Time Stamp
 this.PDU_CDMA_REPLY_OPTION = 0x0A;                  // Reply Option
+this.PDU_CDMA_LANGUAGE_INDICATOR = 0x0D;            // Language Indicator
 this.PDU_CDMA_MSG_USERDATA_CALLBACK_NUMBER = 0x0E;  // Callback Number
 
+// CDMA Language Indicator: Language groups
+// see 3GPP2 C.R1001-F table 9.2-1
+this.CB_CDMA_LANG_GROUP = [
+  null, "en", "fr", "es", "ja", "ko", "zh", "he"
+];
+
 // IS-91 Message Type, as defined in TIA/EIA/IS-91-A, Table 9
 this.PDU_CDMA_MSG_CODING_IS_91_TYPE_VOICEMAIL_STATUS = 0x82;
 this.PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS_FULL = 0x83;
 this.PDU_CDMA_MSG_CODING_IS_91_TYPE_CLI = 0x84;
 this.PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS = 0x85;
 
 // CDMA roaming preference mode
 this.CDMA_ROAMING_PREFERENCE_HOME = 0;
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -4215,17 +4215,23 @@ let RIL = {
 
     if (message) {
       message.result = PDU_FCS_OK;
       if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
         // `MS shall ensure that the message has been to the SMS data field in
         // the (U)SIM before sending an ACK to the SC.`  ~ 3GPP 23.038 clause 4
         message.result = PDU_FCS_RESERVED;
       }
-      message.rilMessageType = "sms-received";
+
+      if (message.messageType == PDU_CDMA_MSG_TYPE_BROADCAST) {
+        message.rilMessageType = "broadcastsms-received";
+      } else {
+        message.rilMessageType = "sms-received";
+      }
+
       this.sendChromeMessage(message);
 
       // We will acknowledge receipt of the SMS after we try to store it
       // in the database.
       return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK;
     }
 
     return PDU_FCS_OK;
@@ -8805,35 +8811,38 @@ let CdmaPDUHelper = {
       message.sender += String.fromCharCode(addrDigit);
     }
 
     // User Data
     this.decodeUserData(message);
 
     // Transform message to GSM msg
     let msg = {
-      SMSC:           "",
-      mti:            0,
-      udhi:           0,
-      sender:         message.sender,
-      recipient:      null,
-      pid:            PDU_PID_DEFAULT,
-      epid:           PDU_PID_DEFAULT,
-      dcs:            0,
-      mwi:            null, //message[PDU_CDMA_MSG_USERDATA_BODY].header ? message[PDU_CDMA_MSG_USERDATA_BODY].header.mwi : null,
-      replace:        false,
-      header:         message[PDU_CDMA_MSG_USERDATA_BODY].header,
-      body:           message[PDU_CDMA_MSG_USERDATA_BODY].body,
-      data:           null,
-      timestamp:      message[PDU_CDMA_MSG_USERDATA_TIMESTAMP],
-      status:         null,
-      scts:           null,
-      dt:             null,
-      encoding:       message[PDU_CDMA_MSG_USERDATA_BODY].encoding,
-      messageClass:   GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]
+      SMSC:            "",
+      mti:             0,
+      udhi:            0,
+      sender:          message.sender,
+      recipient:       null,
+      pid:             PDU_PID_DEFAULT,
+      epid:            PDU_PID_DEFAULT,
+      dcs:             0,
+      mwi:             null, //message[PDU_CDMA_MSG_USERDATA_BODY].header ? message[PDU_CDMA_MSG_USERDATA_BODY].header.mwi : null,
+      replace:         false,
+      header:          message[PDU_CDMA_MSG_USERDATA_BODY].header,
+      body:            message[PDU_CDMA_MSG_USERDATA_BODY].body,
+      data:            null,
+      timestamp:       message[PDU_CDMA_MSG_USERDATA_TIMESTAMP],
+      language:        message[PDU_CDMA_LANGUAGE_INDICATOR],
+      status:          null,
+      scts:            null,
+      dt:              null,
+      encoding:        message[PDU_CDMA_MSG_USERDATA_BODY].encoding,
+      messageClass:    GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
+      messageType:     message.messageType,
+      serviceCategory: message.service
     };
 
     return msg;
   },
 
 
   /**
    * Helper for processing received SMS parcel data.
@@ -8925,16 +8934,19 @@ let CdmaPDUHelper = {
           message[id] = this.decodeUserDataMsg(message[PDU_CDMA_MSG_USERDATA_MSG_ID].userHeader);
           break;
         case PDU_CDMA_MSG_USERDATA_TIMESTAMP:
           message[id] = this.decodeUserDataTimestamp();
           break;
         case PDU_CDMA_REPLY_OPTION:
           message[id] = this.decodeUserDataReplyAction();
           break;
+        case PDU_CDMA_LANGUAGE_INDICATOR:
+          message[id] = this.decodeLanguageIndicator();
+          break;
         case PDU_CDMA_MSG_USERDATA_CALLBACK_NUMBER:
           message[id] = this.decodeUserDataCallbackNumber();
           break;
       }
 
       userDataLength -= (length + 2);
       userDataBuffer = [];
     }
@@ -9306,16 +9318,27 @@ let CdmaPDUHelper = {
                    readAck: (replyAction & 0x2) ? true : false,
                    report: (replyAction & 0x1) ? true : false
                  };
 
     return result;
   },
 
   /**
+   * User data subparameter decoder : Language Indicator
+   *
+   * @see 3GGP2 C.S0015-B 2.0, 4.5.14 Language Indicator
+   */
+  decodeLanguageIndicator: function cdma_decodeLanguageIndicator() {
+    let language = BitBufferHelper.readBits(8);
+    let result = CB_CDMA_LANG_GROUP[language];
+    return result;
+  },
+
+  /**
    * User data subparameter decoder : Call-Back Number
    *
    * @see 3GGP2 C.S0015-B 2.0, 4.5.15 Call-Back Number
    */
   decodeUserDataCallbackNumber: function cdma_decodeUserDataCallbackNumber() {
     let digitMode = BitBufferHelper.readBits(1);
     if (digitMode) {
       let numberType = BitBufferHelper.readBits(3),
new file mode 100644
--- /dev/null
+++ b/dom/webidl/AppInfo.webidl
@@ -0,0 +1,12 @@
+/* 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/. */
+
+/**
+  * This dictionnary holds the parameters supporting the app:// protocol.
+  */
+dictionary AppInfo
+{
+  DOMString path = "";
+  boolean   isCoreApp = false;
+};
--- a/dom/webidl/DummyBinding.webidl
+++ b/dom/webidl/DummyBinding.webidl
@@ -22,13 +22,14 @@ interface DummyInterface : EventTarget {
   void MmsParameters(optional MmsParameters arg);
   void MmsAttachment(optional MmsAttachment arg);
   void AsyncScrollEventDetail(optional AsyncScrollEventDetail arg);
   void OpenWindowEventDetail(optional OpenWindowEventDetail arg);
   void DOMWindowResizeEventDetail(optional DOMWindowResizeEventDetail arg);
   void WifiOptions(optional WifiCommandOptions arg1,
                    optional WifiResultOptions arg2);
   void AppNotificationServiceOptions(optional AppNotificationServiceOptions arg);
+  void AppInfo(optional AppInfo arg1);
 };
 
 interface DummyInterfaceWorkers {
   BlobPropertyBag blobBag();
 };
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -15,16 +15,17 @@ PREPROCESSED_WEBIDL_FILES = [
     'Navigator.webidl',
     'Window.webidl',
 ]
 
 WEBIDL_FILES = [
     'AbstractWorker.webidl',
     'AnalyserNode.webidl',
     'AnimationEvent.webidl',
+    'AppInfo.webidl',
     'AppNotificationServiceOptions.webidl',
     'ArchiveReader.webidl',
     'ArchiveRequest.webidl',
     'Attr.webidl',
     'AudioBuffer.webidl',
     'AudioBufferSourceNode.webidl',
     'AudioChannel.webidl',
     'AudioContext.webidl',
--- a/layout/base/PositionedEventTargeting.cpp
+++ b/layout/base/PositionedEventTargeting.cpp
@@ -169,16 +169,26 @@ IsElementClickable(nsIFrame* aFrame, nsI
     if (content->IsHTML()) {
       if (tag == nsGkAtoms::button ||
           tag == nsGkAtoms::input ||
           tag == nsGkAtoms::select ||
           tag == nsGkAtoms::textarea ||
           tag == nsGkAtoms::label) {
         return true;
       }
+      // Bug 921928: we don't have access to the content of remote iframe.
+      // So fluffing won't go there. We do an optimistic assumption here:
+      // that the content of the remote iframe needs to be a target.
+      if (tag == nsGkAtoms::iframe &&
+          content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mozbrowser,
+                               nsGkAtoms::_true, eIgnoreCase) &&
+          content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::Remote,
+                               nsGkAtoms::_true, eIgnoreCase)) {
+        return true;
+      }
     } else if (content->IsXUL()) {
       nsIAtom* tag = content->Tag();
       // See nsCSSFrameConstructor::FindXULTagData. This code is not
       // really intended to be used with XUL, though.
       if (tag == nsGkAtoms::button ||
           tag == nsGkAtoms::checkbox ||
           tag == nsGkAtoms::radio ||
           tag == nsGkAtoms::autorepeatbutton ||
--- a/layout/base/tests/Makefile.in
+++ b/layout/base/tests/Makefile.in
@@ -110,16 +110,18 @@ MOCHITEST_FILES =	\
 		test_bug582181-2.html \
 		test_bug588174.html \
 		test_bug607529.html \
 		file_bug607529.html \
 		test_bug667512.html \
 		test_bug677878.html \
 		test_bug696020.html \
 		test_event_target_radius.html \
+		test_event_target_iframe_oop.html \
+		bug921928_event_target_iframe_apps_oop.html \
 		test_mozPaintCount.html \
 		test_scroll_event_ordering.html \
 		test_scroll_selection_into_view.html \
 		test_bug583889.html \
 		bug583889_inner1.html \
 		bug583889_inner2.html \
 		test_bug582771.html \
 		test_bug603550.html \
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug921928_event_target_iframe_apps_oop.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test companion for bug 921928</title>
+</head>
+<body>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/test_event_target_iframe_oop.html
@@ -0,0 +1,178 @@
+<!DOCTYPE HTML>
+<html id="html" style="height:100%">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=921928
+-->
+<head>
+  <title>Test for bug 921928</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <style>
+  #dialer {
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 50px;
+    background: green;
+  }
+
+  #apps {
+    position: absolute;
+    left: 0;
+    top: 51px;
+    width: 100%;
+    height: 100px;
+    background: blue;
+  }
+
+  .hit {
+    position: absolute;
+    width: 3px;
+    height: 3px;
+    z-index: 20;
+    background: red;
+    border: 1px solid red;
+  }
+  </style>
+</head>
+<body id="body" style="margin:0; width:100%; height:100%">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var prefs = [
+  ["ui.mouse.radius.enabled", true],
+  ["ui.mouse.radius.inputSource.touchOnly", false],
+  ["ui.mouse.radius.leftmm", 12],
+  ["ui.mouse.radius.topmm", 8],
+  ["ui.mouse.radius.rightmm", 4],
+  ["ui.mouse.radius.bottommm", 4],
+  ["ui.mouse.radius.visitedweight", 50],
+  ["dom.mozBrowserFramesEnabled", true]
+];
+
+var eventTarget;
+var debugHit = [];
+
+function endTest() {
+  SimpleTest.finish();
+  SpecialPowers.removePermission("browser", location.href);
+  for (var pref in prefs) {
+    SpecialPowers.pushPrefEnv({"clear": pref[0]}, function() {});
+  }
+}
+
+function testMouseClick(idPosition, dx, dy, idTarget, msg, options) {
+  eventTarget = null;
+  synthesizeMouse(document.getElementById(idPosition), dx, dy, options || {});
+  try {
+    is(eventTarget.id, idTarget,
+       "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]");
+  } catch (ex) {
+    ok(false, "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]; got " + eventTarget);
+  }
+}
+
+function showDebug() {
+  for (var i = 0; i < debugHit.length; i++) {
+    document.body.appendChild(debugHit[i]);
+  }
+
+  var screenshot = SpecialPowers.snapshotWindow(window, true);
+  dump('IMAGE:' + screenshot.toDataURL() + '\n');
+}
+
+/*
+  Setup the test environment: enabling event fluffing (all ui.* preferences),
+  and enabling remote process.
+*/
+function setupTest(cont) {
+  SpecialPowers.addPermission("browser", true, document);
+  SpecialPowers.pushPrefEnv({"set": prefs}, cont);
+}
+
+function execTest() {
+  /*
+     Creating two iframes that mimics the attention screen behavior on the
+     device:
+       - the 'dialer' iframe is the attention screen you have when a call is
+         in place. it is a green bar, so we copy it as green here too
+       - the 'apps' iframe mimics another application that is being run, be it
+         dialer, sms, ..., anything that the user might want to trigger during
+         a call
+
+     The bug we intent to reproduce here is that in this case, if the user taps
+     onto the top of the 'apps', the event fluffing code will in fact redirect
+     the event to the 'dialer' iframe. In practice, this is bug 921928 where
+     during a call the user wants to place a second call, and while typing the
+     phone number, wants to tap onto the 'delete' key to erase a digit, but ends
+     tapping and activating the dialer.
+   */
+  var dialer = document.createElement('iframe');
+  dialer.id = 'dialer';
+  dialer.src = '';
+  // Force OOP
+  dialer.setAttribute('mozbrowser', 'true');
+  dialer.setAttribute('remote', 'true');
+  document.body.appendChild(dialer);
+
+  var apps = document.createElement('iframe');
+  apps.id = 'apps';
+  apps.src = 'bug921928_event_target_iframe_apps_oop.html';
+  // Force OOP
+  apps.setAttribute('mozbrowser', 'true');
+  apps.setAttribute('remote', 'true');
+  document.body.appendChild(apps);
+
+  var handleEvent = function(event) {
+    eventTarget = event.target;
+
+    // We draw a small red div to show where the event has tapped
+    var hit = document.createElement('div');
+    hit.style.left = (event.clientX - 1.5) + 'px';
+    hit.style.top = (event.clientY - 1.5) + 'px';
+    hit.classList.add('hit');
+    debugHit.push(hit);
+  };
+
+  // In real life, the 'dialer' has a 'mousedown', so we mimic one too,
+  // to reproduce the same behavior
+  dialer.addEventListener('mousedown', function(e) {});
+
+  // This event listener is just here to record what iframe has been hit,
+  // and sets the 'eventTarget' to the iframe's id value so that the
+  // testMouseClick() code can correctly check. We cannot add it on the
+  // 'apps' otherwise it will alter the behavior of the test.
+  document.addEventListener('mousedown', handleEvent);
+
+  // In the following, the coordinates are relative to the iframe
+
+  // First, we check that tapping onto the 'dialer' correctly triggers the
+  // dialer.
+  testMouseClick("dialer", 20, 1, "dialer", "correct hit on dialer with mouse input");
+  testMouseClick("dialer", 20, 1, "dialer", "correct hit on dialer with touch input", {
+    inputSource: SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
+  });
+
+  // Now this is it: we tap inside 'apps', but very close to the border between
+  // 'apps' and 'dialer'. Without the fix from this bug, this test will fail.
+  testMouseClick("apps", 20, 1, "apps", "apps <iframe mozbrowser remote> hit for mouse input");
+  testMouseClick("apps", 20, 1, "apps", "apps <iframe mozbrowser remote> hit for touch input", {
+    inputSource: SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
+  });
+
+  // Show small red spots of where the click happened
+  // showDebug();
+
+  endTest();
+}
+
+function runTest() {
+  setupTest(execTest);
+}
+
+addEventListener('load', function() { SimpleTest.executeSoon(runTest); });
+</script>
+</body>
+</html>
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -382,18 +382,16 @@
 @BINPATH@/components/nsPrompter.manifest
 @BINPATH@/components/nsPrompter.js
 @BINPATH@/components/TelemetryPing.js
 @BINPATH@/components/TelemetryPing.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
-@BINPATH@/components/AppProtocolHandler.js
-@BINPATH@/components/AppProtocolHandler.manifest
 
 @BINPATH@/components/Push.js
 @BINPATH@/components/Push.manifest
 
 @BINPATH@/components/SystemMessageInternal.js
 @BINPATH@/components/SystemMessageManager.js
 @BINPATH@/components/SystemMessageManager.manifest
 
--- a/netwerk/build/Makefile.in
+++ b/netwerk/build/Makefile.in
@@ -64,16 +64,17 @@ LOCAL_INCLUDES = \
   -I$(srcdir)/../base/src \
   -I$(srcdir)/../dns \
   -I$(srcdir)/../socket \
   -I$(srcdir)/../streamconv/src \
   -I$(srcdir)/../streamconv/converters \
   -I$(srcdir)/../mime \
   -I$(srcdir)/../cache \
   -I$(srcdir)/../protocol/about \
+  -I$(srcdir)/../protocol/app \
   -I../dns \
   $(foreach d,$(filter-out about,$(NECKO_PROTOCOLS)), \
     -I$(srcdir)/../protocol/$(d)) \
   $(NULL)
 
 ifeq ($(OS_ARCH),WINNT)
     LOCAL_INCLUDES += -I$(srcdir)/../system/win32
 endif
--- a/netwerk/build/nsNetCID.h
+++ b/netwerk/build/nsNetCID.h
@@ -609,16 +609,28 @@
 { /* fbc81170-1f69-11d3-9344-00104ba0fd40 */         \
     0xfbc81170,                                      \
     0x1f69,                                          \
     0x11d3,                                          \
     {0x93, 0x44, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40} \
 }
 
 /******************************************************************************
+ * netwerk/protocol/app/ classes
+ */
+
+#define NS_APPPROTOCOLHANDLER_CID                    \
+{ /* {B6ED3030-9999-11d3-A178-0050041CAF44} */       \
+    0xb6ed3030,                                      \
+    0x9999,                                          \
+    0x11d3,                                          \
+    {0xa1, 0x78, 0x00, 0x50, 0x04, 0x1c, 0xaf, 0x44} \
+}
+
+/******************************************************************************
  * netwerk/protocol/data/ classes
  */
 
 #define NS_DATAPROTOCOLHANDLER_CID                   \
 { /* {B6ED3030-6183-11d3-A178-0050041CAF44} */       \
     0xb6ed3030,                                      \
     0x6183,                                          \
     0x11d3,                                          \
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -239,16 +239,17 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpDig
 #endif // !NECKO_PROTOCOL_http
 
 #include "mozilla/net/Dashboard.h"
 namespace mozilla {
 namespace net {
   NS_GENERIC_FACTORY_CONSTRUCTOR(Dashboard)
 }
 }
+#include "AppProtocolHandler.h"
 
 #ifdef NECKO_PROTOCOL_res
 // resource
 #include "nsResProtocolHandler.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsResProtocolHandler, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsResURL)
 #endif
 
@@ -768,16 +769,17 @@ NS_DEFINE_NAMED_CID(NS_ABOUT_CACHE_ENTRY
 #endif
 NS_DEFINE_NAMED_CID(NS_SOCKSSOCKETPROVIDER_CID);
 NS_DEFINE_NAMED_CID(NS_SOCKS4SOCKETPROVIDER_CID);
 NS_DEFINE_NAMED_CID(NS_UDPSOCKETPROVIDER_CID);
 NS_DEFINE_NAMED_CID(NS_CACHESERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_APPLICATIONCACHESERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_APPLICATIONCACHENAMESPACE_CID);
 NS_DEFINE_NAMED_CID(NS_APPLICATIONCACHE_CID);
+NS_DEFINE_NAMED_CID(NS_APPPROTOCOLHANDLER_CID);
 #ifdef NECKO_COOKIES
 NS_DEFINE_NAMED_CID(NS_COOKIEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_COOKIESERVICE_CID);
 #endif
 #ifdef NECKO_WIFI
 NS_DEFINE_NAMED_CID(NS_WIFI_MONITOR_COMPONENT_CID);
 #endif
 #ifdef NECKO_PROTOCOL_data
@@ -907,16 +909,17 @@ static const mozilla::Module::CIDEntry k
 #endif
     { &kNS_SOCKSSOCKETPROVIDER_CID, false, nullptr, nsSOCKSSocketProvider::CreateV5 },
     { &kNS_SOCKS4SOCKETPROVIDER_CID, false, nullptr, nsSOCKSSocketProvider::CreateV4 },
     { &kNS_UDPSOCKETPROVIDER_CID, false, nullptr, nsUDPSocketProviderConstructor },
     { &kNS_CACHESERVICE_CID, false, nullptr, nsCacheService::Create },
     { &kNS_APPLICATIONCACHESERVICE_CID, false, nullptr, nsApplicationCacheServiceConstructor },
     { &kNS_APPLICATIONCACHENAMESPACE_CID, false, nullptr, nsApplicationCacheNamespaceConstructor },
     { &kNS_APPLICATIONCACHE_CID, false, nullptr, nsApplicationCacheConstructor },
+    { &kNS_APPPROTOCOLHANDLER_CID, false, nullptr, AppProtocolHandler::Create },
 #ifdef NECKO_COOKIES
     { &kNS_COOKIEMANAGER_CID, false, nullptr, nsICookieServiceConstructor },
     { &kNS_COOKIESERVICE_CID, false, nullptr, nsICookieServiceConstructor },
 #endif
 #ifdef NECKO_WIFI
     { &kNS_WIFI_MONITOR_COMPONENT_CID, false, nullptr, nsWifiMonitorConstructor },
 #endif
 #ifdef NECKO_PROTOCOL_data
@@ -1053,16 +1056,17 @@ static const mozilla::Module::ContractID
 #endif
     { NS_NETWORK_SOCKET_CONTRACTID_PREFIX "socks", &kNS_SOCKSSOCKETPROVIDER_CID },
     { NS_NETWORK_SOCKET_CONTRACTID_PREFIX "socks4", &kNS_SOCKS4SOCKETPROVIDER_CID },
     { NS_NETWORK_SOCKET_CONTRACTID_PREFIX "udp", &kNS_UDPSOCKETPROVIDER_CID },
     { NS_CACHESERVICE_CONTRACTID, &kNS_CACHESERVICE_CID },
     { NS_APPLICATIONCACHESERVICE_CONTRACTID, &kNS_APPLICATIONCACHESERVICE_CID },
     { NS_APPLICATIONCACHENAMESPACE_CONTRACTID, &kNS_APPLICATIONCACHENAMESPACE_CID },
     { NS_APPLICATIONCACHE_CONTRACTID, &kNS_APPLICATIONCACHE_CID },
+    { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "app", &kNS_APPPROTOCOLHANDLER_CID },
 #ifdef NECKO_COOKIES
     { NS_COOKIEMANAGER_CONTRACTID, &kNS_COOKIEMANAGER_CID },
     { NS_COOKIESERVICE_CONTRACTID, &kNS_COOKIESERVICE_CID },
 #endif
 #ifdef NECKO_WIFI
     { NS_WIFI_MONITOR_CONTRACTID, &kNS_WIFI_MONITOR_COMPONENT_CID },
 #endif
 #ifdef NECKO_PROTOCOL_data
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/app/AppProtocolHandler.cpp
@@ -0,0 +1,423 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set expandtab ts=2 sw=2 sts=2 cin: */
+/* 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/. */
+
+#include "AppProtocolHandler.h"
+#include "nsBaseChannel.h"
+#include "nsJARChannel.h"
+#include "nsNetCID.h"
+#include "nsIAppsService.h"
+#include "nsCxPusher.h"
+#include "nsXULAppAPI.h"
+
+/**
+  * This dummy channel implementation only provides enough functionality
+  * to return a fake 404 error when the caller asks for an app:// URL
+  * containing an unknown appId.
+  */
+class DummyChannel : public nsIJARChannel
+                          , nsRunnable
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIREQUEST
+  NS_DECL_NSICHANNEL
+  NS_DECL_NSIJARCHANNEL
+
+  DummyChannel();
+
+  NS_IMETHODIMP Run();
+
+private:
+  bool                        mPending;
+  uint32_t                    mSuspendCount;
+  nsCOMPtr<nsISupports>       mListenerContext;
+  nsCOMPtr<nsIStreamListener> mListener;
+  nsCOMPtr<nsILoadGroup>      mLoadGroup;
+  nsLoadFlags                 mLoadFlags;
+};
+
+NS_IMPL_ISUPPORTS3(DummyChannel, nsIRequest, nsIChannel, nsIJARChannel)
+
+DummyChannel::DummyChannel() : mPending(false)
+                             , mSuspendCount(0)
+                             , mLoadFlags(LOAD_NORMAL)
+{
+}
+
+NS_IMETHODIMP DummyChannel::GetName(nsACString &result)
+{
+  result = "dummy_app_channel";
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::GetStatus(nsresult *aStatus)
+{
+  *aStatus = NS_ERROR_FILE_NOT_FOUND;
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::IsPending(bool *aResult)
+{
+  *aResult = mPending;
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::Suspend()
+{
+  mSuspendCount++;
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::Resume()
+{
+  if (mSuspendCount <= 0) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  if (--mSuspendCount == 0) {
+    NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::Open(nsIInputStream**)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::AsyncOpen(nsIStreamListener* aListener, nsISupports* aContext)
+{
+  mListener = aListener;
+  mListenerContext = aContext;
+  mPending = true;
+
+  if (mLoadGroup) {
+    mLoadGroup->AddRequest(this, aContext);
+  }
+
+  if (mSuspendCount == 0) {
+    NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
+  }
+
+  return NS_OK;
+}
+
+// nsIJarChannel, needed for XHR to turn NS_ERROR_FILE_NOT_FOUND into
+// a 404 error.
+NS_IMETHODIMP DummyChannel::GetIsUnsafe(bool *aResult)
+{
+  *aResult = false;
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::SetAppURI(nsIURI *aURI)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::Run()
+{
+  nsresult rv = mListener->OnStartRequest(this, mListenerContext);
+  NS_ENSURE_SUCCESS(rv, rv);
+  mPending = false;
+  rv = mListener->OnStopRequest(this, mListenerContext, NS_ERROR_FILE_NOT_FOUND);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (mLoadGroup) {
+    mLoadGroup->RemoveRequest(this, mListenerContext, NS_ERROR_FILE_NOT_FOUND);
+  }
+
+  mListener = nullptr;
+  mListenerContext = nullptr;
+  rv = SetNotificationCallbacks(nullptr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::Cancel(nsresult)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetLoadGroup(nsILoadGroup* *aLoadGroup)
+{
+  *aLoadGroup = mLoadGroup;
+  NS_IF_ADDREF(*aLoadGroup);
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
+{
+  mLoadGroup = aLoadGroup;
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+  *aLoadFlags = mLoadFlags;
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+  mLoadFlags = aLoadFlags;
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::GetOriginalURI(nsIURI**)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetOriginalURI(nsIURI*)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetOwner(nsISupports**)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetOwner(nsISupports*)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetNotificationCallbacks(nsIInterfaceRequestor**)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetNotificationCallbacks(nsIInterfaceRequestor*)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetSecurityInfo(nsISupports**)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetContentType(nsACString&)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetContentType(const nsACString&)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetContentCharset(nsACString&)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetContentCharset(const nsACString&)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetContentLength(int64_t*)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetContentLength(int64_t)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetContentDisposition(uint32_t*)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetContentDisposition(uint32_t)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetURI(nsIURI**)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetContentDispositionFilename(nsAString&)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetContentDispositionFilename(nsAString const &)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetContentDispositionHeader(nsACString&)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/**
+  * app:// protocol implementation.
+  */
+
+AppProtocolHandler::AppProtocolHandler() {
+}
+
+AppProtocolHandler::~AppProtocolHandler() {
+  mAppInfoCache.Clear();
+}
+
+NS_IMPL_ISUPPORTS1(AppProtocolHandler, nsIProtocolHandler)
+
+/* static */
+nsresult
+AppProtocolHandler::Create(nsISupports* aOuter,
+                           const nsIID& aIID,
+                           void* *aResult)
+{
+  // Instantiate the service here since that intializes gJarHandler, which we
+  // use indirectly (via our new JarChannel) in NewChannel.
+  nsCOMPtr<nsIProtocolHandler> jarInitializer(
+    do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "jar"));
+  AppProtocolHandler* ph = new AppProtocolHandler();
+  if (ph == nullptr) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  NS_ADDREF(ph);
+  nsresult rv = ph->QueryInterface(aIID, aResult);
+  NS_RELEASE(ph);
+  return rv;
+}
+
+NS_IMETHODIMP
+AppProtocolHandler::GetScheme(nsACString &aResult)
+{
+  aResult.AssignLiteral("app");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AppProtocolHandler::GetDefaultPort(int32_t *aResult)
+{
+  // No ports for the app protocol.
+  *aResult = -1;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AppProtocolHandler::GetProtocolFlags(uint32_t *aResult)
+{
+  *aResult = URI_NOAUTH |
+             URI_DANGEROUS_TO_LOAD |
+             URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AppProtocolHandler::NewURI(const nsACString &aSpec,
+                           const char *aCharset, // ignore charset info
+                           nsIURI *aBaseURI,
+                           nsIURI **result)
+{
+  nsresult rv;
+  nsCOMPtr<nsIStandardURL> surl(do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = surl->Init(nsIStandardURL::URLTYPE_STANDARD, -1, aSpec, aCharset, aBaseURI);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIURL> url(do_QueryInterface(surl, &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  url.forget(result);
+  return NS_OK;
+}
+
+// We map app://ABCDEF/path/to/file.ext to
+// jar:file:///path/to/profile/webapps/ABCDEF/application.zip!/path/to/file.ext
+NS_IMETHODIMP
+AppProtocolHandler::NewChannel(nsIURI* aUri, nsIChannel* *aResult)
+{
+  NS_ENSURE_ARG_POINTER(aUri);
+  nsRefPtr<nsJARChannel> channel = new nsJARChannel();
+
+  nsAutoCString host;
+  nsresult rv = aUri->GetHost(host);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoCString fileSpec;
+  nsCOMPtr<nsIURL> url = do_QueryInterface(aUri);
+  rv = url->GetFilePath(fileSpec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mozilla::dom::AppInfo *appInfo;
+
+  if (!mAppInfoCache.Get(host, &appInfo)) {
+    nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+    if (!appsService) {
+      return NS_ERROR_FAILURE;
+    }
+
+    mozilla::AutoSafeJSContext cx;
+    JS::RootedValue jsInfo(cx);
+    rv = appsService->GetAppInfo(NS_ConvertUTF8toUTF16(host), jsInfo.address());
+    if (NS_FAILED(rv) || !jsInfo.isObject()) {
+      // Return a DummyChannel.
+      printf_stderr("!! Creating a dummy channel for %s (no appInfo)\n", host.get());
+      NS_IF_ADDREF(*aResult = new DummyChannel());
+      return NS_OK;
+    }
+
+    appInfo = new mozilla::dom::AppInfo();
+    JSAutoCompartment ac(cx, &jsInfo.toObject());
+    if (!appInfo->Init(cx, jsInfo) || appInfo->mPath.IsEmpty()) {
+      // Return a DummyChannel.
+      printf_stderr("!! Creating a dummy channel for %s (invalid appInfo)\n", host.get());
+      NS_IF_ADDREF(*aResult = new DummyChannel());
+      return NS_OK;
+    }
+    mAppInfoCache.Put(host, appInfo);
+  }
+
+  bool noRemote = (appInfo->mIsCoreApp ||
+                   XRE_GetProcessType() == GeckoProcessType_Default);
+
+  // In-parent and CoreApps can directly access files, so use jar:file://
+  nsAutoCString jarSpec(noRemote ? "jar:file://"
+                                 : "jar:remoteopenfile://");
+  jarSpec += NS_ConvertUTF16toUTF8(appInfo->mPath) +
+             NS_LITERAL_CSTRING("/application.zip!") +
+             fileSpec;
+
+  nsCOMPtr<nsIURI> jarURI;
+  rv = NS_NewURI(getter_AddRefs(jarURI),
+                 jarSpec, nullptr, nullptr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = channel->Init(jarURI);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = channel->SetAppURI(aUri);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = channel->SetOriginalURI(aUri);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  channel.forget(aResult);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AppProtocolHandler::AllowPort(int32_t aPort, const char *aScheme, bool *aRetval)
+{
+  // No port allowed for this scheme.
+  *aRetval = false;
+  return NS_OK;
+}
+
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/app/AppProtocolHandler.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set expandtab ts=4 sw=4 sts=4 cin: */
+/* 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/. */
+
+#ifndef AppProtocolHandler_
+#define AppProtocolHandler_
+
+#include "nsIProtocolHandler.h"
+#include "nsClassHashtable.h"
+#include "mozilla/dom/AppInfoBinding.h"
+
+class AppProtocolHandler : public nsIProtocolHandler
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  // nsIProtocolHandler methods:
+  NS_DECL_NSIPROTOCOLHANDLER
+
+  // AppProtocolHandler methods:
+  AppProtocolHandler();
+  virtual ~AppProtocolHandler();
+
+  // Define a Create method to be used with a factory:
+  static nsresult Create(nsISupports* aOuter,
+                         const nsIID& aIID,
+                         void* *aResult);
+
+private:
+  nsClassHashtable<nsCStringHashKey, mozilla::dom::AppInfo> mAppInfoCache;
+};
+
+#endif /* AppProtocolHandler_ */
deleted file mode 100644
--- a/netwerk/protocol/app/AppProtocolHandler.js
+++ /dev/null
@@ -1,197 +0,0 @@
-/* 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 Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-const Cr = Components.results;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "appsService",
-                                   "@mozilla.org/AppsService;1",
-                                   "nsIAppsService");
-
-function AppProtocolHandler() {
-  this._appInfo = [];
-  this._runningInParent = Cc["@mozilla.org/xre/runtime;1"]
-                            .getService(Ci.nsIXULRuntime)
-                            .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-}
-
-AppProtocolHandler.prototype = {
-  classID: Components.ID("{b7ad6144-d344-4687-b2d0-b6b9dce1f07f}"),
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]),
-
-  scheme: "app",
-  defaultPort: -1,
-  // Don't allow loading from other protocols, and only from app:// if webapps is granted
-  protocolFlags: Ci.nsIProtocolHandler.URI_NOAUTH |
-                 Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
-                 Ci.nsIProtocolHandler.URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM,
-
-  getAppInfo: function app_phGetAppInfo(aId) {
-
-    if (!this._appInfo[aId]) {
-      this._appInfo[aId] = appsService.getAppInfo(aId);
-    }
-    return this._appInfo[aId];
-  },
-
-  newURI: function app_phNewURI(aSpec, aOriginCharset, aBaseURI) {
-    let uri = Cc["@mozilla.org/network/standard-url;1"]
-              .createInstance(Ci.nsIStandardURL);
-    uri.init(Ci.nsIStandardURL.URLTYPE_STANDARD, -1, aSpec, aOriginCharset,
-             aBaseURI);
-    return uri.QueryInterface(Ci.nsIURI);
-  },
-
-  newChannel: function app_phNewChannel(aURI) {
-    // We map app://ABCDEF/path/to/file.ext to
-    // jar:file:///path/to/profile/webapps/ABCDEF/application.zip!/path/to/file.ext
-    let url = aURI.QueryInterface(Ci.nsIURL);
-    let appId = aURI.host;
-    let fileSpec = url.filePath;
-
-    // Build a jar channel and masquerade as an app:// URI.
-    let appInfo = this.getAppInfo(appId);
-    if (!appInfo) {
-      // That should not happen, so dump() inconditionnally.
-      // We create a dummy channel instead of throwing to let the
-      // downstream user get a 404 error.
-      dump("!! got no appInfo for " + appId + "\n");
-      return new DummyChannel();
-    }
-
-    let uri;
-    if (this._runningInParent || appInfo.isCoreApp) {
-      // In-parent and CoreApps can directly access files, so use jar:file://
-      uri = "jar:file://" + appInfo.path + "/application.zip!" + fileSpec;
-    } else {
-      // non-CoreApps in child need to ask parent for file handle, use jar:ipcfile://
-      uri = "jar:remoteopenfile://" + appInfo.path + "/application.zip!" + fileSpec;
-    }
-    let channel = Services.io.newChannel(uri, null, null);
-    channel.QueryInterface(Ci.nsIJARChannel).setAppURI(aURI);
-    channel.QueryInterface(Ci.nsIChannel).originalURI = aURI;
-
-    return channel;
-  },
-
-  allowPort: function app_phAllowPort(aPort, aScheme) {
-    return false;
-  }
-};
-
-/**
-  * This dummy channel implementation only provides enough functionality
-  * to return a fake 404 error when the caller asks for an app:// URL
-  * containing an unknown appId.
-  */
-function DummyChannel() {
-  this.originalURI = Services.io.newURI("app://unknown/nothing.html", null, null);
-  this.URI = Services.io.newURI("app://unknown/nothing.html", null, null);
-}
-
-DummyChannel.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequest,
-                                         Ci.nsIChannel,
-                                         Ci.nsIJARChannel]),
-
-  // nsIRequest
-  name: "dummy_app_channel",
-
-  isPending: function dc_isPending() {
-    return this._pending;
-  },
-
-  status: Cr.NS_ERROR_FILE_NOT_FOUND,
-
-  cancel: function dc_cancel() {
-  },
-
-  suspend: function dc_suspend() {
-    this._suspendCount++;
-  },
-
-  resume: function dc_resume() {
-    if (this._suspendCount <= 0)
-      throw Cr.NS_ERROR_UNEXPECTED;
-
-    if (--this._suspendCount == 0 && this._pending) {
-      this._dispatch();
-    }
-  },
-
-  loadGroup: null,
-  loadFlags: Ci.nsIRequest.LOAD_NORMAL,
-
-  // nsIChannel
-  owner: null,
-  notificationCallbacks: null,
-  securityInfo: null,
-  contentType: null,
-  contentCharset: null,
-  contentLength: 0,
-  contentDisposition: Ci.nsIChannel.DISPOSITION_INLINE,
-  contentDispositionFilename: "",
-
-  _pending: false,
-  _suspendCount: 0,
-  _listener: null,
-  _context: null,
-
-  open: function dc_open() {
-    return Cr.NS_ERROR_NOT_IMPLEMENTED;
-  },
-
-  _dispatch: function dc_dispatch() {
-    let request = this;
-
-    Services.tm.currentThread.dispatch(
-    {
-      run: function dc_run() {
-        request._listener.onStartRequest(request, request._context);
-        request._listener.onStopRequest(request, request._context,
-                                        Cr.NS_ERROR_FILE_NOT_FOUND);
-        if (request.loadGroup) {
-          request.loadGroup.removeRequest(request, request._context,
-                                          Cr.NS_ERROR_FILE_NOT_FOUND);
-        }
-        request._pending = false;
-        request.notificationCallbacks = null;
-        request._listener = null;
-        request._context = null;
-      }
-    },
-    Ci.nsIThread.DISPATCH_NORMAL);
-  },
-
-  asyncOpen: function dc_asyncopenfunction(aListener, aContext) {
-    if (this.loadGroup) {
-      this.loadGroup.addRequest(this, aContext);
-    }
-
-    this._listener = aListener;
-    this._context = aContext;
-    this._pending = true;
-
-    if (!this._suspended) {
-      this._dispatch();
-    }
-  },
-
-  // nsIJarChannel, needed for XHR to turn NS_ERROR_FILE_NOT_FOUND into
-  // a 404 error.
-  isUnsafe: false,
-
-  setAppURI: function(aURI) {
-    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
-  }
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AppProtocolHandler]);
deleted file mode 100644
--- a/netwerk/protocol/app/AppProtocolHandler.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-# AppProtocolHander.js
-component {b7ad6144-d344-4687-b2d0-b6b9dce1f07f} AppProtocolHandler.js
-contract @mozilla.org/network/protocol;1?name=app {b7ad6144-d344-4687-b2d0-b6b9dce1f07f}
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/app/Makefile.in
@@ -0,0 +1,9 @@
+#
+# 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/.
+
+LOCAL_INCLUDES = \
+  -I$(srcdir)/../../base/src \
+  -I$(srcdir)/../../../modules/libjar \
+  $(NULL)
--- a/netwerk/protocol/app/moz.build
+++ b/netwerk/protocol/app/moz.build
@@ -1,10 +1,18 @@
 # -*- 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/.
 
-EXTRA_COMPONENTS += [
-    'AppProtocolHandler.js',
-    'AppProtocolHandler.manifest',
+MODULE = 'necko'
+
+SOURCES += [
+    'AppProtocolHandler.cpp',
 ]
+
+LIBRARY_NAME = 'nkapp_s'
+
+FAIL_ON_WARNINGS = True
+
+LIBXUL_LIBRARY = True
+