Bug 779500 - WebFM, r=jlebar,glandium sr=jonas
authorRay Cheung <pzhang@mozilla.com>
Wed, 19 Sep 2012 11:23:33 -0400
changeset 107527 75923725b2fc72927458f6281a78ebaab8f6bc71
parent 107526 fa535ebebed4a86bbfad0dee4bbdadc13d8543f8
child 107528 21b31866fcf88d59eca27c0a11fe1815f6c69897
push id1100
push userdcamp@campd.org
push dateThu, 20 Sep 2012 22:53:07 +0000
treeherderfx-team@f731fa718465 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlebar, glandium, jonas
bugs779500
milestone18.0a1
Bug 779500 - WebFM, r=jlebar,glandium sr=jonas
b2g/chrome/content/shell.js
b2g/installer/package-manifest.in
configure.in
content/base/src/nsGkAtomList.h
dom/Makefile.in
dom/base/Makefile.in
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/dom-config.mk
dom/fm/DOMFMRadio.manifest
dom/fm/DOMFMRadioChild.js
dom/fm/DOMFMRadioParent.jsm
dom/fm/FMRadio.cpp
dom/fm/FMRadio.h
dom/fm/Makefile.in
dom/fm/nsFMRadioSettings.cpp
dom/fm/nsFMRadioSettings.h
dom/fm/nsIDOMFMRadio.idl
dom/fm/nsIFMRadio.idl
layout/build/Makefile.in
layout/build/nsLayoutModule.cpp
toolkit/toolkit-makefiles.sh
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -8,16 +8,19 @@ 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');
 Cu.import('resource://gre/modules/ContactService.jsm');
 Cu.import('resource://gre/modules/SettingsChangeNotifier.jsm');
+#ifdef MOZ_B2G_FM
+Cu.import('resource://gre/modules/DOMFMRadioParent.jsm');
+#endif
 Cu.import('resource://gre/modules/AlarmService.jsm');
 Cu.import('resource://gre/modules/ActivitiesService.jsm');
 Cu.import('resource://gre/modules/PermissionPromptHelper.jsm');
 Cu.import('resource://gre/modules/ObjectWrapper.jsm');
 Cu.import('resource://gre/modules/accessibility/AccessFu.jsm');
 Cu.import('resource://gre/modules/Payment.jsm');
 
 XPCOMUtils.defineLazyServiceGetter(Services, 'env',
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -157,16 +157,19 @@
 @BINPATH@/components/dom_apps.xpt
 @BINPATH@/components/dom_base.xpt
 #ifdef MOZ_B2G_RIL
 @BINPATH@/components/dom_telephony.xpt
 @BINPATH@/components/dom_wifi.xpt
 @BINPATH@/components/dom_system_gonk.xpt
 @BINPATH@/components/dom_icc.xpt
 #endif
+#ifdef MOZ_B2G_FM
+@BINPATH@/components/dom_fm.xpt
+#endif
 @BINPATH@/components/dom_battery.xpt
 #ifdef MOZ_B2G_BT
 @BINPATH@/components/dom_bluetooth.xpt
 #endif
 @BINPATH@/components/dom_camera.xpt
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_contacts.xpt
 @BINPATH@/components/dom_alarm.xpt
@@ -447,16 +450,20 @@
 @BINPATH@/components/RILContentHelper.js
 @BINPATH@/components/SmsDatabaseService.manifest
 @BINPATH@/components/SmsDatabaseService.js
 @BINPATH@/components/WifiWorker.js
 @BINPATH@/components/WifiWorker.manifest
 @BINPATH@/components/DOMWifiManager.js
 @BINPATH@/components/DOMWifiManager.manifest
 #endif
+#ifdef MOZ_B2G_FM
+@BINPATH@/components/DOMFMRadioChild.js
+@BINPATH@/components/DOMFMRadio.manifest
+#endif
 #ifdef XP_MACOSX
 @BINPATH@/components/libalerts.dylib
 #endif
 #ifdef MOZ_ENABLE_DBUS
 @BINPATH@/components/@DLL_PREFIX@dbusservice@DLL_SUFFIX@
 #endif
 @BINPATH@/components/nsINIProcessor.manifest
 @BINPATH@/components/nsINIProcessor.js
--- a/configure.in
+++ b/configure.in
@@ -2069,16 +2069,17 @@ ia64*-hpux*)
 
 *-android*|*-linuxandroid*)
     AC_DEFINE(NO_PW_GECOS)
     no_x=yes
     if test -n "$gonkdir"; then
         _PLATFORM_DEFAULT_TOOLKIT=cairo-gonk
         MOZ_B2G_RIL=1
         MOZ_B2G_BT=1
+        MOZ_B2G_FM=1
     else
         _PLATFORM_DEFAULT_TOOLKIT=cairo-android
         MOZ_LINKER=1
     fi
     TARGET_NSPR_MDCPUCFG='\"md/_linux.cfg\"'
 
     MOZ_GFX_OPTIMIZE_MOBILE=1
     MOZ_OPTIMIZE_FLAGS="-Os -freorder-blocks -fno-reorder-functions"
@@ -7315,16 +7316,24 @@ MOZ_ARG_ENABLE_BOOL(b2g-ril,
     MOZ_B2G_RIL=1,
     MOZ_B2G_RIL= )
 if test -n "$MOZ_B2G_RIL"; then
     AC_DEFINE(MOZ_B2G_RIL)
 fi
 AC_SUBST(MOZ_B2G_RIL)
 
 dnl ========================================================
+dnl = Enable Radio FM for B2G (Gonk usually)
+dnl ========================================================
+if test -n "$MOZ_B2G_FM"; then
+    AC_DEFINE(MOZ_B2G_FM)
+fi
+AC_SUBST(MOZ_B2G_FM)
+
+dnl ========================================================
 dnl = Enable Bluetooth Interface for B2G (Gonk usually)
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(b2g-bt,
 [  --enable-b2g-bt      Set compile flags necessary for compiling Bluetooth API for B2G ],
     MOZ_B2G_BT=1,
     MOZ_B2G_BT= )
 if test -n "$MOZ_B2G_BT"; then
     AC_DEFINE(MOZ_B2G_BT)
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -755,16 +755,18 @@ GK_ATOM(onresize, "onresize")
 GK_ATOM(onscroll, "onscroll")
 GK_ATOM(onselect, "onselect")
 GK_ATOM(onsent, "onsent")
 GK_ATOM(onset, "onset")
 GK_ATOM(onshow, "onshow")
 GK_ATOM(onstatechange, "onstatechange")
 GK_ATOM(onstatuschanged, "onstatuschanged")
 GK_ATOM(onstkcommand, "onstkcommand")
+GK_ATOM(onantennastatechange, "onantennastatechange")
+GK_ATOM(onseekcomplete, "onseekcomplete")
 GK_ATOM(onstksessionend, "onstksessionend")
 GK_ATOM(onsubmit, "onsubmit")
 GK_ATOM(onsuccess, "onsuccess")
 GK_ATOM(ontext, "ontext")
 GK_ATOM(ontouchstart, "ontouchstart")
 GK_ATOM(ontouchend, "ontouchend")
 GK_ATOM(ontouchmove, "ontouchmove")
 GK_ATOM(ontouchenter, "ontouchenter")
--- a/dom/Makefile.in
+++ b/dom/Makefile.in
@@ -76,16 +76,20 @@ PARALLEL_DIRS += \
 ifdef MOZ_B2G_RIL
 PARALLEL_DIRS += \
   telephony \
   wifi \
   icc \
   $(NULL)
 endif
 
+ifdef MOZ_B2G_FM
+PARALLEL_DIRS += fm
+endif
+
 ifdef MOZ_PAY
 PARALLEL_DIRS += \
   payment \
   $(NULL)
 endif
 
 # bindings/test is here, because it needs to build after bindings/, and
 # we build subdirectories before ourselves.
--- a/dom/base/Makefile.in
+++ b/dom/base/Makefile.in
@@ -114,16 +114,20 @@ include $(topsrcdir)/dom/dom-config.mk
 ifdef MOZ_JSDEBUGGER
 DEFINES += -DMOZ_JSDEBUGGER
 endif
 
 ifdef MOZ_B2G_RIL
 DEFINES += -DMOZ_B2G_RIL
 endif
 
+ifdef MOZ_B2G_FM
+DEFINES += -DMOZ_B2G_FM
+endif
+
 include $(topsrcdir)/config/config.mk
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 
 include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES += \
 		-I$(topsrcdir)/js/xpconnect/src \
 		-I$(topsrcdir)/js/xpconnect/wrappers \
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -512,16 +512,20 @@ using mozilla::dom::indexedDB::IDBWrappe
 #include "TelephonyCall.h"
 #include "CallEvent.h"
 #include "nsIDOMVoicemail.h"
 #include "nsIDOMVoicemailEvent.h"
 #include "nsIDOMIccManager.h"
 #include "StkCommandEvent.h"
 #endif
 
+#ifdef MOZ_B2G_FM
+#include "FMRadio.h"
+#endif
+
 #ifdef MOZ_B2G_BT
 #include "BluetoothManager.h"
 #include "BluetoothAdapter.h"
 #include "BluetoothDevice.h"
 #include "BluetoothPropertyEvent.h"
 #endif
 
 #include "nsIDOMNavigatorSystemMessages.h"
@@ -1680,16 +1684,21 @@ static nsDOMClassInfoData sClassInfoData
   NS_DEFINE_CLASSINFO_DATA(MozVoicemailEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(MozIccManager, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(MozStkCommandEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 #endif
 
+#ifdef MOZ_B2G_FM
+  NS_DEFINE_CLASSINFO_DATA(FMRadio, nsEventTargetSH,
+                           EVENTTARGET_SCRIPTABLE_FLAGS)
+#endif
+
 #ifdef MOZ_B2G_BT
   NS_DEFINE_CLASSINFO_DATA(BluetoothManager, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(BluetoothAdapter, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)  
   NS_DEFINE_CLASSINFO_DATA(BluetoothDevice, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(BluetoothPropertyEvent, nsDOMGenericSH,
@@ -4490,16 +4499,23 @@ nsDOMClassInfo::Init()
 
   DOM_CLASSINFO_MAP_BEGIN(MozStkCommandEvent, nsIDOMMozStkCommandEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozStkCommandEvent)
     DOM_CLASSINFO_EVENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
 #endif
 
+#ifdef MOZ_B2G_FM
+  DOM_CLASSINFO_MAP_BEGIN(FMRadio, nsIFMRadio)
+    DOM_CLASSINFO_MAP_ENTRY(nsIFMRadio)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
+  DOM_CLASSINFO_MAP_END
+#endif
+
 #ifdef MOZ_B2G_BT
   DOM_CLASSINFO_MAP_BEGIN(BluetoothManager, nsIDOMBluetoothManager)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMBluetoothManager)
   DOM_CLASSINFO_MAP_END  
 
   DOM_CLASSINFO_MAP_BEGIN(BluetoothAdapter, nsIDOMBluetoothAdapter)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMBluetoothAdapter)
   DOM_CLASSINFO_MAP_END
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -516,16 +516,20 @@ DOMCI_CLASS(Telephony)
 DOMCI_CLASS(TelephonyCall)
 DOMCI_CLASS(CallEvent)
 DOMCI_CLASS(MozVoicemail)
 DOMCI_CLASS(MozVoicemailEvent)
 DOMCI_CLASS(MozIccManager)
 DOMCI_CLASS(MozStkCommandEvent)
 #endif
 
+#ifdef MOZ_B2G_FM
+DOMCI_CLASS(FMRadio)
+#endif
+
 #ifdef MOZ_B2G_BT
 DOMCI_CLASS(BluetoothManager)
 DOMCI_CLASS(BluetoothAdapter)
 DOMCI_CLASS(BluetoothDevice)
 DOMCI_CLASS(BluetoothPropertyEvent)
 #endif
 
 DOMCI_CLASS(CameraManager)
--- a/dom/dom-config.mk
+++ b/dom/dom-config.mk
@@ -39,14 +39,20 @@ ifdef MOZ_B2G_RIL
 DOM_SRCDIRS += \
   dom/system/gonk \
   dom/telephony \
   dom/wifi \
   dom/icc/src \
   $(NULL)
 endif
 
+ifdef MOZ_B2G_FM
+DOM_SRCDIRS += \
+  dom/fm \
+  $(NULL)
+endif
+
 ifdef MOZ_B2G_BT
 DOM_SRCDIRS += dom/bluetooth
 endif
 
 LOCAL_INCLUDES += $(DOM_SRCDIRS:%=-I$(topsrcdir)/%)
 DEFINES += -D_IMPL_NS_LAYOUT
new file mode 100644
--- /dev/null
+++ b/dom/fm/DOMFMRadio.manifest
@@ -0,0 +1,4 @@
+# DOMFMRadio.js
+component {901f8a83-03a6-4be9-bb8f-35387d3849da} DOMFMRadioChild.js
+contract @mozilla.org/domfmradio;1 {901f8a83-03a6-4be9-bb8f-35387d3849da}
+category JavaScript-navigator-property mozFMRadio @mozilla.org/domfmradio;1
new file mode 100644
--- /dev/null
+++ b/dom/fm/DOMFMRadioChild.js
@@ -0,0 +1,390 @@
+/* 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"
+let DEBUG = 0;
+if (DEBUG)
+  debug = function (s) { dump("-*- DOMFMRadioChild: " + s + "\n"); };
+else
+  debug = function (s) { };
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
+
+const DOMFMMANAGER_CONTRACTID = "@mozilla.org/domfmradio;1";
+const DOMFMMANAGER_CID = Components.ID("{901f8a83-03a6-4be9-bb8f-35387d3849da}");
+
+XPCOMUtils.defineLazyGetter(Services, "DOMRequest", function() {
+  return Cc["@mozilla.org/dom/dom-request-service;1"]
+           .getService(Ci.nsIDOMRequestService);
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+                                   "@mozilla.org/childprocessmessagemanager;1",
+                                   "nsISyncMessageSender");
+
+function DOMFMRadioChild() { }
+
+DOMFMRadioChild.prototype = {
+  __proto__: DOMRequestIpcHelper.prototype,
+
+  classID: DOMFMMANAGER_CID,
+  classInfo: XPCOMUtils.generateCI({
+               classID: DOMFMMANAGER_CID,
+               contractID: DOMFMMANAGER_CONTRACTID,
+               classDescription: "DOMFMRadio",
+               interfaces: [Ci.nsIDOMFMRadio],
+               flags: Ci.nsIClassInfo.DOM_OBJECT
+             }),
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMFMRadio,
+                                         Ci.nsIDOMGlobalPropertyInitializer]),
+
+  // nsIDOMGlobalPropertyInitializer implementation
+  init: function(aWindow) {
+    let principal = aWindow.document.nodePrincipal;
+    let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
+                   .getService(Ci.nsIScriptSecurityManager);
+
+    let perm = (principal == secMan.getSystemPrincipal()) ?
+                 Ci.nsIPermissionManager.ALLOW_ACTION :
+                 Services.perms.testExactPermission(principal.URI, "fmradio");
+    this._hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
+
+    if (!this._hasPrivileges) {
+      throw new Components.Exception("Denied", Cr.NS_ERROR_FAILURE);
+    }
+
+    const messages = ["DOMFMRadio:enable:Return:OK",
+                      "DOMFMRadio:enable:Return:NO",
+                      "DOMFMRadio:disable:Return:OK",
+                      "DOMFMRadio:disable:Return:NO",
+                      "DOMFMRadio:setFrequency:Return:OK",
+                      "DOMFMRadio:setFrequency:Return:NO",
+                      "DOMFMRadio:seekUp:Return:OK",
+                      "DOMFMRadio:seekUp:Return:NO",
+                      "DOMFMRadio:seekDown:Return:OK",
+                      "DOMFMRadio:seekDown:Return:NO",
+                      "DOMFMRadio:cancelSeek:Return:OK",
+                      "DOMFMRadio:cancelSeek:Return:NO",
+                      "DOMFMRadio:frequencyChange",
+                      "DOMFMRadio:powerStateChange",
+                      "DOMFMRadio:antennaChange"];
+    this.initHelper(aWindow, messages);
+  },
+
+  // Called from DOMRequestIpcHelper
+  uninit: function() {
+    this._onFrequencyChange = null;
+    this._onAntennaChange = null;
+    this._onDisabled = null;
+    this._onEnabled = null;
+  },
+
+  _createEvent: function(name) {
+    return new this._window.Event(name);
+  },
+
+  _sendMessageForRequest: function(name, data, request) {
+    let id = this.getRequestId(request);
+    cpmm.sendAsyncMessage(name, {
+      data: data,
+      rid: id,
+      mid: this._id
+    });
+  },
+
+  _fireFrequencyChangeEvent: function() {
+    let e = this._createEvent("frequencychange");
+    if (this._onFrequencyChange) {
+      this._onFrequencyChange.handleEvent(e);
+    }
+    this.dispatchEvent(e);
+  },
+
+  _firePowerStateChangeEvent: function() {
+    let _enabled = this.enabled;
+    debug("Current power state: " + _enabled);
+    if (_enabled) {
+      let e = this._createEvent("enabled");
+      if (this._onEnabled) {
+        this._onEnabled.handleEvent(e);
+      }
+      this.dispatchEvent(e);
+    } else {
+      let e = this._createEvent("disabled");
+      if (this._onDisabled) {
+        this._onDisabled.handleEvent(e);
+      }
+      this.dispatchEvent(e);
+    }
+  },
+
+  _fireAntennaAvailableChangeEvent: function() {
+    let e = this._createEvent("antennaavailablechange");
+    if (this._onAntennaChange) {
+      this._onAntennaChange.handleEvent(e);
+    }
+    this.dispatchEvent(e);
+  },
+
+  receiveMessage: function(aMessage) {
+    let msg = aMessage.json;
+    if (msg.mid && msg.mid != this._id) {
+      return;
+    }
+
+    let request;
+    switch (aMessage.name) {
+      case "DOMFMRadio:enable:Return:OK":
+        request = this.takeRequest(msg.rid);
+        if (!request) {
+          return;
+        }
+        Services.DOMRequest.fireSuccess(request, null);
+        break;
+      case "DOMFMRadio:enable:Return:NO":
+        request = this.takeRequest(msg.rid);
+        if (!request) {
+          return;
+        }
+        Services.DOMRequest.fireError(request, "Failed to turn on the FM radio");
+        break;
+      case "DOMFMRadio:disable:Return:OK":
+        request = this.takeRequest(msg.rid);
+        if (!request) {
+          return;
+        }
+        Services.DOMRequest.fireSuccess(request, null);
+        break;
+      case "DOMFMRadio:disable:Return:NO":
+        request = this.takeRequest(msg.rid);
+        if (!request) {
+          return;
+        }
+        Services.DOMRequest.fireError(request,
+                                      "Failed to turn off the FM radio");
+        break;
+      case "DOMFMRadio:setFrequency:Return:OK":
+        request = this.takeRequest(msg.rid);
+        if (!request) {
+          return;
+        }
+        Services.DOMRequest.fireSuccess(request, null);
+        break;
+      case "DOMFMRadio:setFrequency:Return:NO":
+        request = this.takeRequest(msg.rid);
+        if (!request) {
+          return;
+        }
+        Services.DOMRequest.fireError(request,
+                                      "Failed to set the FM radio frequency");
+        break;
+      case "DOMFMRadio:seekUp:Return:OK":
+        request = this.takeRequest(msg.rid);
+        if (!request) {
+          return;
+        }
+        Services.DOMRequest.fireSuccess(request, null);
+        break;
+      case "DOMFMRadio:seekUp:Return:NO":
+        request = this.takeRequest(msg.rid);
+        if (!request) {
+          return;
+        }
+        Services.DOMRequest.fireError(request, "FM radio seek-up failed");
+        break;
+      case "DOMFMRadio:seekDown:Return:OK":
+        request = this.takeRequest(msg.rid);
+        if (!request) {
+          return;
+        }
+        Services.DOMRequest.fireSuccess(request, null);
+        break;
+      case "DOMFMRadio:seekDown:Return:NO":
+        request = this.takeRequest(msg.rid);
+        if (!request) {
+          return;
+        }
+        Services.DOMRequest.fireError(request, "FM radio seek-down failed");
+        break;
+      case "DOMFMRadio:cancelSeek:Return:OK":
+        request = this.takeRequest(msg.rid);
+        if (!request) {
+          return;
+        }
+        Services.DOMRequest.fireSuccess(request, null);
+        break;
+      case "DOMFMRadio:cancelSeek:Return:NO":
+        request = this.takeRequest(msg.rid);
+        if (!request) {
+          return;
+        }
+        Services.DOMRequest.fireError(request, "Failed to cancel seek");
+        break;
+      case "DOMFMRadio:powerStateChange":
+        this._firePowerStateChangeEvent();
+        break;
+      case "DOMFMRadio:frequencyChange":
+        this._fireFrequencyChangeEvent();
+        break;
+      case "DOMFMRadio:antennaChange":
+        this._fireAntennaAvailableChangeEvent();
+        break;
+    }
+  },
+
+  _call: function(name, arg) {
+    var request = this.createRequest();
+    this._sendMessageForRequest("DOMFMRadio:" + name, arg, request);
+    return request;
+  },
+
+  // nsIDOMFMRadio
+  get enabled() {
+    return cpmm.sendSyncMessage("DOMFMRadio:getPowerState")[0];
+  },
+
+  get antennaAvailable() {
+    return cpmm.sendSyncMessage("DOMFMRadio:getAntennaState")[0];
+  },
+
+  get frequency() {
+    return cpmm.sendSyncMessage("DOMFMRadio:getFrequency")[0];
+  },
+
+  get frequencyUpperBound() {
+    let range = cpmm.sendSyncMessage("DOMFMRadio:getCurrentBand")[0];
+    return range.upper;
+  },
+
+  get frequencyLowerBound() {
+    let range = cpmm.sendSyncMessage("DOMFMRadio:getCurrentBand")[0];
+    return range.lower;
+  },
+
+  get channelWidth() {
+    let range = cpmm.sendSyncMessage("DOMFMRadio:getCurrentBand")[0];
+    return range.channelWidth;
+  },
+
+  set onantennaavailablechange(callback) {
+    this._onAntennaChange = callback;
+  },
+
+  set onenabled(callback) {
+    this._onEnabled = callback;
+  },
+
+  set ondisabled(callback) {
+    this._onDisabled = callback;
+  },
+
+  set onfrequencychange(callback) {
+    this._onFrequencyChange = callback;
+  },
+
+  disable: function nsIDOMFMRadio_disable() {
+    return this._call("disable", null);
+  },
+
+  enable: function nsIDOMFMRadio_enable(frequency) {
+    return this._call("enable", frequency);
+  },
+
+  setFrequency: function nsIDOMFMRadio_setFreq(frequency) {
+    return this._call("setFrequency", frequency);
+  },
+
+  seekDown: function nsIDOMFMRadio_seekDown() {
+    return this._call("seekDown", null);
+  },
+
+  seekUp: function nsIDOMFMRadio_seekUp() {
+    return this._call("seekUp", null);
+  },
+
+  cancelSeek: function nsIDOMFMRadio_cancelSeek() {
+    return this._call("cancelSeek", null);
+  },
+
+  // These are fake implementations, will be replaced by using
+  // nsJSDOMEventTargetHelper, see bug 731746
+  addEventListener: function(type, listener, useCapture) {
+    if (!this._eventListenersByType) {
+      this._eventListenersByType = {};
+    }
+
+    if (!listener) {
+      return;
+    }
+
+    var listeners = this._eventListenersByType[type];
+    if (!listeners) {
+      listeners = this._eventListenersByType[type] = [];
+    }
+
+    useCapture = !!useCapture;
+    for (let i = 0, len = listeners.length; i < len; i++) {
+      let l = listeners[i];
+      if (l && l.listener === listener && l.useCapture === useCapture) {
+        return;
+      }
+    }
+
+    listeners.push({
+      listener: listener,
+      useCapture: useCapture
+    });
+  },
+
+  removeEventListener: function(type, listener, useCapture) {
+    if (!this._eventListenersByType) {
+      return;
+    }
+
+    useCapture = !!useCapture;
+
+    var listeners = this._eventListenersByType[type];
+    if (listeners) {
+      for (let i = 0, len = listeners.length; i < len; i++) {
+        let l = listeners[i];
+        if (l && l.listener === listener && l.useCapture === useCapture) {
+          listeners.splice(i, 1);
+        }
+      }
+    }
+  },
+
+  dispatchEvent: function(evt) {
+    if (!this._eventListenersByType) {
+      return;
+    }
+
+    let type = evt.type;
+    var listeners = this._eventListenersByType[type];
+    if (listeners) {
+      for (let i = 0, len = listeners.length; i < len; i++) {
+        let listener = listeners[i].listener;
+
+        try {
+          if (typeof listener == "function") {
+            listener.call(this, evt);
+          } else if (listener && listener.handleEvent &&
+                     typeof listener.handleEvent == "function") {
+            listener.handleEvent(evt);
+          }
+        } catch (e) {
+          debug("Exception is caught: " + e);
+        }
+      }
+    }
+  }
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([DOMFMRadioChild]);
+
new file mode 100644
--- /dev/null
+++ b/dom/fm/DOMFMRadioParent.jsm
@@ -0,0 +1,456 @@
+/* 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"
+
+let DEBUG = 0;
+if (DEBUG)
+  debug = function(s) { dump("-*- DOMFMRadioParent component: " + s + "\n");  };
+else
+  debug = function(s) {};
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const MOZ_SETTINGS_CHANGED_OBSERVER_TOPIC  = "mozsettings-changed";
+const PROFILE_BEFORE_CHANGE_OBSERVER_TOPIC = "profile-before-change";
+
+const BAND_87500_108000_kHz = 1;
+const BAND_76000_108000_kHz = 2;
+const BAND_76000_90000_kHz  = 3;
+
+const FM_BANDS = { };
+FM_BANDS[BAND_76000_90000_kHz] = {
+  lower: 76000,
+  upper: 90000
+};
+
+FM_BANDS[BAND_87500_108000_kHz] = {
+  lower: 87500,
+  upper: 108000
+};
+
+FM_BANDS[BAND_76000_108000_kHz] = {
+  lower: 76000,
+  upper: 108000
+};
+
+const BAND_SETTING_KEY          = "fmRadio.band";
+const CHANNEL_WIDTH_SETTING_KEY = "fmRadio.channelWidth";
+
+// Hal types
+const CHANNEL_WIDTH_200KHZ = 200;
+const CHANNEL_WIDTH_100KHZ = 100;
+const CHANNEL_WIDTH_50KHZ  = 50;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
+                                   "@mozilla.org/parentprocessmessagemanager;1",
+                                   "nsIMessageListenerManager");
+
+XPCOMUtils.defineLazyGetter(this, "FMRadio", function() {
+  return Cc["@mozilla.org/fmradio;1"].getService(Ci.nsIFMRadio);
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
+                                   "@mozilla.org/settingsService;1",
+                                   "nsISettingsService");
+
+let EXPORTED_SYMBOLS = ["DOMFMRadioParent"];
+
+let DOMFMRadioParent = {
+  _initialized: false,
+
+  /* Indicates if the FM radio is currently enabled */
+  _isEnabled: false,
+
+  /* Indicates if the FM radio is currently being enabled */
+  _enabling: false,
+
+  /* Current frequency in KHz */
+  _currentFrequency: 0,
+
+  /* Current band setting */
+  _currentBand: BAND_87500_108000_kHz,
+
+  /* Current channel width */
+  _currentWidth: CHANNEL_WIDTH_100KHZ,
+
+  /* Indicates if the antenna is currently available */
+  _antennaAvailable: true,
+
+  _seeking: false,
+
+  _seekingCallback: null,
+
+  init: function() {
+    if (this._initialized === true) {
+      return;
+    }
+    this._initialized = true;
+
+    this._messages = ["DOMFMRadio:enable", "DOMFMRadio:disable",
+                      "DOMFMRadio:setFrequency", "DOMFMRadio:getCurrentBand",
+                      "DOMFMRadio:getPowerState", "DOMFMRadio:getFrequency",
+                      "DOMFMRadio:getAntennaState",
+                      "DOMFMRadio:seekUp", "DOMFMRadio:seekDown",
+                      "DOMFMRadio:cancelSeek"
+                     ];
+    this._messages.forEach(function(msgName) {
+      ppmm.addMessageListener(msgName, this);
+    }.bind(this));
+
+    Services.obs.addObserver(this, PROFILE_BEFORE_CHANGE_OBSERVER_TOPIC, false);
+    Services.obs.addObserver(this, MOZ_SETTINGS_CHANGED_OBSERVER_TOPIC, false);
+
+    this._updatePowerState();
+
+    // Get the band setting and channel width setting
+    let lock = gSettingsService.createLock();
+    lock.get(BAND_SETTING_KEY, this);
+    lock.get(CHANNEL_WIDTH_SETTING_KEY, this);
+
+    this._updateAntennaState();
+
+    let self = this;
+    FMRadio.onantennastatechange = function onantennachange() {
+      self._updateAntennaState();
+    };
+
+    debug("Initialized");
+  },
+
+  // nsISettingsServiceCallback
+  handle: function(aName, aResult) {
+    if (aName == BAND_SETTING_KEY) {
+      this._updateBand(aResult);
+    } else if (aName == CHANNEL_WIDTH_SETTING_KEY) {
+      this._updateChannelWidth(aResult);
+    }
+  },
+
+  handleError: function(aErrorMessage) {
+    this._updateBand(BAND_87500_108000_kHz);
+    this._updateChannelWidth(CHANNEL_WIDTH_100KHZ);
+  },
+
+  _updateAntennaState: function() {
+    let antennaState = FMRadio.isAntennaAvailable;
+
+    if (antennaState != this._antennaAvailable) {
+      this._antennaAvailable = antennaState;
+      ppmm.broadcastAsyncMessage("DOMFMRadio:antennaChange", { });
+    }
+  },
+
+  _updateBand: function(band) {
+      switch (parseInt(band)) {
+        case BAND_87500_108000_kHz:
+        case BAND_76000_108000_kHz:
+        case BAND_76000_90000_kHz:
+          this._currentBand = band;
+          break;
+      }
+  },
+
+  _updateChannelWidth: function(channelWidth) {
+    switch (parseInt(channelWidth)) {
+      case CHANNEL_WIDTH_50KHZ:
+      case CHANNEL_WIDTH_100KHZ:
+      case CHANNEL_WIDTH_200KHZ:
+        this._currentWidth = channelWidth;
+        break;
+    }
+  },
+
+  /**
+   * Update and cache the current frequency.
+   * Send frequency change message if the frequency is changed.
+   * The returned boolean value indicates if the frequency is changed.
+   */
+  _updateFrequency: function() {
+    let frequency = FMRadio.frequency;
+
+    if (frequency != this._currentFrequency) {
+      this._currentFrequency = frequency;
+      ppmm.broadcastAsyncMessage("DOMFMRadio:frequencyChange", { });
+      return true;
+    }
+
+    return false;
+  },
+
+  /**
+   * Update and cache the power state of the FM radio.
+   * Send message if the power state is changed.
+   */
+  _updatePowerState: function() {
+    let enabled = FMRadio.enabled;
+
+    if (this._isEnabled != enabled) {
+      this._isEnabled = enabled;
+      ppmm.broadcastAsyncMessage("DOMFMRadio:powerStateChange", { });
+
+      // If the FM radio is enabled, update the current frequency immediately,
+      if (enabled) {
+        this._updateFrequency();
+      }
+    }
+  },
+
+  _onSeekComplete: function(success) {
+    if (this._seeking) {
+      this._seeking = false;
+
+      if (this._seekingCallback) {
+        this._seekingCallback(success);
+        this._seekingCallback = null;
+      }
+    }
+  },
+
+  /**
+
+   * Seek the next channel with given direction.
+   * Only one seek action is allowed at once.
+   */
+  _seekStation: function(direction, aMessage) {
+    let msg = aMessage.json || { };
+    let messageName = aMessage.name + ":Return";
+
+    // If the FM radio is disabled, do not execute the seek action.
+    if(!this._isEnabled) {
+       this._sendMessage(messageName, false, null, msg);
+       return;
+    }
+
+    let self = this;
+    function callback(success) {
+      debug("Seek completed.");
+      if (!success) {
+        self._sendMessage(messageName, false, null, msg);
+      } else {
+        // Make sure the FM app will get the right frequency.
+        self._updateFrequency();
+        self._sendMessage(messageName, true, null, msg);
+      }
+    }
+
+    if (this._seeking) {
+      // Pass a boolean value to the callback which indicates that
+      // the seek action failed.
+      callback(false);
+      return;
+    }
+
+    this._seekingCallback = callback;
+    this._seeking = true;
+
+    let self = this;
+    FMRadio.seek(direction);
+    FMRadio.addEventListener("seekcomplete", function FM_onSeekComplete() {
+      FMRadio.removeEventListener("seekcomplete", FM_onSeekComplete);
+      self._onSeekComplete(true);
+    });
+  },
+
+  /**
+   * Round the frequency to match the range of frequency and the channel width.
+   * If the given frequency is out of range, return null.
+   * For example:
+   *  - lower: 87.5MHz, upper: 108MHz, channel width: 0.2MHz
+   *    87600 is rounded to 87700
+   *    87580 is rounded to 87500
+   *    109000 is not rounded, null will be returned
+   */
+  _roundFrequency: function(frequencyInKHz) {
+    if (frequencyInKHz < FM_BANDS[this._currentBand].lower ||
+        frequencyInKHz > FM_BANDS[this._currentBand].upper) {
+      return null;
+    }
+
+    let partToBeRounded = frequencyInKHz - FM_BANDS[this._currentBand].lower;
+    let roundedPart = Math.round(partToBeRounded / this._currentWidth) *
+                        this._currentWidth;
+    return FM_BANDS[this._currentBand].lower + roundedPart;
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case PROFILE_BEFORE_CHANGE_OBSERVER_TOPIC:
+        this._messages.forEach(function(msgName) {
+          ppmm.removeMessageListener(msgName, this);
+        }.bind(this));
+
+        Services.obs.removeObserver(this, PROFILE_BEFORE_CHANGE_OBSERVER_TOPIC);
+        Services.obs.removeObserver(this, MOZ_SETTINGS_CHANGED_OBSERVER_TOPIC);
+
+        ppmm = null;
+        this._messages = null;
+        break;
+      case MOZ_SETTINGS_CHANGED_OBSERVER_TOPIC:
+        let setting = JSON.parse(aData);
+        this.handleMozSettingsChanged(setting);
+        break;
+    }
+  },
+
+  _sendMessage: function(message, success, data, msg) {
+    msg.manager.sendAsyncMessage(message + (success ? ":OK" : ":NO"), {
+      data: data,
+      rid: msg.rid,
+      mid: msg.mid
+    });
+  },
+
+  handleMozSettingsChanged: function(settings) {
+    switch (settings.key) {
+      case BAND_SETTING_KEY:
+        this._updateBand(settings.value);
+        break;
+      case CHANNEL_WIDTH_SETTING_KEY:
+        this._updateChannelWidth(settings.value);
+        break;
+    }
+  },
+
+  _enableFMRadio: function(msg) {
+    let frequencyInKHz = this._roundFrequency(msg.data * 1000);
+
+    // If the FM radio is already enabled or it is currently being enabled
+    // or the given frequency is out of range, return false.
+    if (this._isEnabled || this._enabling || !frequencyInKHz) {
+      this._sendMessage("DOMFMRadio:enable:Return", false, null, msg);
+      return;
+    }
+
+    this._enabling = true;
+    let self = this;
+
+    FMRadio.addEventListener("enabled", function on_enabled() {
+      debug("FM Radio is enabled!");
+      self._enabling = false;
+
+      FMRadio.removeEventListener("enabled", on_enabled);
+
+      // To make sure the FM app will get right frequency after the FM
+      // radio is enabled, we have to set the frequency first.
+      FMRadio.setFrequency(frequencyInKHz);
+
+      // Update the current frequency without sending 'frequencyChange'
+      // msg, to make sure the FM app will get the right frequency when the
+      // 'enabled' event is fired.
+      self._currentFrequency = FMRadio.frequency;
+
+      self._updatePowerState();
+      self._sendMessage("DOMFMRadio:enable:Return", true, null, msg);
+
+      // The frequency is changed from 'null' to some number, so we should
+      // send the 'frequencyChange' message manually.
+      ppmm.broadcastAsyncMessage("DOMFMRadio:frequencyChange", { });
+    });
+
+    FMRadio.enable({
+      lowerLimit: FM_BANDS[self._currentBand].lower,
+      upperLimit: FM_BANDS[self._currentBand].upper,
+      channelWidth:  self._currentWidth   // 100KHz by default
+    });
+  },
+
+  _disableFMRadio: function(msg) {
+    // If the FM radio is already disabled, return false.
+    if (!this._isEnabled) {
+      this._sendMessage("DOMFMRadio:disable:Return", false, null, msg);
+      return;
+    }
+
+    let self = this;
+    FMRadio.addEventListener("disabled", function on_disabled() {
+      debug("FM Radio is disabled!");
+      FMRadio.removeEventListener("disabled", on_disabled);
+
+      self._updatePowerState();
+      self._sendMessage("DOMFMRadio:disable:Return", true, null, msg);
+
+      // If the FM Radio is currently seeking, no fail-to-seek or similar
+      // event will be fired, execute the seek callback manually.
+      self._onSeekComplete(false);
+    });
+
+    FMRadio.disable();
+  },
+
+  receiveMessage: function(aMessage) {
+    let msg = aMessage.json || {};
+    msg.manager = aMessage.target;
+
+    let ret = 0;
+    let self = this;
+    switch (aMessage.name) {
+      case "DOMFMRadio:enable":
+        self._enableFMRadio(msg);
+        break;
+      case "DOMFMRadio:disable":
+        self._disableFMRadio(msg);
+        break;
+      case "DOMFMRadio:setFrequency":
+        let frequencyInKHz = self._roundFrequency(msg.data * 1000);
+
+        // If the FM radio is disabled or the given frequency is out of range,
+        // skip to set frequency and send back the False message immediately.
+        if (!self._isEnabled || !frequencyInKHz) {
+          self._sendMessage("DOMFMRadio:setFrequency:Return", false, null, msg);
+        } else {
+          FMRadio.setFrequency(frequencyInKHz);
+          self._sendMessage("DOMFMRadio:setFrequency:Return", true, null, msg);
+          this._updateFrequency();
+        }
+        break;
+      case "DOMFMRadio:getCurrentBand":
+        // this message is sync
+        return {
+          lower: FM_BANDS[self._currentBand].lower / 1000,   // in MHz
+          upper: FM_BANDS[self._currentBand].upper / 1000,   // in MHz
+          channelWidth: self._currentWidth / 1000            // in MHz
+        };
+      case "DOMFMRadio:getPowerState":
+        // this message is sync
+        return self._isEnabled;
+      case "DOMFMRadio:getFrequency":
+        // this message is sync
+        return self._isEnabled ? this._currentFrequency / 1000 : null; // in MHz
+      case "DOMFMRadio:getAntennaState":
+        // this message is sync
+        return self._antennaAvailable;
+      case "DOMFMRadio:seekUp":
+        self._seekStation(Ci.nsIFMRadio.SEEK_DIRECTION_UP, aMessage);
+        break;
+      case "DOMFMRadio:seekDown":
+        self._seekStation(Ci.nsIFMRadio.SEEK_DIRECTION_DOWN, aMessage);
+        break;
+      case "DOMFMRadio:cancelSeek":
+        // If the FM radio is disabled, or the FM radio is not currently
+        // seeking, do not execute the cancel seek action.
+        if (!self._isEnabled || !self._seeking) {
+          self._sendMessage("DOMFMRadio:cancelSeek:Return", false, null, msg);
+        } else {
+          FMRadio.cancelSeek();
+          // No fail-to-seek or similar event will be fired from the hal part,
+          // so execute the seek callback here manually.
+          this._onSeekComplete(false);
+          // The FM radio will stop at one frequency without any event, so we need to
+          // update the current frequency, make sure the FM app will get the right frequency.
+          this._updateFrequency();
+          self._sendMessage("DOMFMRadio:cancelSeek:Return", true, null, msg);
+        }
+        break;
+    }
+  }
+};
+
+DOMFMRadioParent.init();
+
new file mode 100644
--- /dev/null
+++ b/dom/fm/FMRadio.cpp
@@ -0,0 +1,241 @@
+/* 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 "mozilla/Hal.h"
+#include "mozilla/HalTypes.h"
+#include "mozilla/Preferences.h"
+#include "nsIAudioManager.h"
+#include "FMRadio.h"
+#include "nsDOMEvent.h"
+#include "nsDOMClassInfo.h"
+#include "nsFMRadioSettings.h"
+#include "nsCOMPtr.h"
+
+#undef LOG
+#if defined(MOZ_WIDGET_GONK)
+#include <android/log.h>
+#define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "FMRadio" , ## args)
+#else
+#define LOG(args...)
+#endif
+
+// The pref indicates if the device has an internal antenna.
+// If the pref is true, the antanna will be always available.
+#define DOM_FM_ANTENNA_INTERNAL_PREF "dom.fm.antenna.internal"
+
+#define RADIO_SEEK_COMPLETE_EVENT_NAME   NS_LITERAL_STRING("seekcomplete")
+#define RADIO_DIABLED_EVENT_NAME         NS_LITERAL_STRING("disabled")
+#define RADIO_ENABLED_EVENT_NAME         NS_LITERAL_STRING("enabled")
+#define ANTENNA_STATE_CHANGED_EVENT_NAME NS_LITERAL_STRING("antennastatechange")
+
+#define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1"
+
+using namespace mozilla::dom::fm;
+using namespace mozilla::hal;
+using mozilla::Preferences;
+
+FMRadio::FMRadio()
+  : mHeadphoneState(SWITCH_STATE_OFF)
+  , mHasInternalAntenna(false)
+{
+  LOG("FMRadio is initialized.");
+
+  mHasInternalAntenna = Preferences::GetBool(DOM_FM_ANTENNA_INTERNAL_PREF,
+                                             /* default = */ false);
+  if (mHasInternalAntenna) {
+    LOG("We have an internal antenna.");
+  } else {
+    RegisterSwitchObserver(SWITCH_HEADPHONES, this);
+    mHeadphoneState = GetCurrentSwitchState(SWITCH_HEADPHONES);
+  }
+
+  RegisterFMRadioObserver(this);
+}
+
+FMRadio::~FMRadio()
+{
+  UnregisterFMRadioObserver(this);
+  if (!mHasInternalAntenna) {
+    UnregisterSwitchObserver(SWITCH_HEADPHONES, this);
+  }
+}
+
+DOMCI_DATA(FMRadio, FMRadio)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(FMRadio)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FMRadio,
+                                                  nsDOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(FMRadio,
+                                               nsDOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FMRadio,
+                                                nsDOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FMRadio)
+  NS_INTERFACE_MAP_ENTRY(nsIFMRadio)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(FMRadio)
+NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
+
+NS_IMPL_EVENT_HANDLER(FMRadio, seekcomplete)
+NS_IMPL_EVENT_HANDLER(FMRadio, disabled)
+NS_IMPL_EVENT_HANDLER(FMRadio, enabled)
+NS_IMPL_EVENT_HANDLER(FMRadio, antennastatechange)
+
+NS_IMPL_ADDREF_INHERITED(FMRadio, nsDOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(FMRadio, nsDOMEventTargetHelper)
+
+/* readonly attribute boolean isAntennaAvailable; */
+NS_IMETHODIMP FMRadio::GetIsAntennaAvailable(bool *aIsAvailable)
+{
+  if (mHasInternalAntenna) {
+    *aIsAvailable = true;
+  } else {
+    *aIsAvailable = mHeadphoneState == SWITCH_STATE_ON;
+  }
+  return NS_OK;
+}
+
+/* readonly attribute long frequency; */
+NS_IMETHODIMP FMRadio::GetFrequency(int32_t *aFrequency)
+{
+  *aFrequency = GetFMRadioFrequency();
+  return NS_OK;
+}
+
+/* readonly attribute blean enabled; */
+NS_IMETHODIMP FMRadio::GetEnabled(bool *aEnabled)
+{
+  *aEnabled = IsFMRadioOn();
+  return NS_OK;
+}
+
+/* void enable (in nsIFMRadioSettings settings); */
+NS_IMETHODIMP FMRadio::Enable(nsIFMRadioSettings *settings)
+{
+  hal::FMRadioSettings info;
+
+  int32_t upperLimit, lowerLimit, channelWidth;
+
+  settings->GetUpperLimit(&upperLimit);
+  settings->GetLowerLimit(&lowerLimit);
+  settings->GetChannelWidth(&channelWidth);
+
+  info.upperLimit() = upperLimit;
+  info.lowerLimit() = lowerLimit;
+  info.spaceType() = channelWidth;
+
+  EnableFMRadio(info);
+
+  nsCOMPtr<nsIAudioManager> audioManager =
+    do_GetService(NS_AUDIOMANAGER_CONTRACTID);
+  NS_ENSURE_TRUE(audioManager, NS_OK);
+
+  audioManager->SetFmRadioAudioEnabled(true);
+
+  return NS_OK;
+}
+
+/* void disableRadio (); */
+NS_IMETHODIMP FMRadio::Disable()
+{
+  nsCOMPtr<nsIAudioManager> audioManager =
+    do_GetService(NS_AUDIOMANAGER_CONTRACTID);
+  NS_ENSURE_TRUE(audioManager, NS_OK);
+
+  audioManager->SetFmRadioAudioEnabled(false);
+
+  DisableFMRadio();
+
+  return NS_OK;
+}
+
+/* void cancelSeek */
+NS_IMETHODIMP FMRadio::CancelSeek()
+{
+  CancelFMRadioSeek();
+  return NS_OK;
+}
+
+/* void seek (in long direction); */
+NS_IMETHODIMP FMRadio::Seek(int32_t direction)
+{
+  if (direction == (int)FM_RADIO_SEEK_DIRECTION_UP) {
+    FMRadioSeek(FM_RADIO_SEEK_DIRECTION_UP);
+  } else {
+    FMRadioSeek(FM_RADIO_SEEK_DIRECTION_DOWN);
+  }
+  return NS_OK;
+}
+
+/* nsIFMRadioSettings getSettings (); */
+NS_IMETHODIMP FMRadio::GetSettings(nsIFMRadioSettings * *_retval)
+{
+  hal::FMRadioSettings settings;
+  GetFMRadioSettings(&settings);
+
+  nsCOMPtr<nsIFMRadioSettings> radioSettings(new nsFMRadioSettings(
+                                                   settings.upperLimit(),
+                                                   settings.lowerLimit(),
+                                                   settings.spaceType()));
+  radioSettings.forget(_retval);
+
+  return NS_OK;
+}
+
+/* void setFrequency (in long frequency); */
+NS_IMETHODIMP FMRadio::SetFrequency(int32_t frequency)
+{
+  SetFMRadioFrequency(frequency);
+  return NS_OK;
+}
+
+void FMRadio::Notify(const SwitchEvent& aEvent)
+{
+  if (mHeadphoneState != aEvent.status()) {
+    LOG("Antenna state is changed!");
+    mHeadphoneState = aEvent.status();
+    DispatchTrustedEventToSelf(ANTENNA_STATE_CHANGED_EVENT_NAME);
+  }
+}
+
+void FMRadio::Notify(const FMRadioOperationInformation& info)
+{
+  switch (info.operation())
+  {
+    case FM_RADIO_OPERATION_ENABLE:
+      DispatchTrustedEventToSelf(RADIO_ENABLED_EVENT_NAME);
+      break;
+    case FM_RADIO_OPERATION_DISABLE:
+      DispatchTrustedEventToSelf(RADIO_DIABLED_EVENT_NAME);
+      break;
+    case FM_RADIO_OPERATION_SEEK:
+      DispatchTrustedEventToSelf(RADIO_SEEK_COMPLETE_EVENT_NAME);
+      break;
+  }
+}
+
+nsresult
+FMRadio::DispatchTrustedEventToSelf(const nsAString& aEventName)
+{
+  nsRefPtr<nsDOMEvent> event = new nsDOMEvent(nullptr, nullptr);
+  nsresult rv = event->InitEvent(aEventName,
+                                 /* bubbles = */ false,
+                                 /* cancelable = */ false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = event->SetTrusted(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool dummy;
+  rv = DispatchEvent(event, &dummy);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/fm/FMRadio.h
@@ -0,0 +1,56 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* 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 mozilla_dom_fm_radio_h__
+#define mozilla_dom_fm_radio_h__
+
+#include "nsCOMPtr.h"
+#include "mozilla/HalTypes.h"
+#include "nsDOMEventTargetHelper.h"
+#include "nsIFMRadio.h"
+
+#define NS_FMRADIO_CONTRACTID "@mozilla.org/fmradio;1"
+// 9cb91834-78a9-4029-b644-7806173c5e2d
+#define NS_FMRADIO_CID {0x9cb91834, 0x78a9, 0x4029, \
+      {0xb6, 0x44, 0x78, 0x06, 0x17, 0x3c, 0x5e, 0x2d}}
+
+namespace mozilla {
+namespace dom {
+namespace fm {
+
+/* Header file */
+class FMRadio : public nsDOMEventTargetHelper
+              , public nsIFMRadio
+              , public hal::FMRadioObserver
+              , public hal::SwitchObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIFMRADIO
+
+  NS_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper::)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+                                                   FMRadio,
+                                                   nsDOMEventTargetHelper)
+  FMRadio();
+  virtual void Notify(const hal::FMRadioOperationInformation& info);
+  virtual void Notify(const hal::SwitchEvent& aEvent);
+
+private:
+  ~FMRadio();
+  bool mHasInternalAntenna;
+  hal::SwitchState mHeadphoneState;
+  /**
+   * Dispatch a trusted non-cancellable and no-bubbling event to itself
+   */
+  nsresult DispatchTrustedEventToSelf(const nsAString& aEventName);
+};
+
+} // namespace fm
+} // namespace dom
+} // namespace mozilla
+#endif
+
new file mode 100644
--- /dev/null
+++ b/dom/fm/Makefile.in
@@ -0,0 +1,45 @@
+# 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/.
+
+DEPTH            = ../..
+topsrcdir        = @top_srcdir@
+srcdir           = @srcdir@
+VPATH            = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE           = dom
+LIBRARY_NAME     = domfm_s
+XPIDL_MODULE     = dom_fm
+LIBXUL_LIBRARY   = 1
+FORCE_STATIC_LIB = 1
+
+include $(topsrcdir)/dom/dom-config.mk
+
+CPPSRCS += \
+  FMRadio.cpp \
+  nsFMRadioSettings.cpp \
+  $(NULL)
+
+XPIDLSRCS = \
+  nsIDOMFMRadio.idl \
+  nsIFMRadio.idl \
+  $(NULL)
+
+EXTRA_COMPONENTS =      \
+  DOMFMRadioChild.js       \
+  DOMFMRadio.manifest \
+  $(NULL)
+
+EXTRA_JS_MODULES =   \
+  DOMFMRadioParent.jsm \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
+include $(topsrcdir)/ipc/chromium/chromium-config.mk
+
+XPIDL_FLAGS += \
+  -I$(topsrcdir)/dom/interfaces/events \
+  $(NULL)
+
new file mode 100644
--- /dev/null
+++ b/dom/fm/nsFMRadioSettings.cpp
@@ -0,0 +1,61 @@
+/* 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 "nsFMRadioSettings.h"
+
+NS_IMPL_ISUPPORTS1(nsFMRadioSettings, nsIFMRadioSettings)
+
+nsFMRadioSettings::nsFMRadioSettings(int32_t aUpperLimit,
+                                     int32_t aLowerLimit,
+                                     int32_t aChannelWidth)
+{
+  mUpperLimit = aUpperLimit;
+  mLowerLimit = aLowerLimit;
+  mChannelWidth  = aChannelWidth;
+}
+
+nsFMRadioSettings::~nsFMRadioSettings()
+{
+
+}
+
+/* attribute long upperLimit; */
+NS_IMETHODIMP nsFMRadioSettings::GetUpperLimit(int32_t *aUpperLimit)
+{
+  *aUpperLimit = mUpperLimit;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsFMRadioSettings::SetUpperLimit(int32_t aUpperLimit)
+{
+  mUpperLimit = aUpperLimit;
+  return NS_OK;
+}
+
+/* attribute long lowerLimit; */
+NS_IMETHODIMP nsFMRadioSettings::GetLowerLimit(int32_t *aLowerLimit)
+{
+  *aLowerLimit = mLowerLimit;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsFMRadioSettings::SetLowerLimit(int32_t aLowerLimit)
+{
+  mLowerLimit = aLowerLimit;
+  return NS_OK;
+}
+
+/* attribute long spaceType; */
+NS_IMETHODIMP nsFMRadioSettings::GetChannelWidth(int32_t *aChannelWidth)
+{
+  *aChannelWidth = mChannelWidth;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsFMRadioSettings::SetChannelWidth(int32_t aChannelWidth)
+{
+  mChannelWidth = aChannelWidth;
+  return NS_OK;
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/fm/nsFMRadioSettings.h
@@ -0,0 +1,26 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* 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 mozilla_dom_fm_radio_settings_h__
+#define mozilla_dom_fm_radio_settings_h__
+
+#include "nsIFMRadio.h"
+
+class nsFMRadioSettings : public nsIFMRadioSettings
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIFMRADIOSETTINGS
+
+  nsFMRadioSettings(int32_t aUpperLimit, int32_t aLowerLimit, int32_t aChannelWidth);
+private:
+  ~nsFMRadioSettings();
+  int32_t mUpperLimit;
+  int32_t mLowerLimit;
+  int32_t mChannelWidth;
+};
+#endif
+
new file mode 100644
--- /dev/null
+++ b/dom/fm/nsIDOMFMRadio.idl
@@ -0,0 +1,122 @@
+/* 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 "nsISupports.idl"
+#include "nsIDOMDOMRequest.idl"
+
+[scriptable, uuid(1d0443f3-ac30-4f9e-a070-002bb20ce1e6)]
+interface nsIDOMFMRadio : nsISupports {
+    /* Indicates if the FM radio is enabled. */
+    readonly attribute boolean enabled;
+
+    /* Indicates if the antenna is plugged and available. */
+    readonly attribute boolean antennaAvailable;
+
+    /**
+     * Current frequency in MHz.
+     * The value will be null if the FM radio is disabled.
+     */
+    readonly attribute jsval frequency;
+
+    /* The upper bound of frequency in MHz. */
+    readonly attribute double frequencyUpperBound;
+
+    /* The lower bound of frequency in MHz. */
+    readonly attribute double frequencyLowerBound;
+
+    /**
+     * The channel width of the ranges of frequency, in MHz.
+     * Usually, the value is one of:
+     *  - 0.05 MHz
+     *  - 0.1  MHz
+     *  - 0.2  MHz
+     */
+    readonly attribute double channelWidth;
+
+    /* Fired when the FM radio is enabled. */
+    attribute nsIDOMEventListener onenabled;
+
+    /* Fired when the FM radio is disabled. */
+    attribute nsIDOMEventListener ondisabled;
+
+    /**
+     * Fired when the antenna becomes available or unavailable, i.e., fired when
+     * the antennaAvailable attribute changes.
+     */
+    attribute nsIDOMEventListener onantennaavailablechange;
+
+    /* Fired when the FM radio's frequency is changed. */
+    attribute nsIDOMEventListener onfrequencychange;
+
+    /**
+     * Power the FM radio off.
+     * The disabled event will be fired if this request completes successfully.
+     */
+    nsIDOMDOMRequest disable();
+
+    /**
+     * Power the FM radio on, and tune the radio to the given frequency in MHz.
+     * This will fail if the given frequency is out of range.
+     * The enabled event and frequencychange event will be fired if this request
+     * completes successfully.
+     */
+    nsIDOMDOMRequest enable(in double frequency);
+
+    /**
+     * Tune the FM radio to the given frequency.
+     * This will fail if the given frequency is out of range.
+     *
+     * Note that the FM radio may not tuned to the exact frequency given. To get
+     * the frequency the radio is actually tuned to, wait for the request to fire
+     * onsucess (or wait for the frequencychange event to fire), and then read the
+     * frequency attribute.
+     */
+    nsIDOMDOMRequest setFrequency(in double frequency);
+
+    /**
+     * Tell the FM radio to seek up to the next channel. If the frequency is
+     * successfully changed, the frequencychange event will be triggered.
+     *
+     * Only one seek is allowed at once:
+     * If the radio is seeking when the seekUp is called, onerror will be fired.
+     */
+    nsIDOMDOMRequest seekUp();
+
+    /**
+     * Tell the FM radio to seek down to the next channel. If the frequency is
+     * successfully changed, the frequencychange event will be triggered.
+     *
+     * Only one seek is allowed at once:
+     * If the radio is seeking when the seekDown is called, onerror will be fired.
+     */
+    nsIDOMDOMRequest seekDown();
+
+    /**
+     * Cancel the seek action.
+     * If the radio is not currently seeking up or down, onerror will be fired.
+     */
+    nsIDOMDOMRequest cancelSeek();
+
+
+    /**
+     * These functions related to EventTarget are temporary hacks:
+     *   - addEventListener
+     *   - removeEventListener
+     *   - handleEvent
+     *
+     * These will be removed by inheriting from nsIJSDOMEventTarget,
+     * see bug 731746.
+     */
+    [optional_argc] void addEventListener(in DOMString type,
+                                          in nsIDOMEventListener listener,
+                                          [optional] in boolean useCapture,
+                                          [optional] in boolean wantsUntrusted);
+
+    void  removeEventListener(in DOMString type,
+                              in nsIDOMEventListener listener,
+                              [optional] in boolean useCapture);
+
+    boolean dispatchEvent(in nsIDOMEvent evt) raises(DOMException);
+};
+
new file mode 100644
--- /dev/null
+++ b/dom/fm/nsIFMRadio.idl
@@ -0,0 +1,102 @@
+/* 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 "nsIDOMEventTarget.idl"
+
+[scriptable, uuid(c142387a-5488-454b-8b5a-91f0dbee833b)]
+interface nsIFMRadioSettings : nsISupports
+{
+    /* Upper limit in KHz */
+    attribute long upperLimit;
+    /* Lower limit in KHz */
+    attribute long lowerLimit;
+    /* Channel width in KHz */
+    attribute long channelWidth;
+};
+
+/**
+ * This is an interface to expose the FM radio hardware related functions;
+ * it's kind of the FM radio hardware wrapper interface.
+ *
+ * Because the WebFM API (navigator.mozFMRadio) is implemented as a JS component,
+ * it can't access our C++ hardware interface directly; instead it must go
+ * through this interface.
+ * Do not confuse this interface with the WebFM DOM interface (nsIDOMFMRadio).
+ *
+ * If the WebFM API is re-written in c++ some day, this interface will be useless.
+ */
+[scriptable, builtinclass, uuid(26288adc-d2c1-4fbc-86b5-ecd8173fbf90)]
+interface nsIFMRadio : nsIDOMEventTarget {
+    const long SEEK_DIRECTION_UP   = 0;
+    const long SEEK_DIRECTION_DOWN = 1;
+
+    /**
+     * Indicates if the FM radio hardware is enabled.
+     */
+    readonly attribute boolean enabled;
+
+    /**
+     * Current frequency in KHz
+     */
+    readonly attribute long frequency;
+
+    /**
+     * Indicates if the antenna is plugged in and available.
+     */
+    readonly attribute boolean isAntennaAvailable;
+
+    /**
+     * Enable the FM radio hardware with the given settings.
+     */
+    void enable(in nsIFMRadioSettings settings);
+
+    /**
+     * Disable the FM radio hardware.
+     */
+    void disable();
+
+    /**
+     * Seek the next available channel (up or down).
+     *
+     * @param direction
+     *   The value should be one of SEEK_DIRECTION_DOWN and SEEK_DIRECTION_UP
+     */
+    void seek(in long direction);
+
+    /**
+     * Cancel the seek action.
+     */
+    void cancelSeek();
+
+    /**
+     * Get the current settings.
+     */
+    nsIFMRadioSettings getSettings();
+
+    /**
+     * Set the frequency in KHz
+     */
+    void setFrequency(in long frequency);
+
+    /**
+     * Fired when the antenna state is changed.
+     */
+    [implicit_jscontext] attribute jsval onantennastatechange;
+
+    /**
+     * Fired when a seek action completes.
+     */
+    [implicit_jscontext] attribute jsval onseekcomplete;
+
+    /**
+     * Fired when the FM radio hardware is enabled.
+     */
+    [implicit_jscontext] attribute jsval onenabled;
+
+    /**
+     * Fired when the FM radio hardware is disabled.
+     */
+    [implicit_jscontext] attribute jsval ondisabled;
+};
+
--- a/layout/build/Makefile.in
+++ b/layout/build/Makefile.in
@@ -113,16 +113,22 @@ SHARED_LIBRARY_LIBS += \
 	$(DEPTH)/dom/plugins/base/android/$(LIB_PREFIX)gkpluginandroid_s.$(LIB_SUFFIX) \
 	$(NULL)
 LOCAL_INCLUDES	+= \
 	-I$(topsrcdir)/dom/system/android \
 	-I$(topsrcdir)/dom/system \
 	$(NULL)
 endif
 
+ifdef MOZ_B2G_FM #{
+SHARED_LIBRARY_LIBS	+= \
+  $(DEPTH)/dom/fm/$(LIB_PREFIX)domfm_s.$(LIB_SUFFIX) \
+  $(NULL)
+endif #}
+
 ifdef MOZ_B2G_BT #{
 SHARED_LIBRARY_LIBS += $(DEPTH)/dom/bluetooth/$(LIB_PREFIX)dombluetooth_s.$(LIB_SUFFIX)
 endif #}
 
 SHARED_LIBRARY_LIBS	+= $(DEPTH)/dom/camera/$(LIB_PREFIX)domcamera_s.$(LIB_SUFFIX)
 
 ifdef MOZ_B2G_RIL #{
 SHARED_LIBRARY_LIBS	+= \
@@ -270,15 +276,19 @@ LOCAL_INCLUDES	+= -I$(srcdir)/../base \
 		   -I$(topsrcdir)/content/svg/content/src \
 		   -I$(topsrcdir)/extensions/cookie \
 		   $(NULL)
 
 ifdef MOZ_B2G_RIL #{
 LOCAL_INCLUDES	+= -I$(topsrcdir)/dom/system/gonk
 endif #}
 
+ifdef MOZ_B2G_FM #{
+LOCAL_INCLUDES	+= -I$(topsrcdir)/dom/fm
+endif #}
+
 ifdef MOZ_B2G_BT #{
 LOCAL_INCLUDES	+= -I$(topsrcdir)/dom/bluetooth
 endif #}
 
 LOCAL_INCLUDES	+= -I$(topsrcdir)/dom/camera
 
 DEFINES += -D_IMPL_NS_LAYOUT
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -119,16 +119,22 @@ using mozilla::dom::bluetooth::Bluetooth
 #endif
 
 #ifdef MOZ_WIDGET_GONK
 #include "AudioManager.h"
 using mozilla::dom::gonk::AudioManager;
 #include "nsVolumeService.h"
 using mozilla::system::nsVolumeService;
 #endif
+
+#ifdef MOZ_B2G_FM
+#include "FMRadio.h"
+using mozilla::dom::fm::FMRadio;
+#endif
+
 #include "nsDOMMutationObserver.h"
 
 // Editor stuff
 #include "nsEditorCID.h"
 #include "nsEditor.h"
 #include "nsPlaintextEditor.h"
 #include "nsEditorController.h" //CID
 
@@ -286,16 +292,21 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR
                                          BluetoothService::FactoryCreate)
 #endif
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDOMMutationObserver)
 
 #ifdef MOZ_WIDGET_GONK
 NS_GENERIC_FACTORY_CONSTRUCTOR(AudioManager)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsVolumeService)
 #endif
+
+#ifdef MOZ_B2G_FM
+NS_GENERIC_FACTORY_CONSTRUCTOR(FMRadio)
+#endif
+
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceSensors)
 
 #ifndef MOZ_WIDGET_GONK
 #if defined(ANDROID) || defined(MOZ_PLATFORM_MAEMO)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsHapticFeedback)
 #endif
 #endif
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(ThirdPartyUtil, Init)
@@ -766,16 +777,21 @@ NS_DEFINE_NAMED_CID(SYSTEMWORKERMANAGER_
 #endif
 #ifdef MOZ_B2G_BT
 NS_DEFINE_NAMED_CID(BLUETOOTHSERVICE_CID);
 #endif
 #ifdef MOZ_WIDGET_GONK
 NS_DEFINE_NAMED_CID(NS_AUDIOMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_VOLUMESERVICE_CID);
 #endif
+
+#ifdef MOZ_B2G_FM
+NS_DEFINE_NAMED_CID(NS_FMRADIO_CID);
+#endif
+
 #ifdef ENABLE_EDITOR_API_LOG
 NS_DEFINE_NAMED_CID(NS_HTMLEDITOR_CID);
 #else
 NS_DEFINE_NAMED_CID(NS_HTMLEDITOR_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_EDITORCONTROLLER_CID);
 NS_DEFINE_NAMED_CID(NS_EDITINGCONTROLLER_CID);
 NS_DEFINE_NAMED_CID(NS_EDITORCOMMANDTABLE_CID);
@@ -1042,16 +1058,19 @@ static const mozilla::Module::CIDEntry k
 #endif
 #ifdef MOZ_B2G_BT
   { &kBLUETOOTHSERVICE_CID, true, NULL, BluetoothServiceConstructor },
 #endif
 #ifdef MOZ_WIDGET_GONK
   { &kNS_AUDIOMANAGER_CID, true, NULL, AudioManagerConstructor },
   { &kNS_VOLUMESERVICE_CID, true, NULL, nsVolumeServiceConstructor },
 #endif
+#ifdef MOZ_B2G_FM
+  { &kNS_FMRADIO_CID, true, NULL, FMRadioConstructor },
+#endif
 #ifdef ENABLE_EDITOR_API_LOG
   { &kNS_HTMLEDITOR_CID, false, NULL, nsHTMLEditorLogConstructor },
 #else
   { &kNS_HTMLEDITOR_CID, false, NULL, nsHTMLEditorConstructor },
 #endif
   { &kNS_EDITORCONTROLLER_CID, false, NULL, nsEditorControllerConstructor },
   { &kNS_EDITINGCONTROLLER_CID, false, NULL, nsEditingControllerConstructor },
   { &kNS_EDITORCOMMANDTABLE_CID, false, NULL, nsEditorCommandTableConstructor },
@@ -1183,16 +1202,19 @@ static const mozilla::Module::ContractID
 #endif
 #ifdef MOZ_B2G_BT
   { BLUETOOTHSERVICE_CONTRACTID, &kBLUETOOTHSERVICE_CID },
 #endif
 #ifdef MOZ_WIDGET_GONK
   { NS_AUDIOMANAGER_CONTRACTID, &kNS_AUDIOMANAGER_CID },
   { NS_VOLUMESERVICE_CONTRACTID, &kNS_VOLUMESERVICE_CID },
 #endif
+#ifdef MOZ_B2G_FM
+  { NS_FMRADIO_CONTRACTID, &kNS_FMRADIO_CID },
+#endif
 #ifdef ENABLE_EDITOR_API_LOG
   { "@mozilla.org/editor/htmleditor;1", &kNS_HTMLEDITOR_CID },
 #else
   { "@mozilla.org/editor/htmleditor;1", &kNS_HTMLEDITOR_CID },
 #endif
   { "@mozilla.org/editor/editorcontroller;1", &kNS_EDITORCONTROLLER_CID },
   { "@mozilla.org/editor/editingcontroller;1", &kNS_EDITINGCONTROLLER_CID },
   { "@mozilla.org/textservices/textservicesdocument;1", &kNS_TEXTSERVICESDOCUMENT_CID },
--- a/toolkit/toolkit-makefiles.sh
+++ b/toolkit/toolkit-makefiles.sh
@@ -1214,16 +1214,22 @@ if [ "$MOZ_B2G_RIL" ]; then
 fi
 
 if [ "$MOZ_PAY" ]; then
   add_makefiles "
     dom/payment/Makefile
   "
 fi
 
+if [ "$MOZ_B2G_FM" ]; then
+  add_makefiles "
+    dom/fm/Makefile
+  "
+fi
+
 if [ "$MOZ_CRASHREPORTER" ]; then
   add_makefiles "
     toolkit/crashreporter/Makefile
   "
   MAKEFILES_crashreporter_shared="
     toolkit/crashreporter/google-breakpad/src/client/Makefile
     toolkit/crashreporter/google-breakpad/src/common/Makefile
     toolkit/crashreporter/google-breakpad/src/common/dwarf/Makefile