Bug 779500 - WebFM, r=jlebar,glandium sr=jonas
authorRay Cheung <pzhang@mozilla.com>
Wed, 19 Sep 2012 11:23:33 -0400
changeset 114052 432033434b3a33c93e7b9333d318ea5685efe309
parent 114051 6bd02a2c2f326cdd17a74a8a29162e5561997725
child 114053 f7354c77ad5cb27dc036ab70f1a3196e25ab75f3
push id1708
push userakeybl@mozilla.com
push dateMon, 19 Nov 2012 21:10:21 +0000
treeherdermozilla-beta@27b14fe50103 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlebar, glandium, jonas
bugs779500
milestone18.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
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/PermissionSettings.jsm');
 Cu.import('resource://gre/modules/ObjectWrapper.jsm');
 Cu.import('resource://gre/modules/accessibility/AccessFu.jsm');
 Cu.import('resource://gre/modules/Payment.jsm');
 
--- 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
@@ -450,16 +453,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
@@ -2077,16 +2077,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"
@@ -7316,16 +7317,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
@@ -77,16 +77,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
@@ -513,16 +513,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"
@@ -1677,16 +1681,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,
@@ -4479,16 +4488,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
@@ -514,16 +514,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
@@ -40,14 +40,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
@@ -114,16 +114,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	+= \
@@ -272,15 +278,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
@@ -113,16 +113,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
 
@@ -282,16 +288,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)
@@ -762,16 +773,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);
@@ -1038,16 +1054,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 },
@@ -1179,16 +1198,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