Bug 674726 - WebTelephony. r=philikon+mounir, sr=sicking. Not part of the default build.
authorBen Turner <bent.mozilla@gmail.com>
Mon, 09 Jan 2012 14:28:47 -0800
changeset 84063 bac673bc7211a1f5cc8487f564f482a9aca03219
parent 84062 1927c7905f5e6dcc562b2c97bd4f1c8842264f1f
child 84064 b575f4ce92734f304c169f5a88fc22173e0647ce
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersphilikon, sicking
bugs674726
milestone12.0a1
Bug 674726 - WebTelephony. r=philikon+mounir, sr=sicking. Not part of the default build.
b2g/app/b2g.js
dom/Makefile.in
dom/base/Makefile.in
dom/base/Navigator.cpp
dom/base/Navigator.h
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/system/b2g/RadioManager.cpp
dom/telephony/CallEvent.cpp
dom/telephony/CallEvent.h
dom/telephony/Makefile.in
dom/telephony/Telephony.cpp
dom/telephony/Telephony.h
dom/telephony/Telephony.js
dom/telephony/Telephony.manifest
dom/telephony/TelephonyCall.cpp
dom/telephony/TelephonyCall.h
dom/telephony/TelephonyCommon.h
dom/telephony/TelephonyFactory.h
dom/telephony/mozIDOMTelephony.idl
dom/telephony/nsIDOMCallEvent.idl
dom/telephony/nsIDOMNavigatorTelephony.idl
dom/telephony/nsIDOMTelephony.idl
dom/telephony/nsIDOMTelephonyCall.idl
dom/telephony/nsITelephone.idl
dom/telephony/nsTelephonyWorker.js
dom/telephony/ril_consts.js
dom/telephony/ril_worker.js
js/xpconnect/src/dom_quickstubs.qsconf
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -41,16 +41,19 @@ pref("toolkit.defaultChromeURI", "chrome
 pref("general.useragent.compatMode.firefox", true);
 pref("browser.chromeURL", "chrome://browser/content/");
 #ifdef MOZ_OFFICIAL_BRANDING
 pref("browser.homescreenURL", "file:///system/home/homescreen.html");
 #else
 pref("browser.homescreenURL", "file:///data/local/homescreen.html,file:///system/home/homescreen.html");
 #endif
 
+// URL for the dialer application.
+pref("dom.telephony.app.phone.url", "http://localhost:7777/dialer/dialer.html");
+
 // Device pixel to CSS px ratio, in percent. Set to -1 to calculate based on display density.
 pref("browser.viewport.scaleRatio", -1);
 
 /* disable text selection */
 pref("browser.ignoreNativeFrameTextSelection", true);
 
 /* cache prefs */
 pref("browser.cache.disk.enable", false);
--- a/dom/Makefile.in
+++ b/dom/Makefile.in
@@ -78,22 +78,22 @@ DIRS += \
   plugins/base \
   plugins/ipc \
   indexedDB \
   system \
   ipc \
   workers \
   $(NULL)
 
-ifdef MOZ_B2G_RIL #{
+ifdef MOZ_B2G_RIL
 DIRS += \
   telephony \
   wifi \
   $(NULL)
-endif #}
+endif
 
 ifdef ENABLE_TESTS
 DIRS += tests
 ifneq (,$(filter gtk2 cocoa windows android qt os2,$(MOZ_WIDGET_TOOLKIT)))
 DIRS += plugins/test
 endif
 endif
 
--- a/dom/base/Makefile.in
+++ b/dom/base/Makefile.in
@@ -132,16 +132,20 @@ CPPSRCS =			\
 	$(NULL)
 
 include $(topsrcdir)/dom/dom-config.mk
 
 ifdef MOZ_JSDEBUGGER
 DEFINES += -DMOZ_JSDEBUGGER
 endif
 
+ifdef MOZ_B2G_RIL
+DEFINES += -DMOZ_B2G_RIL
+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/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -70,16 +70,20 @@
 #include "mozilla/Telemetry.h"
 #include "BatteryManager.h"
 #include "SmsManager.h"
 #include "nsISmsService.h"
 #include "mozilla/Hal.h"
 #include "nsIWebNavigation.h"
 #include "mozilla/ClearOnShutdown.h"
 
+#ifdef MOZ_B2G_RIL
+#include "TelephonyFactory.h"
+#endif
+
 // This should not be in the namespace.
 DOMCI_DATA(Navigator, mozilla::dom::Navigator)
 
 namespace mozilla {
 namespace dom {
 
 static const char sJSStackContractID[] = "@mozilla.org/js/xpc/ContextStack;1";
 
@@ -118,16 +122,19 @@ Navigator::~Navigator()
 NS_INTERFACE_MAP_BEGIN(Navigator)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMNavigator)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNavigator)
   NS_INTERFACE_MAP_ENTRY(nsIDOMClientInformation)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorGeolocation)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozNavigatorBattery)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorDesktopNotification)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozNavigatorSms)
+#ifdef MOZ_B2G_RIL
+  NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorTelephony)
+#endif
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Navigator)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(Navigator)
 NS_IMPL_RELEASE(Navigator)
 
 void
 Navigator::Invalidate()
@@ -154,16 +161,22 @@ Navigator::Invalidate()
     mBatteryManager->Shutdown();
     mBatteryManager = nsnull;
   }
 
   if (mSmsManager) {
     mSmsManager->Shutdown();
     mSmsManager = nsnull;
   }
+
+#ifdef MOZ_B2G_RIL
+  if (mTelephony) {
+    mTelephony = nsnull;
+  }
+#endif
 }
 
 nsPIDOMWindow *
 Navigator::GetWindow()
 {
   nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mWindow));
 
   return win;
@@ -1013,16 +1026,44 @@ Navigator::GetMozSms(nsIDOMMozSmsManager
     mSmsManager->Init(window, scx);
   }
 
   NS_ADDREF(*aSmsManager = mSmsManager);
 
   return NS_OK;
 }
 
+#ifdef MOZ_B2G_RIL
+
+//*****************************************************************************
+//    nsNavigator::nsIDOMNavigatorTelephony
+//*****************************************************************************
+
+NS_IMETHODIMP
+Navigator::GetMozTelephony(nsIDOMTelephony** aTelephony)
+{
+  nsCOMPtr<nsIDOMTelephony> telephony = mTelephony;
+
+  if (!telephony) {
+    nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
+    NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+    nsresult rv = NS_NewTelephony(window, getter_AddRefs(mTelephony));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // mTelephony may be null here!
+    telephony = mTelephony;
+  }
+
+  telephony.forget(aTelephony);
+  return NS_OK;
+}
+
+#endif // MOZ_B2G_RIL
+
 PRInt64
 Navigator::SizeOf() const
 {
   PRInt64 size = sizeof(*this);
 
   // TODO: add SizeOf() to nsMimeTypeArray, bug 674113.
   size += mMimeTypes ? sizeof(*mMimeTypes.get()) : 0;
   // TODO: add SizeOf() to nsPluginArray, bug 674114.
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -53,16 +53,21 @@
 #include "nsWeakReference.h"
 
 class nsPluginArray;
 class nsMimeTypeArray;
 class nsGeolocation;
 class nsDesktopNotificationCenter;
 class nsPIDOMWindow;
 
+#ifdef MOZ_B2G_RIL
+#include "nsIDOMNavigatorTelephony.h"
+class nsIDOMTelephony;
+#endif
+
 //*****************************************************************************
 // Navigator: Script "navigator" object
 //*****************************************************************************
 
 namespace mozilla {
 namespace dom {
 
 namespace battery {
@@ -74,29 +79,36 @@ class SmsManager;
 } // namespace sms
 
 class Navigator : public nsIDOMNavigator,
                   public nsIDOMClientInformation,
                   public nsIDOMNavigatorGeolocation,
                   public nsIDOMNavigatorDesktopNotification,
                   public nsIDOMMozNavigatorBattery,
                   public nsIDOMMozNavigatorSms
+#ifdef MOZ_B2G_RIL
+                , public nsIDOMNavigatorTelephony
+#endif
 {
 public:
   Navigator(nsPIDOMWindow *aInnerWindow);
   virtual ~Navigator();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMNAVIGATOR
   NS_DECL_NSIDOMCLIENTINFORMATION
   NS_DECL_NSIDOMNAVIGATORGEOLOCATION
   NS_DECL_NSIDOMNAVIGATORDESKTOPNOTIFICATION
   NS_DECL_NSIDOMMOZNAVIGATORBATTERY
   NS_DECL_NSIDOMMOZNAVIGATORSMS
 
+#ifdef MOZ_B2G_RIL
+  NS_DECL_NSIDOMNAVIGATORTELEPHONY
+#endif
+
   static void Init();
 
   void Invalidate();
   nsPIDOMWindow *GetWindow();
 
   void RefreshMIMEArray();
 
   static bool HasDesktopNotificationSupport();
@@ -113,16 +125,19 @@ private:
   bool IsSmsSupported() const;
 
   nsRefPtr<nsMimeTypeArray> mMimeTypes;
   nsRefPtr<nsPluginArray> mPlugins;
   nsRefPtr<nsGeolocation> mGeolocation;
   nsRefPtr<nsDesktopNotificationCenter> mNotification;
   nsRefPtr<battery::BatteryManager> mBatteryManager;
   nsRefPtr<sms::SmsManager> mSmsManager;
+#ifdef MOZ_B2G_RIL
+  nsCOMPtr<nsIDOMTelephony> mTelephony;
+#endif
   nsWeakPtr mWindow;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 nsresult NS_GetNavigatorUserAgent(nsAString& aUserAgent);
 nsresult NS_GetNavigatorPlatform(nsAString& aPlatform);
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -511,16 +511,22 @@
 
 #include "nsIDOMBatteryManager.h"
 #include "BatteryManager.h"
 #include "nsIDOMSmsManager.h"
 #include "nsIDOMSmsMessage.h"
 #include "nsIDOMSmsEvent.h"
 #include "nsIPrivateDOMEvent.h"
 
+#ifdef MOZ_B2G_RIL
+#include "Telephony.h"
+#include "TelephonyCall.h"
+#include "CallEvent.h"
+#endif
+
 using namespace mozilla;
 using namespace mozilla::dom;
 
 static NS_DEFINE_CID(kDOMSOF_CID, NS_DOM_SCRIPT_OBJECT_FACTORY_CID);
 
 static const char kDOMStringBundleURL[] =
   "chrome://global/locale/dom/dom.properties";
 
@@ -1548,16 +1554,25 @@ static nsDOMClassInfoData sClassInfoData
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(MozCSSKeyframesRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(MediaQueryList, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(CustomEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
+#ifdef MOZ_B2G_RIL
+  NS_DEFINE_CLASSINFO_DATA(Telephony, nsEventTargetSH,
+                           EVENTTARGET_SCRIPTABLE_FLAGS)
+  NS_DEFINE_CLASSINFO_DATA(TelephonyCall, nsEventTargetSH,
+                           EVENTTARGET_SCRIPTABLE_FLAGS)
+  NS_DEFINE_CLASSINFO_DATA(CallEvent, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+#endif
 };
 
 // Objects that should be constructable through |new Name();|
 struct nsContractIDMapData
 {
   PRInt32 mDOMClassInfoID;
   const char *mContractID;
 };
@@ -2336,16 +2351,19 @@ nsDOMClassInfo::Init()
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigator)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorGeolocation)
     DOM_CLASSINFO_MAP_CONDITIONAL_ENTRY(nsIDOMNavigatorDesktopNotification,
                                         Navigator::HasDesktopNotificationSupport())
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMClientInformation)
     DOM_CLASSINFO_MAP_CONDITIONAL_ENTRY(nsIDOMMozNavigatorBattery,
                                         battery::BatteryManager::HasSupport())
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozNavigatorSms)
+#ifdef MOZ_B2G_RIL
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorTelephony)
+#endif
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(Plugin, nsIDOMPlugin)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMPlugin)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(PluginArray, nsIDOMPluginArray)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMPluginArray)
@@ -4240,16 +4258,33 @@ nsDOMClassInfo::Init()
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMediaQueryList)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(CustomEvent, nsIDOMCustomEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMCustomEvent)
     DOM_CLASSINFO_EVENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
+#ifdef MOZ_B2G_RIL
+  DOM_CLASSINFO_MAP_BEGIN(Telephony, nsIDOMTelephony)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMTelephony)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
+  DOM_CLASSINFO_MAP_END
+
+  DOM_CLASSINFO_MAP_BEGIN(TelephonyCall, nsIDOMTelephonyCall)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMTelephonyCall)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
+  DOM_CLASSINFO_MAP_END
+
+  DOM_CLASSINFO_MAP_BEGIN(CallEvent, nsIDOMCallEvent)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMCallEvent)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMEvent)
+  DOM_CLASSINFO_MAP_END
+#endif
+
 #ifdef NS_DEBUG
   {
     PRUint32 i = ArrayLength(sClassInfoData);
 
     if (i != eDOMClassInfoIDCount) {
       NS_ERROR("The number of items in sClassInfoData doesn't match the "
                "number of nsIDOMClassInfo ID's, this is bad! Fix it!");
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -521,8 +521,14 @@ DOMCI_CLASS(Touch)
 DOMCI_CLASS(TouchList)
 DOMCI_CLASS(TouchEvent)
 
 DOMCI_CLASS(MozCSSKeyframeRule)
 DOMCI_CLASS(MozCSSKeyframesRule)
 
 DOMCI_CLASS(MediaQueryList)
 DOMCI_CLASS(CustomEvent)
+
+#ifdef MOZ_B2G_RIL
+DOMCI_CLASS(Telephony)
+DOMCI_CLASS(TelephonyCall)
+DOMCI_CLASS(CallEvent)
+#endif
--- a/dom/system/b2g/RadioManager.cpp
+++ b/dom/system/b2g/RadioManager.cpp
@@ -51,18 +51,20 @@
 #include "nsWifiWorker.h"
 #include "nsIWifi.h"
 
 #include "nsThreadUtils.h"
 
 #if defined(MOZ_WIDGET_GONK)
 #include <android/log.h>
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Gonk", args)
+#elif defined(__GNUC__)
+#define LOG(args...)  printf(args);
 #else
-#define LOG(args...)  printf(args);
+#define LOG(args) printf args
 #endif
 
 USING_WORKERS_NAMESPACE
 using namespace mozilla::ipc;
 
 static NS_DEFINE_CID(kTelephonyWorkerCID, NS_TELEPHONYWORKER_CID);
 static NS_DEFINE_CID(kWifiWorkerCID, NS_WIFIWORKER_CID);
 
@@ -145,17 +147,18 @@ bool
 ConnectWorkerToRIL::RunTask(JSContext *aCx)
 {
   // Set up the postRILMessage on the function for worker -> RIL thread
   // communication.
   NS_ASSERTION(!NS_IsMainThread(), "Expecting to be on the worker thread");
   NS_ASSERTION(!JS_IsRunning(aCx), "Are we being called somehow?");
   JSObject *workerGlobal = JS_GetGlobalObject(aCx);
 
-  return JS_DefineFunction(aCx, workerGlobal, "postRILMessage", PostToRIL, 1, 0);
+  return !!JS_DefineFunction(aCx, workerGlobal, "postRILMessage", PostToRIL, 1,
+                             0);
 }
 
 class RILReceiver : public RilConsumer
 {
   class DispatchRILEvent : public WorkerTask {
   public:
     DispatchRILEvent(RilRawData *aMessage)
       : mMessage(aMessage)
new file mode 100644
--- /dev/null
+++ b/dom/telephony/CallEvent.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "CallEvent.h"
+
+#include "nsDOMClassInfo.h"
+
+#include "Telephony.h"
+#include "TelephonyCall.h"
+
+USING_TELEPHONY_NAMESPACE
+
+// static
+already_AddRefed<CallEvent>
+CallEvent::Create(TelephonyCall* aCall)
+{
+  NS_ASSERTION(aCall, "Null pointer!");
+
+  nsRefPtr<CallEvent> event = new CallEvent();
+
+  event->mCall = aCall;
+
+  return event.forget();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CallEvent)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CallEvent,
+                                                  nsDOMEvent)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_PTR(tmp->mCall->ToISupports(),
+                                               TelephonyCall, "mCall")
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CallEvent,
+                                                nsDOMEvent)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCall)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(CallEvent)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMCallEvent)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CallEvent)
+NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
+
+NS_IMPL_ADDREF_INHERITED(CallEvent, nsDOMEvent)
+NS_IMPL_RELEASE_INHERITED(CallEvent, nsDOMEvent)
+
+DOMCI_DATA(CallEvent, CallEvent)
+
+NS_IMETHODIMP
+CallEvent::GetCall(nsIDOMTelephonyCall** aCall)
+{
+  nsCOMPtr<nsIDOMTelephonyCall> call = mCall;
+  call.forget(aCall);
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/telephony/CallEvent.h
@@ -0,0 +1,99 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef mozilla_dom_telephony_callevent_h__
+#define mozilla_dom_telephony_callevent_h__
+
+#include "TelephonyCommon.h"
+
+#include "nsIDOMCallEvent.h"
+#include "nsIDOMEventTarget.h"
+
+#include "nsDOMEvent.h"
+
+BEGIN_TELEPHONY_NAMESPACE
+
+class CallEvent : public nsDOMEvent,
+                  public nsIDOMCallEvent
+{
+  nsRefPtr<TelephonyCall> mCall;
+
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_FORWARD_TO_NSDOMEVENT
+  NS_DECL_NSIDOMCALLEVENT
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CallEvent, nsDOMEvent)
+
+  static already_AddRefed<CallEvent>
+  Create(TelephonyCall* aCall);
+
+  nsresult
+  Dispatch(nsIDOMEventTarget* aTarget, const nsAString& aEventType)
+  {
+    NS_ASSERTION(aTarget, "Null pointer!");
+    NS_ASSERTION(!aEventType.IsEmpty(), "Empty event type!");
+
+    nsresult rv = InitEvent(aEventType, false, false);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = SetTrusted(true);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsIDOMEvent* thisEvent =
+      static_cast<nsDOMEvent*>(const_cast<CallEvent*>(this));
+
+    bool dummy;
+    rv = aTarget->DispatchEvent(thisEvent, &dummy);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+private:
+  CallEvent()
+  : nsDOMEvent(nsnull, nsnull)
+  { }
+
+  ~CallEvent()
+  { }
+};
+
+END_TELEPHONY_NAMESPACE
+
+#endif // mozilla_dom_telephony_callevent_h__
--- a/dom/telephony/Makefile.in
+++ b/dom/telephony/Makefile.in
@@ -45,24 +45,31 @@ include $(DEPTH)/config/autoconf.mk
 MODULE           = dom
 LIBRARY_NAME     = domtelephony_s
 XPIDL_MODULE     = dom_telephony
 LIBXUL_LIBRARY   = 1
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/dom/dom-config.mk
 
+CPPSRCS = \
+  Telephony.cpp \
+  TelephonyCall.cpp \
+  CallEvent.cpp \
+  $(NULL)
+
 XPIDLSRCS = \
-  mozIDOMTelephony.idl \
+  nsIDOMNavigatorTelephony.idl \
+  nsIDOMTelephony.idl \
+  nsIDOMTelephonyCall.idl \
+  nsIDOMCallEvent.idl \
   nsITelephone.idl \
   $(NULL)
 
 EXTRA_COMPONENTS = \
-  Telephony.manifest \
-  Telephony.js \
   nsTelephonyWorker.manifest \
   nsTelephonyWorker.js \
   $(NULL)
 
 EXTRA_JS_MODULES = \
   ril_consts.js \
   ril_worker.js \
   $(NULL)
new file mode 100644
--- /dev/null
+++ b/dom/telephony/Telephony.cpp
@@ -0,0 +1,465 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "Telephony.h"
+
+#include "nsIDocument.h"
+#include "nsIURI.h"
+#include "nsPIDOMWindow.h"
+
+#include "jsapi.h"
+#include "mozilla/Preferences.h"
+#include "nsContentUtils.h"
+#include "nsDOMClassInfo.h"
+#include "nsServiceManagerUtils.h"
+
+#include "CallEvent.h"
+#include "TelephonyCall.h"
+
+USING_TELEPHONY_NAMESPACE
+using mozilla::Preferences;
+
+#define DOM_TELEPHONY_APP_PHONE_URL_PREF "dom.telephony.app.phone.url"
+
+namespace {
+
+template <class T>
+inline nsresult
+nsTArrayToJSArray(JSContext* aCx, JSObject* aGlobal,
+                  const nsTArray<nsRefPtr<T> >& aSourceArray,
+                  JSObject** aResultArray)
+{
+  NS_ASSERTION(aCx, "Null context!");
+  NS_ASSERTION(aGlobal, "Null global!");
+
+  JSAutoRequest ar(aCx);
+  JSAutoEnterCompartment ac;
+  if (!ac.enter(aCx, aGlobal)) {
+    NS_WARNING("Failed to enter compartment!");
+    return NS_ERROR_FAILURE;
+  }
+
+  JSObject* arrayObj;
+
+  if (aSourceArray.IsEmpty()) {
+    arrayObj = JS_NewArrayObject(aCx, 0, nsnull);
+  } else {
+    nsTArray<jsval> valArray;
+    valArray.SetLength(aSourceArray.Length());
+
+    for (PRUint32 index = 0; index < valArray.Length(); index++) {
+      nsISupports* obj = aSourceArray[index]->ToISupports();
+      nsresult rv =
+        nsContentUtils::WrapNative(aCx, aGlobal, obj, &valArray[index]);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    arrayObj = JS_NewArrayObject(aCx, valArray.Length(), valArray.Elements());
+  }
+
+  if (!arrayObj) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  // XXX This is not what Jonas wants. He wants it to be live.
+  if (!JS_FreezeObject(aCx, arrayObj)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  *aResultArray = arrayObj;
+  return NS_OK;
+}
+
+} // anonymous namespace
+
+Telephony::~Telephony()
+{
+  if (mTelephone && mTelephoneCallback) {
+    mTelephone->UnregisterCallback(mTelephoneCallback);
+  }
+
+  if (mRooted) {
+    NS_DROP_JS_OBJECTS(this, Telephony);
+  }
+}
+
+// static
+already_AddRefed<Telephony>
+Telephony::Create(nsPIDOMWindow* aOwner, nsITelephone* aTelephone)
+{
+  NS_ASSERTION(aOwner, "Null owner!");
+  NS_ASSERTION(aTelephone, "Null telephone!");
+
+  nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aOwner);
+  NS_ENSURE_TRUE(sgo, nsnull);
+
+  nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext();
+  NS_ENSURE_TRUE(scriptContext, nsnull);
+
+  nsRefPtr<Telephony> telephony = new Telephony();
+
+  telephony->mOwner = aOwner;
+  telephony->mScriptContext.swap(scriptContext);
+  telephony->mTelephone = aTelephone;
+  telephony->mTelephoneCallback = new TelephoneCallback(telephony);
+
+  nsresult rv = aTelephone->EnumerateCalls(telephony->mTelephoneCallback);
+  NS_ENSURE_SUCCESS(rv, nsnull);
+
+  rv = aTelephone->RegisterCallback(telephony->mTelephoneCallback);
+  NS_ENSURE_SUCCESS(rv, nsnull);
+
+  return telephony.forget();
+}
+
+void
+Telephony::SwitchActiveCall(TelephonyCall* aCall)
+{
+  if (mActiveCall) {
+    // Put the call on hold?
+    NS_NOTYETIMPLEMENTED("Implement me!");
+  }
+  mActiveCall = aCall;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Telephony)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Telephony,
+                                                  nsDOMEventTargetWrapperCache)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+  NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(incoming)
+  for (PRUint32 index = 0; index < tmp->mCalls.Length(); index++) {
+    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCalls[i]");
+    cb.NoteXPCOMChild(tmp->mCalls[index]->ToISupports());
+  }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Telephony,
+                                               nsDOMEventTargetWrapperCache)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_CALLBACK(tmp->mCallsArray, "mCallsArray")
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Telephony,
+                                                nsDOMEventTargetWrapperCache)
+  NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(incoming)
+  tmp->mCalls.Clear();
+  tmp->mActiveCall = nsnull;
+  tmp->mCallsArray = nsnull;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Telephony)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMTelephony)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Telephony)
+NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetWrapperCache)
+
+NS_IMPL_ADDREF_INHERITED(Telephony, nsDOMEventTargetWrapperCache)
+NS_IMPL_RELEASE_INHERITED(Telephony, nsDOMEventTargetWrapperCache)
+
+DOMCI_DATA(Telephony, Telephony)
+
+NS_IMPL_ISUPPORTS1(Telephony::TelephoneCallback, nsITelephoneCallback)
+
+NS_IMETHODIMP
+Telephony::Dial(const nsAString& aNumber, nsIDOMTelephonyCall** aResult)
+{
+  NS_ENSURE_ARG(!aNumber.IsEmpty());
+
+  nsresult rv = mTelephone->Dial(aNumber);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<TelephonyCall> call =
+    TelephonyCall::Create(this, aNumber, nsITelephone::CALL_STATE_DIALING);
+  NS_ASSERTION(call, "This should never fail!");
+
+  NS_ASSERTION(mCalls.Contains(call), "Should have auto-added new call!");
+
+  call.forget(aResult);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Telephony::GetMuted(bool* aMuted)
+{
+  nsresult rv = mTelephone->GetMicrophoneMuted(aMuted);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Telephony::SetMuted(bool aMuted)
+{
+  nsresult rv = mTelephone->SetMicrophoneMuted(aMuted);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Telephony::GetSpeakerEnabled(bool* aSpeakerEnabled)
+{
+  nsresult rv = mTelephone->GetSpeakerEnabled(aSpeakerEnabled);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Telephony::SetSpeakerEnabled(bool aSpeakerEnabled)
+{
+  nsresult rv = mTelephone->SetSpeakerEnabled(aSpeakerEnabled);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Telephony::GetActive(jsval* aActive)
+{
+  if (!mActiveCall) {
+    aActive->setNull();
+    return NS_OK;
+  }
+
+  nsresult rv =
+    nsContentUtils::WrapNative(mScriptContext->GetNativeContext(),
+                               mScriptContext->GetNativeGlobal(),
+                               mActiveCall->ToISupports(), aActive);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Telephony::SetActive(const jsval& aActive)
+{
+  if (aActive.isObject()) {
+    nsIXPConnect* xpc = nsContentUtils::XPConnect();
+    NS_ASSERTION(xpc, "This should never be null!");
+
+    nsISupports* native =
+      xpc->GetNativeOfWrapper(mScriptContext->GetNativeContext(),
+                              &aActive.toObject());
+
+    nsCOMPtr<nsIDOMTelephonyCall> call = do_QueryInterface(native);
+    if (call) {
+      // See if this call has the same telephony object. Otherwise we can't use
+      // it.
+      TelephonyCall* concreteCall = static_cast<TelephonyCall*>(call.get());
+      if (this == concreteCall->mTelephony) {
+        SwitchActiveCall(concreteCall);
+        return NS_OK;
+      }
+    }
+  }
+
+  return NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP
+Telephony::GetCalls(jsval* aCalls)
+{
+  JSObject* calls = mCallsArray;
+  if (!calls) {
+    nsresult rv =
+      nsTArrayToJSArray(mScriptContext->GetNativeContext(),
+                        mScriptContext->GetNativeGlobal(), mCalls, &calls);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!mRooted) {
+      NS_HOLD_JS_OBJECTS(this, Telephony);
+      mRooted = true;
+    }
+
+    mCallsArray = calls;
+  }
+
+  aCalls->setObject(*calls);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Telephony::StartTone(const nsAString& aDTMFChar)
+{
+  if (aDTMFChar.IsEmpty()) {
+    NS_WARNING("Empty tone string will be ignored");
+    return NS_OK;
+  }
+
+  if (aDTMFChar.Length() > 1) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsresult rv = mTelephone->StartTone(aDTMFChar);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Telephony::StopTone()
+{
+  nsresult rv = mTelephone->StopTone();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Telephony::SendTones(const nsAString& aTones, PRUint32 aToneDuration,
+                     PRUint32 aIntervalDuration)
+{
+  NS_NOTYETIMPLEMENTED("Implement me!");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMPL_EVENT_HANDLER(Telephony, incoming)
+
+NS_IMETHODIMP
+Telephony::CallStateChanged(PRUint32 aCallIndex, PRUint16 aCallState,
+                            const nsAString& aNumber)
+{
+  // If we already know about this call then just update its state.
+  for (PRUint32 index = 0; index < mCalls.Length(); index++) {
+    nsRefPtr<TelephonyCall>& tempCall = mCalls[index];
+    if (tempCall->CallIndex() == aCallIndex) {
+      // This can call back and modify the array... Grab a real ref here.
+      nsRefPtr<TelephonyCall> call = tempCall;
+
+      // See if this should replace our current active call.
+      if (aCallState == nsITelephone::CALL_STATE_CONNECTED) {
+        SwitchActiveCall(call);
+      }
+
+      // Change state.
+      call->ChangeState(aCallState);
+      return NS_OK;
+    }
+  }
+
+  // Didn't know anything about this call before now, must be incoming.
+  NS_ASSERTION(aCallState == nsITelephone::CALL_STATE_INCOMING,
+               "Serious logic problem here!");
+
+  nsRefPtr<TelephonyCall> call =
+    TelephonyCall::Create(this, aNumber, aCallState, aCallIndex);
+  NS_ASSERTION(call, "This should never fail!");
+
+  NS_ASSERTION(mCalls.Contains(call), "Should have auto-added new call!");
+
+  // Dispatch incoming event.
+  nsRefPtr<CallEvent> event = CallEvent::Create(call);
+  NS_ASSERTION(event, "This should never fail!");
+
+  nsresult rv =
+    event->Dispatch(ToIDOMEventTarget(), NS_LITERAL_STRING("incoming"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Telephony::EnumerateCallState(PRUint32 aCallIndex, PRUint16 aCallState,
+                              const nsAString& aNumber, bool aIsActive,
+                              bool* aContinue)
+{
+#ifdef DEBUG
+  // Make sure we don't somehow add duplicates.
+  for (PRUint32 index = 0; index < mCalls.Length(); index++) {
+    NS_ASSERTION(mCalls[index]->CallIndex() != aCallIndex,
+                 "Something is really wrong here!");
+  }
+#endif
+  nsRefPtr<TelephonyCall> call =
+    TelephonyCall::Create(this, aNumber, aCallState, aCallIndex);
+  NS_ASSERTION(call, "This should never fail!");
+
+  NS_ASSERTION(mCalls.Contains(call), "Should have auto-added new call!");
+
+  if (aIsActive) {
+    NS_ASSERTION(!mActiveCall, "Already have an active call!");
+    mActiveCall = call;
+  }
+
+  *aContinue = true;
+  return NS_OK;
+}
+
+nsresult
+NS_NewTelephony(nsPIDOMWindow* aWindow, nsIDOMTelephony** aTelephony)
+{
+  NS_ASSERTION(aWindow, "Null pointer!");
+
+  // Make sure we're dealing with an inner window.
+  nsPIDOMWindow* innerWindow = aWindow->IsInnerWindow() ?
+                               aWindow :
+                               aWindow->GetCurrentInnerWindow();
+  NS_ENSURE_TRUE(innerWindow, NS_ERROR_FAILURE);
+
+  if (!nsContentUtils::CanCallerAccess(innerWindow)) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsCOMPtr<nsIDocument> document =
+    do_QueryInterface(innerWindow->GetExtantDocument());
+  NS_ENSURE_TRUE(document, NS_NOINTERFACE);
+
+  nsCOMPtr<nsIURI> documentURI;
+  nsresult rv = document->NodePrincipal()->GetURI(getter_AddRefs(documentURI));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCString documentURL;
+  rv = documentURI->GetSpec(documentURL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCString phoneAppURL;
+  rv = Preferences::GetCString(DOM_TELEPHONY_APP_PHONE_URL_PREF, &phoneAppURL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<Telephony> telephony;
+  if (phoneAppURL.Equals(documentURL, nsCaseInsensitiveCStringComparator())) {
+    nsCOMPtr<nsITelephone> telephone =
+      do_GetService("@mozilla.org/telephony/radio-interface;1");
+    NS_ENSURE_TRUE(telephone, NS_ERROR_FAILURE);
+
+    telephony = Telephony::Create(innerWindow, telephone);
+  }
+  telephony.forget(aTelephony);
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/telephony/Telephony.h
@@ -0,0 +1,158 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef mozilla_dom_telephony_telephony_h__
+#define mozilla_dom_telephony_telephony_h__
+
+#include "TelephonyCommon.h"
+
+#include "nsIDOMTelephony.h"
+#include "nsIDOMTelephonyCall.h"
+#include "nsITelephone.h"
+
+class nsIScriptContext;
+class nsPIDOMWindow;
+
+BEGIN_TELEPHONY_NAMESPACE
+
+class Telephony : public nsDOMEventTargetWrapperCache,
+                  public nsIDOMTelephony
+{
+  nsCOMPtr<nsITelephone> mTelephone;
+  nsCOMPtr<nsITelephoneCallback> mTelephoneCallback;
+
+  NS_DECL_EVENT_HANDLER(incoming);
+
+  TelephonyCall* mActiveCall;
+  nsTArray<nsRefPtr<TelephonyCall> > mCalls;
+
+  // Cached calls array object. Cleared whenever mCalls changes and then rebuilt
+  // once a page looks for the liveCalls attribute.
+  JSObject* mCallsArray;
+
+  bool mRooted;
+
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIDOMTELEPHONY
+  NS_DECL_NSITELEPHONECALLBACK
+  NS_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetWrapperCache::)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+                                                   Telephony,
+                                                   nsDOMEventTargetWrapperCache)
+
+  static already_AddRefed<Telephony>
+  Create(nsPIDOMWindow* aOwner, nsITelephone* aTelephone);
+
+  nsIDOMEventTarget*
+  ToIDOMEventTarget() const
+  {
+    return static_cast<nsDOMEventTargetWrapperCache*>(
+             const_cast<Telephony*>(this));
+  }
+
+  nsISupports*
+  ToISupports() const
+  {
+    return ToIDOMEventTarget();
+  }
+
+  void
+  AddCall(TelephonyCall* aCall)
+  {
+    NS_ASSERTION(!mCalls.Contains(aCall), "Already know about this one!");
+    mCalls.AppendElement(aCall);
+    mCallsArray = nsnull;
+  }
+
+  void
+  RemoveCall(TelephonyCall* aCall)
+  {
+    NS_ASSERTION(mCalls.Contains(aCall), "Didn't know about this one!");
+    mCalls.RemoveElement(aCall);
+    mCallsArray = nsnull;
+  }
+
+  nsITelephone*
+  Telephone() const
+  {
+    return mTelephone;
+  }
+
+  nsPIDOMWindow*
+  Owner() const
+  {
+    return mOwner;
+  }
+
+  nsIScriptContext*
+  ScriptContext() const
+  {
+    return mScriptContext;
+  }
+
+private:
+  Telephony()
+  : mActiveCall(nsnull), mCallsArray(nsnull), mRooted(false)
+  { }
+
+  ~Telephony();
+
+  void
+  SwitchActiveCall(TelephonyCall* aCall);
+
+  class TelephoneCallback : public nsITelephoneCallback
+  {
+    Telephony* mTelephony;
+
+  public:
+    NS_DECL_ISUPPORTS
+    NS_FORWARD_NSITELEPHONECALLBACK(mTelephony->)
+
+    TelephoneCallback(Telephony* aTelephony)
+    : mTelephony(aTelephony)
+    {
+      NS_ASSERTION(mTelephony, "Null pointer!");
+    }
+  };
+};
+
+END_TELEPHONY_NAMESPACE
+
+#endif // mozilla_dom_telephony_telephony_h__
deleted file mode 100644
--- a/dom/telephony/Telephony.js
+++ /dev/null
@@ -1,448 +0,0 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Telephony.
- *
- * The Initial Developer of the Original Code is
- * the Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2011
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *  Philipp von Weitershausen <philipp@weitershausen.de>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-const TELEPHONY_CID = Components.ID("{37e248d2-02ff-469b-bb31-eef5a4a4bee3}");
-const TELEPHONY_CONTRACTID = "@mozilla.org/telephony;1";
-
-const TELEPHONY_CALL_CID = Components.ID("{6b9b3daf-e5ea-460b-89a5-641ee20dd577}");
-const TELEPHONY_CALL_CONTRACTID = "@mozilla.org/telephony-call;1";
-
-
-const DOM_RADIOSTATE_UNAVAILABLE   = "unavailable";
-const DOM_RADIOSTATE_OFF           = "off";
-const DOM_RADIOSTATE_READY         = "ready";
-
-const DOM_CARDSTATE_UNAVAILABLE    = "unavailable";
-const DOM_CARDSTATE_ABSENT         = "absent";
-const DOM_CARDSTATE_PIN_REQUIRED   = "pin_required";
-const DOM_CARDSTATE_PUK_REQUIRED   = "puk_required";
-const DOM_CARDSTATE_NETWORK_LOCKED = "network_locked";
-const DOM_CARDSTATE_NOT_READY      = "not_ready";
-const DOM_CARDSTATE_READY          = "ready";
-
-const DOM_CALL_READYSTATE_DIALING        = "dialing";
-const DOM_CALL_READYSTATE_RINGING        = "ringing";
-const DOM_CALL_READYSTATE_BUSY           = "busy";
-const DOM_CALL_READYSTATE_CONNECTING     = "connecting";
-const DOM_CALL_READYSTATE_CONNECTED      = "connected";
-const DOM_CALL_READYSTATE_DISCONNECTING  = "disconnecting";
-const DOM_CALL_READYSTATE_DISCONNECTED   = "disconnected";
-const DOM_CALL_READYSTATE_INCOMING       = "incoming";
-const DOM_CALL_READYSTATE_HOLDING        = "holding";
-const DOM_CALL_READYSTATE_HELD           = "held";
-
-const CALLINDEX_TEMPORARY_DIALING = -1;
-
-/**
- * Define an event listener slot on an object, e.g.
- *
- *   obj.onerror = function () {...}
- *
- * will register the function as an event handler for the "error" event
- * if the "error" slot was defined on 'obj' or its prototype.
- */
-function defineEventListenerSlot(object, event_type) {
-  let property_name = "on" + event_type;
-  let hidden_name = "_on" + event_type;
-  let bound_name = "_bound_on" + event_type;
-  object.__defineGetter__(property_name, function getter() {
-    return this[hidden_name];
-  });
-  object.__defineSetter__(property_name, function setter(handler) {
-    let old_handler = this[bound_name];
-    if (old_handler) {
-      this.removeEventListener(event_type, old_handler);
-    }
-    // Bind the handler to the object so that its 'this' is correct.
-    let bound_handler = handler.bind(this);
-    this.addEventListener(event_type, bound_handler);
-    this[hidden_name] = handler;
-    this[bound_name] = bound_handler;
-  });
-}
-
-
-/**
- * Base object for event targets.
- */
-function EventTarget() {}
-EventTarget.prototype = {
-
-  addEventListener: function addEventListener(type, handler) {
-    //TODO verify that handler is an nsIDOMEventListener (or function)
-    if (!this._listeners) {
-      this._listeners = {};
-    }
-    if (!this._listeners[type]) {
-      this._listeners[type] = [];
-    }
-    if (this._listeners[type].indexOf(handler) != -1) {
-      // The handler is already registered. Ignore.
-      return;
-    }
-    this._listeners[type].push(handler);
-  },
-
-  removeEventListener: function removeEventListener(type, handler) {
-     let list, index;
-     if (this._listeners &&
-         (list = this._listeners[type]) &&
-         (index = list.indexOf(handler) != -1)) {
-       list.splice(index, 1);
-       return;
-     }
-  },
-
-  dispatchEvent: function dispatchEvent(event) {
-    //TODO this does not deal with bubbling, defaultPrevented, canceling, etc.
-    //TODO disallow re-dispatch of the same event if it's already being
-    // dispatched (recursion).
-    if (!this._listeners) {
-      return;
-    }
-    let handlerList = this._listeners[event.type];
-    if (!handlerList) {
-      return;
-    }
-    event.target = this;
-
-    // We need to worry about event handler mutations during the event firing.
-    // The correct behaviour is to *not* call any listeners that are added
-    // during the firing and to *not* call any listeners that are removed
-    // during the firing. To address this, we make a copy of the listener list
-    // before dispatching and then double-check that each handler is still
-    // registered before firing it.
-    let handlers = handlerList.slice();
-    handlers.forEach(function (handler) {
-      if (handlerList.indexOf(handler) == -1) {
-        return;
-      }
-      switch (typeof handler) {
-        case "function":
-          handler(event);
-          break;
-        case "object":
-          handler.handleEvent(event);
-          break;
-      }
-    });
-  }
-};
-
-/**
- * Callback object that Telephony registers with nsITelephone.
- * Telephony can't use itself because that might overload event handler
- * attributes ('onfoobar').
- */
-function TelephoneCallback(telephony) {
-  this.telephony = telephony;
-}
-TelephoneCallback.prototype = {
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephoneCallback]),
-
-  // nsITelephoneCallback
-
-  onsignalstrengthchange: function onsignalstrengthchange(event) {
-    this.telephony.signalStrength = event.signalStrength;
-    this.telephony._dispatchEventByType("signalstrengthchange");
-  },
-
-  onoperatorchange: function onoperatorchange(event) {
-    this.telephony.operator = event.operator;
-    this.telephony._dispatchEventByType("operatorchange");
-  },
-
-  onradiostatechange: function onradiostatechange(event) {
-    this.telephony.radioState = event.radioState;
-    this.telephony._dispatchEventByType("radiostatechange");
-  },
-
-  oncardstatechange: function oncardstatechange(event) {
-    this.telephony.cardState = event.cardState;
-    this.telephony._dispatchEventByType("cardstatechange");
-  },
-
-  oncallstatechange: function oncallstatechange(event) {
-    this.telephony._processCallState(event);
-  },
-
-};
-
-/**
- * The navigator.mozTelephony object.
- */
-function Telephony() {}
-Telephony.prototype = {
-
-  __proto__: EventTarget.prototype,
-
-  classID: TELEPHONY_CID,
-  classInfo: XPCOMUtils.generateCI({classID: TELEPHONY_CID,
-                                    contractID: TELEPHONY_CONTRACTID,
-                                    interfaces: [Ci.mozIDOMTelephony,
-                                                 Ci.nsIDOMEventTarget],
-                                    flags: Ci.nsIClassInfo.DOM_OBJECT,
-                                    classDescription: "Telephony"}),
-  QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMTelephony,
-                                         Ci.nsIDOMEventTarget,
-                                         Ci.nsIDOMGlobalPropertyInitializer]),
-
-  // nsIDOMGlobalPropertyInitializer
-
-  init: function init(window) {
-    this.window = window;
-    this.telephone = Cc["@mozilla.org/telephony/radio-interface;1"]
-                       .createInstance(Ci.nsITelephone);
-    this.telephoneCallback = new TelephoneCallback(this);
-    //TODO switch to method suggested by bz in bug 707507
-    window.addEventListener("unload", function onunload(event) {
-      this.telephone.unregisterCallback(this.telephoneCallback);
-      this.telephoneCallback = null;
-      this.window = null;
-    }.bind(this));
-    this.telephone.registerCallback(this.telephoneCallback);
-    this.callsByIndex = {};
-    this.liveCalls = [];
-
-    // Populate existing state.
-    let currentState = this.telephone.currentState;
-    let states = currentState.currentCalls;
-    for (let i = 0; i < states.length; i++) {
-      let state = states[i];
-      let call = new TelephonyCall(this.telephone, state.callIndex);
-      call.readyState = state.callState;
-      call.number = state.number;
-      this.liveCalls.push(call);
-      this.callsByIndex[state.callIndex] = call;
-    }
-
-    this.operator        = currentState.operator;
-    this.radioState      = currentState.radioState;
-    this.cardState       = currentState.cardState;
-    this.signalStrength  = currentState.signalStrength;
-  },
-
-  _dispatchEventByType: function _dispatchEventByType(type) {
-    let event = this.window.document.createEvent("Event");
-    event.initEvent(type, false, false);
-    //event.isTrusted = true;
-    this.dispatchEvent(event);
-  },
-
-  _dispatchCallEvent: function _dispatchCallEvent(call, type, target) {
-    let event = this.window.document.createEvent("Event");
-    event.initEvent(type, false, false);
-    event.call = call; //XXX this is probably not going to work
-    //event.isTrusted = true;
-    target = target || call;
-    target.dispatchEvent(event);
-  },
-
-  _processCallState: function _processCallState(state) {
-    // If the call is dialing, chances are good that we just kicked that off
-    // so there's a call object without a callIndex. Let's fix that.
-    if (state.callState == DOM_CALL_READYSTATE_DIALING) {
-      let call = this.callsByIndex[CALLINDEX_TEMPORARY_DIALING];
-      if (call) {
-        call.callIndex = state.callIndex;
-        delete this.callsByIndex[CALLINDEX_TEMPORARY_DIALING];
-        this.callsByIndex[call.callIndex] = call;
-        // Nothing else to do, since the initial call state will already be
-        // DOM_CALL_READYSTATE_DIALING, so there's no event to dispatch.
-        return;
-      }
-    }
-
-    // If there is an existing call object, update state and dispatch event
-    // on it.
-    let call = this.callsByIndex[state.callIndex];
-    if (call) {
-      if (call.readyState == state.callState) {
-        // No change in ready state, don't dispatch an event.
-        return;
-      }
-      if (state.readyState == DOM_CALL_READYSTATE_DISCONNECTED) {
-        let index = this.liveCalls.indexOf(call);
-        if (index != -1) {
-          this.liveCalls.splice(index, 1);
-        }
-        delete this.callsByIndex[call.callIndex];
-      }
-      call.readyState = state.callState;
-      this._dispatchCallEvent(call, "readystatechange");
-      this._dispatchCallEvent(call, state.callState);
-      return;
-    }
-
-    // There's no call object yet, so let's create a new one, except when
-    // the state notified means that the call is over.
-    if (state.readyState == DOM_CALL_READYSTATE_DISCONNECTED) {
-      return;
-    }
-    call = new TelephonyCall(this.telephone, state.callIndex);
-    call.number = state.number;
-    call.readyState = state.callState;
-    this.callsByIndex[state.callIndex] = call;
-    this.liveCalls.push(call);
-
-    let target;
-    if (call.readyState == DOM_CALL_READYSTATE_INCOMING) {
-      target = this;
-    } else {
-      target = call;
-      this._dispatchCallEvent(call, "readystatechange");
-    }
-    this._dispatchCallEvent(call, state.callState, target);
-  },
-
-  callsByIndex: null,
-
-  // mozIDOMTelephony
-
-  liveCalls: null,
-
-  dial: function dial(number) {
-    this.telephone.dial(number);
-
-    // We don't know ahead of time what callIndex the call is going to have
-    // so let's assign a temp value for now and sort it out on the first
-    // 'callstatechange' event.
-    //TODO ensure there isn't already an outgoing call
-    let callIndex = CALLINDEX_TEMPORARY_DIALING;
-    let call = new TelephonyCall(this.telephone, callIndex);
-    call.readyState = DOM_CALL_READYSTATE_DIALING;
-    call.number = number;
-    this.callsByIndex[callIndex] = call;
-    this.liveCalls.push(call);
-    return call;
-  },
-
-  startTone: function startTone(dtmfChar) {
-    this.telephone.startTone(dtmfChar);
-  },
-
-  stopTone: function stopTone() {
-    this.telephone.stopTone();
-  },
-
-  get muted() {
-    return this.telephone.microphoneMuted;
-  },
-  set muted(value) {
-    this.telephone.microphoneMuted = value;
-  },
-
-  get speakerOn() {
-    return this.telephone.speakerEnabled;
-  },
-  set speakerOn(value) {
-    this.telephone.speakerEnabled = value;
-  },
-
-  // Additional stuff that's useful.
-
-  signalStrength: null,
-  operator: null,
-  radioState: DOM_RADIOSTATE_UNAVAILABLE,
-  cardState: DOM_CARDSTATE_UNAVAILABLE,
-
-};
-defineEventListenerSlot(Telephony.prototype, DOM_CALL_READYSTATE_INCOMING);
-//XXX philikon's additions
-defineEventListenerSlot(Telephony.prototype, "radiostatechange");
-defineEventListenerSlot(Telephony.prototype, "cardstatechange");
-defineEventListenerSlot(Telephony.prototype, "signalstrengthchange");
-defineEventListenerSlot(Telephony.prototype, "operatorchange");
-
-
-function TelephonyCall(telephone, callIndex) {
-  this.telephone = telephone;
-  this.callIndex = callIndex;
-}
-TelephonyCall.prototype = {
-
-  __proto__: EventTarget.prototype,
-
-  classID: TELEPHONY_CALL_CID,
-  classInfo: XPCOMUtils.generateCI({classID: TELEPHONY_CALL_CID,
-                                    contractID: TELEPHONY_CALL_CONTRACTID,
-                                    interfaces: [Ci.mozIDOMTelephonyCall,
-                                                 Ci.nsIDOMEventTarget],
-                                    flags: Ci.nsIClassInfo.DOM_OBJECT,
-                                    classDescription: "TelephonyCall"}),
-  QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMTelephonyCall,
-                                         Ci.nsIDOMEventTarget]),
-
-
-  callIndex: null,
-
-  // mozIDOMTelephonyCall
-
-  number: null,
-  readyState: null,
-
-  answer: function answer() {
-    if (this.readyState != DOM_CALL_READYSTATE_INCOMING) {
-      throw "Can only answer an incoming call!";
-    }
-    this.telephone.answerCall();
-  },
-
-  disconnect: function disconnect() {
-    if (this.readyState == DOM_CALL_READYSTATE_INCOMING) {
-      this.telephone.rejectCall();
-    } else {
-      this.telephone.hangUp(this.callIndex);
-    }
-  },
-
-};
-defineEventListenerSlot(TelephonyCall.prototype, "readystatechange");
-defineEventListenerSlot(TelephonyCall.prototype, DOM_CALL_READYSTATE_RINGING);
-defineEventListenerSlot(TelephonyCall.prototype, DOM_CALL_READYSTATE_BUSY);
-defineEventListenerSlot(TelephonyCall.prototype, DOM_CALL_READYSTATE_CONNECTING);
-defineEventListenerSlot(TelephonyCall.prototype, DOM_CALL_READYSTATE_CONNECTED);
-defineEventListenerSlot(TelephonyCall.prototype, DOM_CALL_READYSTATE_DISCONNECTING);
-defineEventListenerSlot(TelephonyCall.prototype, DOM_CALL_READYSTATE_DISCONNECTED);
-
-
-const NSGetFactory = XPCOMUtils.generateNSGetFactory([Telephony]);
deleted file mode 100644
--- a/dom/telephony/Telephony.manifest
+++ /dev/null
@@ -1,4 +0,0 @@
-# Telephony.js
-component {37e248d2-02ff-469b-bb31-eef5a4a4bee3} Telephony.js
-contract @mozilla.org/telephony;1 {37e248d2-02ff-469b-bb31-eef5a4a4bee3}
-category JavaScript-navigator-property mozTelephony @mozilla.org/telephony;1
new file mode 100644
--- /dev/null
+++ b/dom/telephony/TelephonyCall.cpp
@@ -0,0 +1,245 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "TelephonyCall.h"
+
+#include "nsDOMClassInfo.h"
+
+#include "CallEvent.h"
+#include "Telephony.h"
+
+USING_TELEPHONY_NAMESPACE
+
+// static
+already_AddRefed<TelephonyCall>
+TelephonyCall::Create(Telephony* aTelephony, const nsAString& aNumber,
+                      PRUint16 aCallState, PRUint32 aCallIndex)
+{
+  NS_ASSERTION(aTelephony, "Null pointer!");
+  NS_ASSERTION(!aNumber.IsEmpty(), "Empty number!");
+  NS_ASSERTION(aCallIndex >= 1, "Invalid call index!");
+
+  nsRefPtr<TelephonyCall> call = new TelephonyCall();
+
+  call->mOwner = aTelephony->Owner();
+  call->mScriptContext = aTelephony->ScriptContext();
+  call->mTelephony = aTelephony;
+  call->mNumber = aNumber;
+  call->mCallIndex = aCallIndex;
+
+  call->ChangeStateInternal(aCallState, false);
+
+  return call.forget();
+}
+
+void
+TelephonyCall::ChangeStateInternal(PRUint16 aCallState, bool aFireEvents)
+{
+  nsRefPtr<TelephonyCall> kungFuDeathGrip(this);
+
+  nsString stateString;
+  switch (aCallState) {
+    case nsITelephone::CALL_STATE_DIALING:
+      stateString.AssignLiteral("dialing");
+      break;
+    case nsITelephone::CALL_STATE_RINGING:
+      stateString.AssignLiteral("ringing");
+      break;
+    case nsITelephone::CALL_STATE_BUSY:
+      stateString.AssignLiteral("busy");
+      break;
+    case nsITelephone::CALL_STATE_CONNECTING:
+      stateString.AssignLiteral("connecting");
+      break;
+    case nsITelephone::CALL_STATE_CONNECTED:
+      stateString.AssignLiteral("connected");
+      break;
+    case nsITelephone::CALL_STATE_HOLDING:
+      stateString.AssignLiteral("holding");
+      break;
+    case nsITelephone::CALL_STATE_HELD:
+      stateString.AssignLiteral("held");
+      break;
+    case nsITelephone::CALL_STATE_RESUMING:
+      stateString.AssignLiteral("resuming");
+      break;
+    case nsITelephone::CALL_STATE_DISCONNECTING:
+      stateString.AssignLiteral("disconnecting");
+      break;
+    case nsITelephone::CALL_STATE_DISCONNECTED:
+      stateString.AssignLiteral("disconnected");
+      break;
+    case nsITelephone::CALL_STATE_INCOMING:
+      stateString.AssignLiteral("incoming");
+      break;
+    default:
+      NS_NOTREACHED("Unknown state!");
+  }
+
+  mState = stateString;
+  mCallState = aCallState;
+
+  if (aCallState == nsITelephone::CALL_STATE_DISCONNECTED) {
+    NS_ASSERTION(mLive, "Should be live!");
+    mTelephony->RemoveCall(this);
+    mLive = false;
+  } else if (!mLive) {
+    mTelephony->AddCall(this);
+    mLive = true;
+  }
+
+  if (aFireEvents) {
+    nsRefPtr<CallEvent> event = CallEvent::Create(this);
+    NS_ASSERTION(event, "This should never fail!");
+
+    if (NS_FAILED(event->Dispatch(ToIDOMEventTarget(),
+                                  NS_LITERAL_STRING("statechange")))) {
+      NS_WARNING("Failed to dispatch statechange event!");
+    }
+
+    // This can change if the statechange handler called back here... Need to
+    // figure out something smarter.
+    if (mCallState == aCallState) {
+      event = CallEvent::Create(this);
+      NS_ASSERTION(event, "This should never fail!");
+
+      if (NS_FAILED(event->Dispatch(ToIDOMEventTarget(), stateString))) {
+        NS_WARNING("Failed to dispatch specific event!");
+      }
+    }
+  }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(TelephonyCall)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TelephonyCall,
+                                                  nsDOMEventTargetWrapperCache)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_PTR(tmp->mTelephony->ToISupports(),
+                                               Telephony, "mTelephony")
+  NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(statechange)
+  NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(dialing)
+  NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(ringing)
+  NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(busy)
+  NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(connecting)
+  NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(connected)
+  NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(disconnecting)
+  NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(disconnected)
+  NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(incoming)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TelephonyCall,
+                                                nsDOMEventTargetWrapperCache)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mTelephony)
+  NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(statechange)
+  NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(dialing)
+  NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(ringing)
+  NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(busy)
+  NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(connecting)
+  NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(connected)
+  NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(disconnecting)
+  NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(disconnected)
+  NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(incoming)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TelephonyCall)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMTelephonyCall)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TelephonyCall)
+NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetWrapperCache)
+
+NS_IMPL_ADDREF_INHERITED(TelephonyCall, nsDOMEventTargetWrapperCache)
+NS_IMPL_RELEASE_INHERITED(TelephonyCall, nsDOMEventTargetWrapperCache)
+
+DOMCI_DATA(TelephonyCall, TelephonyCall)
+
+NS_IMETHODIMP
+TelephonyCall::GetNumber(nsAString& aNumber)
+{
+  aNumber.Assign(mNumber);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TelephonyCall::GetState(nsAString& aState)
+{
+  aState.Assign(mState);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TelephonyCall::Answer()
+{
+  if (mCallState != nsITelephone::CALL_STATE_INCOMING) {
+    NS_WARNING("Answer on non-incoming call ignored!");
+    return NS_OK;
+  }
+
+  nsresult rv = mTelephony->Telephone()->AnswerCall(mCallIndex);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  ChangeStateInternal(nsITelephone::CALL_STATE_CONNECTING, true);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TelephonyCall::HangUp()
+{
+  if (mCallState == nsITelephone::CALL_STATE_DISCONNECTING ||
+      mCallState == nsITelephone::CALL_STATE_DISCONNECTED) {
+    NS_WARNING("HangUp on previously disconnected call ignored!");
+    return NS_OK;
+  }
+
+  nsresult rv = mCallState == nsITelephone::CALL_STATE_INCOMING ?
+                mTelephony->Telephone()->RejectCall(mCallIndex) :
+                mTelephony->Telephone()->HangUp(mCallIndex);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  ChangeStateInternal(nsITelephone::CALL_STATE_DISCONNECTING, true);
+  return NS_OK;
+}
+
+NS_IMPL_EVENT_HANDLER(TelephonyCall, statechange)
+NS_IMPL_EVENT_HANDLER(TelephonyCall, dialing)
+NS_IMPL_EVENT_HANDLER(TelephonyCall, ringing)
+NS_IMPL_EVENT_HANDLER(TelephonyCall, busy)
+NS_IMPL_EVENT_HANDLER(TelephonyCall, connecting)
+NS_IMPL_EVENT_HANDLER(TelephonyCall, connected)
+NS_IMPL_EVENT_HANDLER(TelephonyCall, disconnecting)
+NS_IMPL_EVENT_HANDLER(TelephonyCall, disconnected)
+NS_IMPL_EVENT_HANDLER(TelephonyCall, incoming)
new file mode 100644
--- /dev/null
+++ b/dom/telephony/TelephonyCall.h
@@ -0,0 +1,131 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef mozilla_dom_telephony_telephonycall_h__
+#define mozilla_dom_telephony_telephonycall_h__
+
+#include "TelephonyCommon.h"
+
+#include "nsIDOMTelephonyCall.h"
+#include "nsITelephone.h"
+
+class nsPIDOMWindow;
+
+BEGIN_TELEPHONY_NAMESPACE
+
+class TelephonyCall : public nsDOMEventTargetWrapperCache,
+                      public nsIDOMTelephonyCall
+{
+  NS_DECL_EVENT_HANDLER(statechange);
+  NS_DECL_EVENT_HANDLER(dialing);
+  NS_DECL_EVENT_HANDLER(ringing);
+  NS_DECL_EVENT_HANDLER(busy);
+  NS_DECL_EVENT_HANDLER(connecting);
+  NS_DECL_EVENT_HANDLER(connected);
+  NS_DECL_EVENT_HANDLER(disconnecting);
+  NS_DECL_EVENT_HANDLER(disconnected);
+  NS_DECL_EVENT_HANDLER(incoming);
+
+  nsRefPtr<Telephony> mTelephony;
+
+  nsString mNumber;
+  nsString mState;
+
+  PRUint32 mCallIndex;
+  PRUint16 mCallState;
+  bool mLive;
+
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIDOMTELEPHONYCALL
+  NS_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetWrapperCache::)
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TelephonyCall,
+                                           nsDOMEventTargetWrapperCache)
+
+  static already_AddRefed<TelephonyCall>
+  Create(Telephony* aTelephony, const nsAString& aNumber, PRUint16 aCallState,
+         PRUint32 aCallIndex = PR_UINT32_MAX);
+
+  nsIDOMEventTarget*
+  ToIDOMEventTarget() const
+  {
+    return static_cast<nsDOMEventTargetWrapperCache*>(
+             const_cast<TelephonyCall*>(this));
+  }
+
+  nsISupports*
+  ToISupports() const
+  {
+    return ToIDOMEventTarget();
+  }
+
+  void
+  ChangeState(PRUint16 aCallState)
+  {
+    ChangeStateInternal(aCallState, true);
+  }
+
+  PRUint32
+  CallIndex() const
+  {
+    return mCallIndex;
+  }
+
+  PRUint16
+  CallState() const
+  {
+    return mCallState;
+  }
+
+private:
+  TelephonyCall()
+  : mCallIndex(PR_UINT32_MAX), mCallState(nsITelephone::CALL_STATE_UNKNOWN),
+    mLive(false)
+  { }
+
+  ~TelephonyCall()
+  { }
+
+  void
+  ChangeStateInternal(PRUint16 aCallState, bool aFireEvents);
+};
+
+END_TELEPHONY_NAMESPACE
+
+#endif // mozilla_dom_telephony_telephonycall_h__
new file mode 100644
--- /dev/null
+++ b/dom/telephony/TelephonyCommon.h
@@ -0,0 +1,68 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef mozilla_dom_telephony_telephonycommon_h__
+#define mozilla_dom_telephony_telephonycommon_h__
+
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDebug.h"
+#include "nsDOMEventTargetWrapperCache.h"
+#include "nsStringGlue.h"
+#include "nsTArray.h"
+
+#define BEGIN_TELEPHONY_NAMESPACE \
+  namespace mozilla { namespace dom { namespace telephony {
+#define END_TELEPHONY_NAMESPACE \
+  } /* namespace telephony */ } /* namespace dom */ } /* namespace mozilla */
+#define USING_TELEPHONY_NAMESPACE \
+  using namespace mozilla::dom::telephony;
+
+class nsIDOMTelephony;
+class nsIDOMTelephonyCall;
+
+BEGIN_TELEPHONY_NAMESPACE
+
+class Telephony;
+class TelephonyCall;
+
+END_TELEPHONY_NAMESPACE
+
+#endif // mozilla_dom_telephony_telephonycommon_h__
new file mode 100644
--- /dev/null
+++ b/dom/telephony/TelephonyFactory.h
@@ -0,0 +1,51 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef mozilla_dom_telephony_telephonyfactory_h__
+#define mozilla_dom_telephony_telephonyfactory_h__
+
+#include "nsIDOMTelephony.h"
+#include "nsPIDOMWindow.h"
+
+// Implemented in Telephony.cpp.
+
+nsresult
+NS_NewTelephony(nsPIDOMWindow* aWindow, nsIDOMTelephony** aTelephony);
+
+#endif // mozilla_dom_telephony_telephonyfactory_h__
deleted file mode 100644
--- a/dom/telephony/mozIDOMTelephony.idl
+++ /dev/null
@@ -1,87 +0,0 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Telephony.
- *
- * The Initial Developer of the Original Code is
- * the Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2011
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *  Philipp von Weitershausen <philipp@weitershausen.de>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#include "nsIDOMEventTarget.idl"
-#include "nsIDOMEvent.idl"
-interface nsIDOMEventListener;
-interface mozIDOMTelephonyCall;
-
-[scriptable, uuid(c7b0046b-ee80-447c-8a95-a389003891bc)]
-interface mozIDOMTelephony : nsIDOMEventTarget {
-
-  readonly attribute jsval liveCalls;
-  mozIDOMTelephonyCall dial(in DOMString number);
-  void startTone(in DOMString dtmfChar);
-  void stopTone();
-  attribute nsIDOMEventListener onincoming;
-
-  attribute boolean muted;
-  attribute boolean speakerOn;
-
-  //XXX philikon's additions
-  attribute nsIDOMEventListener onoperatorchange;
-  attribute nsIDOMEventListener onradiostatechange;
-  attribute nsIDOMEventListener oncardstatechange;
-  attribute nsIDOMEventListener onsignalstrengthchange;
-  readonly attribute jsval signalStrength;
-  readonly attribute jsval operator;
-  readonly attribute jsval radioState;
-  readonly attribute jsval cardState;
-};
-
-[scriptable, uuid(3d0060db-72ef-4b87-aceb-a16ed4c5253e)]
-interface mozIDOMTelephonyCall : nsIDOMEventTarget {
-
-  readonly attribute DOMString number;
-  readonly attribute DOMString readyState;
-
-  void answer();
-  void disconnect();
-
-  attribute nsIDOMEventListener onreadystatechange;
-  attribute nsIDOMEventListener onringing;
-  attribute nsIDOMEventListener onbusy;
-  attribute nsIDOMEventListener onconnecting;
-  attribute nsIDOMEventListener onconnected;
-  attribute nsIDOMEventListener ondisconnecting;
-  attribute nsIDOMEventListener ondisconnected;
-};
-
-[scriptable, uuid(c8c42b0c-a0dd-4702-9425-a7a80b2075c3)]
-interface mozIDOMTelephonyCallEvent : nsIDOMEvent {
-  readonly attribute mozIDOMTelephonyCall call;
-};
new file mode 100644
--- /dev/null
+++ b/dom/telephony/nsIDOMCallEvent.idl
@@ -0,0 +1,48 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsIDOMEvent.idl"
+
+interface nsIDOMTelephonyCall;
+
+[scriptable, builtinclass, uuid(95625a90-7f1c-493b-b4bd-73f05834c6dd)]
+interface nsIDOMCallEvent : nsIDOMEvent
+{
+  readonly attribute nsIDOMTelephonyCall call;
+};
new file mode 100644
--- /dev/null
+++ b/dom/telephony/nsIDOMNavigatorTelephony.idl
@@ -0,0 +1,48 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+interface nsIDOMTelephony;
+
+[scriptable, builtinclass, uuid(fb2f5927-41ea-442a-8292-81074f69dc41)]
+interface nsIDOMNavigatorTelephony : nsISupports
+{
+  readonly attribute nsIDOMTelephony mozTelephony;
+};
new file mode 100644
--- /dev/null
+++ b/dom/telephony/nsIDOMTelephony.idl
@@ -0,0 +1,68 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *   Philipp von Weitershausen <philipp@weitershausen.de>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsIDOMEventTarget.idl"
+
+interface nsIDOMEventListener;
+interface nsIDOMTelephonyCall;
+
+[scriptable, builtinclass, uuid(047be0d8-a9cd-49aa-8948-2f60ff3a7a18)]
+interface nsIDOMTelephony : nsIDOMEventTarget
+{
+  nsIDOMTelephonyCall dial(in DOMString number);
+
+  attribute boolean muted;
+  attribute boolean speakerEnabled;
+
+  // The call that is "active", i.e. receives microphone input and tones
+  // generated via startTone.
+  attribute jsval active;
+
+  // Array of all calls that are currently connected.
+  readonly attribute jsval calls;
+
+  void startTone(in DOMString tone);
+  void stopTone();
+  void sendTones(in DOMString tones,
+                 [optional] in unsigned long toneDuration,
+                 [optional] in unsigned long intervalDuration);
+
+  attribute nsIDOMEventListener onincoming;
+};
new file mode 100644
--- /dev/null
+++ b/dom/telephony/nsIDOMTelephonyCall.idl
@@ -0,0 +1,64 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Telephony.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsIDOMEventTarget.idl"
+
+interface nsIDOMEventListener;
+
+[scriptable, builtinclass, uuid(832b7551-ff53-403f-9e2c-d7d28e2bb40b)]
+interface nsIDOMTelephonyCall : nsIDOMEventTarget
+{
+  readonly attribute DOMString number;
+
+  readonly attribute DOMString state;
+
+  void answer();
+  void hangUp();
+
+  attribute nsIDOMEventListener onstatechange;
+
+  attribute nsIDOMEventListener ondialing;
+  attribute nsIDOMEventListener onringing;
+  attribute nsIDOMEventListener onbusy;
+  attribute nsIDOMEventListener onconnecting;
+  attribute nsIDOMEventListener onconnected;
+  attribute nsIDOMEventListener ondisconnecting;
+  attribute nsIDOMEventListener ondisconnected;
+  attribute nsIDOMEventListener onincoming;
+};
--- a/dom/telephony/nsITelephone.idl
+++ b/dom/telephony/nsITelephone.idl
@@ -35,41 +35,68 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 [scriptable, uuid(9b7e3a01-9c45-4af3-81bb-1bf08a842226)]
 interface nsITelephoneCallback : nsISupports
 {
-  void oncallstatechange(in jsval event);
+  // 'callState' uses the CALL_STATE values from nsITelephone.
+  void callStateChanged(in unsigned long callIndex,
+                        in unsigned short callState,
+                        in AString number);
 
-  //XXX philikon's additions
-  void onoperatorchange(in jsval event);
-  void onradiostatechange(in jsval event);
-  void oncardstatechange(in jsval event);
-  void onsignalstrengthchange(in jsval event);
+  // 'callState' uses the CALL_STATE values from nsITelephone. Return true to
+  // continue enumeration or false to cancel.
+  boolean enumerateCallState(in unsigned long callIndex,
+                             in unsigned short callState,
+                             in AString number,
+                             in boolean isActive);
 };
 
 [scriptable, uuid(5be6e41d-3aee-4f5c-8284-95cf529dd6fe)]
 interface nsITelephone : nsISupports
 {
+  const unsigned short CALL_STATE_UNKNOWN = 0;
+  const unsigned short CALL_STATE_DIALING = 1;
+  const unsigned short CALL_STATE_RINGING = 2;
+  const unsigned short CALL_STATE_BUSY = 3;
+  const unsigned short CALL_STATE_CONNECTING = 4;
+  const unsigned short CALL_STATE_CONNECTED = 5;
+  const unsigned short CALL_STATE_HOLDING = 6;
+  const unsigned short CALL_STATE_HELD = 7;
+  const unsigned short CALL_STATE_RESUMING = 8;
+  const unsigned short CALL_STATE_DISCONNECTING = 9;
+  const unsigned short CALL_STATE_DISCONNECTED = 10;
+  const unsigned short CALL_STATE_INCOMING = 11;
+
   readonly attribute jsval currentState;
+
   void registerCallback(in nsITelephoneCallback callback);
   void unregisterCallback(in nsITelephoneCallback callback);
 
   /**
+   * Will continue calling callback.enumerateCallState until the callback
+   * returns false.
+   */
+  void enumerateCalls(in nsITelephoneCallback callback);
+
+  /**
    * Functionality for making and managing phone calls.
    */
   void dial(in DOMString number);
-  void hangUp(in long callIndex);
+  void hangUp(in unsigned long callIndex);
+
   void startTone(in DOMString dtmfChar);
   void stopTone();
-  void answerCall();
-  void rejectCall();
+
+  void answerCall(in unsigned long callIndex);
+  void rejectCall(in unsigned long callIndex);
+
   attribute bool microphoneMuted;
   attribute bool speakerEnabled;
 
   /**
    * SMS-related functionality.
    */
   unsigned short getNumberOfMessagesForText(in DOMString text);
   void sendSMS(in DOMString number, in DOMString message);
--- a/dom/telephony/nsTelephonyWorker.js
+++ b/dom/telephony/nsTelephonyWorker.js
@@ -31,203 +31,250 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+"use strict";
+
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
+var RIL = {};
+Cu.import("resource://gre/modules/ril_consts.js", RIL);
+
 const DEBUG = true; // set to false to suppress debug messages
 
 const TELEPHONYWORKER_CONTRACTID = "@mozilla.org/telephony/worker;1";
 const TELEPHONYWORKER_CID        = Components.ID("{2d831c8d-6017-435b-a80c-e5d422810cea}");
 
-const DOM_CALL_READYSTATE_DIALING        = "dialing";
-const DOM_CALL_READYSTATE_RINGING        = "ringing";
-const DOM_CALL_READYSTATE_BUSY           = "busy";
-const DOM_CALL_READYSTATE_CONNECTING     = "connecting";
-const DOM_CALL_READYSTATE_CONNECTED      = "connected";
-const DOM_CALL_READYSTATE_DISCONNECTING  = "disconnecting";
-const DOM_CALL_READYSTATE_DISCONNECTED   = "disconnected";
-const DOM_CALL_READYSTATE_INCOMING       = "incoming";
-const DOM_CALL_READYSTATE_HOLDING        = "holding";
-const DOM_CALL_READYSTATE_HELD           = "held";
+const nsIAudioManager = Ci.nsIAudioManager;
+const nsITelephone = Ci.nsITelephone;
 
 const kSmsReceivedObserverTopic          = "sms-received";
 const DOM_SMS_DELIVERY_RECEIVED          = "received";
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSmsService",
                                    "@mozilla.org/sms/smsservice;1",
                                    "nsISmsService");
 
+function convertRILCallState(state) {
+  switch (state) {
+    case RIL.CALL_STATE_ACTIVE:
+      return nsITelephone.CALL_STATE_CONNECTED;
+    case RIL.CALL_STATE_HOLDING:
+      return nsITelephone.CALL_STATE_HELD;
+    case RIL.CALL_STATE_DIALING:
+      return nsITelephone.CALL_STATE_DIALING;
+    case RIL.CALL_STATE_ALERTING:
+      return nsITelephone.CALL_STATE_RINGING;
+    case RIL.CALL_STATE_INCOMING:
+      return nsITelephone.CALL_STATE_INCOMING;
+    case RIL.CALL_STATE_WAITING:
+      return nsITelephone.CALL_STATE_HELD; // XXX This may not be right...
+    default:
+      throw new Error("Unknown rilCallState: " + state);
+  }
+}
+
 /**
  * Fake nsIAudioManager implementation so that we can run the telephony
  * code in a non-Gonk build.
  */
 let FakeAudioManager = {
   microphoneMuted: false,
   masterVolume: 1.0,
   masterMuted: false,
-  phoneState: Ci.nsIAudioManager.PHONE_STATE_CURRENT,
+  phoneState: nsIAudioManager.PHONE_STATE_CURRENT,
   _forceForUse: {},
   setForceForUse: function setForceForUse(usage, force) {
     this._forceForUse[usage] = force;
   },
   getForceForUse: function setForceForUse(usage) {
-    return this._forceForUse[usage] || Ci.nsIAudioManager.FORCE_NONE;
+    return this._forceForUse[usage] || nsIAudioManager.FORCE_NONE;
   }
 };
 
 XPCOMUtils.defineLazyGetter(this, "gAudioManager", function getAudioManager() {
   try {
     return Cc["@mozilla.org/telephony/audiomanager;1"]
-             .getService(Ci.nsIAudioManager);
+             .getService(nsIAudioManager);
   } catch (ex) {
     //TODO on the phone this should not fall back as silently.
     debug("Using fake audio manager.");
     return FakeAudioManager;
   }
 });
 
 
 function nsTelephonyWorker() {
   this.worker = new ChromeWorker("resource://gre/modules/ril_worker.js");
   this.worker.onerror = this.onerror.bind(this);
   this.worker.onmessage = this.onmessage.bind(this);
-
-  this._callbacks = [];
+  debug("Starting Worker\n");
   this.currentState = {
     signalStrength: null,
     operator:       null,
     radioState:     null,
-    cardState:      null,
-    currentCalls:   {}
+    cardState:      null
   };
 }
 nsTelephonyWorker.prototype = {
 
   classID:   TELEPHONYWORKER_CID,
   classInfo: XPCOMUtils.generateCI({classID: TELEPHONYWORKER_CID,
                                     contractID: TELEPHONYWORKER_CONTRACTID,
-                                    classDescription: "TelephonyWorker",
+                                    classDescription: "Telephone",
                                     interfaces: [Ci.nsIRadioWorker,
                                                  Ci.nsITelephone]}),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIRadioWorker,
                                          Ci.nsITelephone]),
 
   onerror: function onerror(event) {
-    // It is very important to call preventDefault on the event here.
-    // If an exception is thrown on the worker, it bubbles out to the
-    // component that created it. If that component doesn't have an
-    // onerror handler, the worker will try to call the error reporter
-    // on the context it was created on. However, That doesn't work
-    // for component contexts and can result in crashes. This onerror
-    // handler has to make sure that it calls preventDefault on the
-    // incoming event.
-    event.preventDefault();
-
     debug("Got an error: " + event.filename + ":" +
           event.lineno + ": " + event.message + "\n");
+    event.preventDefault();
   },
 
   /**
    * Process the incoming message from the RIL worker:
    * (1) Update the current state. This way any component that hasn't
    *     been listening for callbacks can easily catch up by looking at
    *     this.currentState.
    * (2) Update state in related systems such as the audio.
    * (3) Multiplex the message to telephone callbacks.
    */
   onmessage: function onmessage(event) {
     let message = event.data;
     debug("Received message: " + JSON.stringify(message));
-    let value;
     switch (message.type) {
+      case "callStateChange":
+        // This one will handle its own notifications.
+        this.handleCallStateChange(message.call);
+        break;
+      case "callDisconnected":
+        // This one will handle its own notifications.
+        this.handleCallDisconnected(message.call);
+        break;
+      case "enumerateCalls":
+        // This one will handle its own notifications.
+        this.handleEnumerateCalls(message.calls);
+        break;
       case "signalstrengthchange":
         this.currentState.signalStrength = message.signalStrength;
         break;
       case "operatorchange":
         this.currentState.operator = message.operator;
         break;
       case "radiostatechange":
         this.currentState.radioState = message.radioState;
         break;
       case "cardstatechange":
         this.currentState.cardState = message.cardState;
         break;
-      case "callstatechange":
-        this.handleCallState(message);
-        break;
       case "sms-received":
         this.handleSmsReceived(message);
-        break;
+        return;
       default:
-        // Got some message from the RIL worker that we don't know about.
-        return;
+        throw new Error("Don't know about this message type: " + message.type);
     }
-    let methodname = "on" + message.type;
-    this._callbacks.forEach(function (callback) {
-      let method = callback[methodname];
-      if (typeof method != "function") {
-        return;
+  },
+
+  /**
+   * Track the active call and update the audio system as its state changes.
+   *
+   * XXX Needs some more work to support hold/resume.
+   */
+  _activeCall: null,
+  get activeCall() {
+    return this._activeCall;
+  },
+  set activeCall(val) {
+    if (val && !this._activeCall) {
+      // Enable audio.
+      switch (val.state) {
+        case nsITelephone.CALL_STATE_INCOMING:
+          gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_RINGTONE;
+          break;
+        case nsITelephone.CALL_STATE_DIALING: // Fall through...
+        case nsITelephone.CALL_STATE_CONNECTED:
+          gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL;
+          gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION,
+                                       nsIAudioManager.FORCE_NONE);
+          break;
+        default:
+          throw new Error("Invalid call state for active call: " + val.state);
       }
-      method.call(callback, message);
-    });
+    } else if (!val && this._activeCall) {
+      // Disable audio.
+      gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
+    }
+    this._activeCall = val;
   },
 
   /**
-   * Handle call state changes by updating our current state and
-   * the audio system.
+   * Handle call state changes by updating our current state and the audio
+   * system.
    */
-  handleCallState: function handleCallState(message) {
-    let currentCalls = this.currentState.currentCalls;
-    let oldState = currentCalls[message.callIndex];
-
-    // Update current state.
-    if (message.callState == DOM_CALL_READYSTATE_DISCONNECTED) {
-      delete currentCalls[message.callIndex];
-    } else {
-      currentCalls[message.callIndex] = message;
+  handleCallStateChange: function handleCallStateChange(call) {
+    debug("handleCallStateChange: " + JSON.stringify(call));
+    call.state = convertRILCallState(call.state);
+    if (call.state == nsITelephone.CALL_STATE_INCOMING ||
+        call.state == nsITelephone.CALL_STATE_DIALING ||
+        call.state == nsITelephone.CALL_STATE_CONNECTED) {
+      // This is now the active call.
+      this.activeCall = call;
     }
+    this._deliverCallback("callStateChanged",
+                          [call.callIndex, call.state, call.number]);
+  },
 
-    // Update the audio system.
-    //TODO this does not handle multiple concurrent calls yet.
-    switch (message.callState) {
-      case DOM_CALL_READYSTATE_DIALING:
-        this.worker.postMessage({type: "setMute", mute: false});
-        gAudioManager.phoneState = Ci.nsIAudioManager.PHONE_STATE_IN_CALL;
-        gAudioManager.setForceForUse(Ci.nsIAudioManager.USE_COMMUNICATION,
-                                     Ci.nsIAudioManager.FORCE_NONE);
-        break;
-      case DOM_CALL_READYSTATE_INCOMING:
-        gAudioManager.phoneState = Ci.nsIAudioManager.PHONE_STATE_RINGTONE;
+  /**
+   * Handle call disconnects by updating our current state and the audio system.
+   */
+  handleCallDisconnected: function handleCallStateChange(call) {
+    debug("handleCallDisconnected: " + JSON.stringify(call));
+    if (this.activeCall == call) {
+      // No loner active.
+      this.activeCall = null;
+    }
+    this._deliverCallback("callStateChanged",
+                          [call.callIndex, nsITelephone.CALL_STATE_DISCONNECTED,
+                           call.number]);
+  },
+
+  /**
+   * Handle calls delivered in response to a 'enumerateCalls' request.
+   */
+  handleEnumerateCalls: function handleEnumerateCalls(calls) {
+    debug("handleEnumerateCalls: " + JSON.stringify(calls));
+    let callback = this._enumerationCallbacks.shift();
+    let activeCallIndex = this.activeCall ? this.activeCall.callIndex : -1;
+    for (let i in calls) {
+      let call = calls[i];
+      let state = convertRILCallState(call.state);
+      let keepGoing;
+      try {
+        keepGoing =
+          callback.enumerateCallState(call.callIndex, state, call.number,
+                                      call.callIndex == activeCallIndex);
+      } catch (e) {
+        debug("callback handler for 'enumerateCallState' threw an " +
+              " exception: " + e);
+        keepGoing = true;
+      }
+      if (!keepGoing) {
         break;
-      case DOM_CALL_READYSTATE_CONNECTED:
-        if (!oldState ||
-            oldState.callState == DOM_CALL_READYSTATE_INCOMING ||
-            oldState.callState == DOM_CALL_READYSTATE_CONNECTING) {
-          // It's an incoming call, so tweak the audio now. If it was an
-          // outgoing call, it would have been tweaked at dialing.
-          this.worker.postMessage({type: "setMute", mute: false});
-          gAudioManager.phoneState = Ci.nsIAudioManager.PHONE_STATE_IN_CALL;
-          gAudioManager.setForceForUse(Ci.nsIAudioManager.USE_COMMUNICATION,
-                                       Ci.nsIAudioManager.FORCE_NONE);
-        }
-        break;
-      case DOM_CALL_READYSTATE_DISCONNECTED:
-        this.worker.postMessage({type: "setMute", mute: true});
-        gAudioManager.phoneState = Ci.nsIAudioManager.PHONE_STATE_NORMAL;
-        break;
+      }
     }
   },
 
   handleSmsReceived: function handleSmsReceived(message) {
     //TODO: put the sms into a database, assign it a proper id, yada yada
     let sms = gSmsService.createSmsMessage(-1,
                                            DOM_SMS_DELIVERY_RECEIVED,
                                            message.sender || null,
@@ -260,78 +307,122 @@ nsTelephonyWorker.prototype = {
     this.worker.postMessage({type: "startTone", dtmfChar: dtmfChar});
   },
 
   stopTone: function stopTone() {
     debug("Stopping Tone");
     this.worker.postMessage({type: "stopTone"});
   },
 
-  answerCall: function answerCall() {
-    this.worker.postMessage({type: "answerCall"});
+  answerCall: function answerCall(callIndex) {
+    this.worker.postMessage({type: "answerCall", callIndex: callIndex});
   },
 
-  rejectCall: function rejectCall() {
-    this.worker.postMessage({type: "rejectCall"});
+  rejectCall: function rejectCall(callIndex) {
+    this.worker.postMessage({type: "rejectCall", callIndex: callIndex});
   },
 
   get microphoneMuted() {
     return gAudioManager.microphoneMuted;
   },
   set microphoneMuted(value) {
     if (value == this.microphoneMuted) {
       return;
     }
     gAudioManager.phoneState = value ?
-      Ci.nsIAudioManager.PHONE_STATE_IN_COMMUNICATION :
-      Ci.nsIAudioManager.PHONE_STATE_IN_CALL;  //XXX why is this needed?
+      nsIAudioManager.PHONE_STATE_IN_COMMUNICATION :
+      nsIAudioManager.PHONE_STATE_IN_CALL;  //XXX why is this needed?
     gAudioManager.microphoneMuted = value;
   },
 
   get speakerEnabled() {
-    return (gAudioManager.getForceForUse(Ci.nsIAudioManager.USE_COMMUNICATION)
-            == Ci.nsIAudioManager.FORCE_SPEAKER);
+    return (gAudioManager.getForceForUse(nsIAudioManager.USE_COMMUNICATION) ==
+            nsIAudioManager.FORCE_SPEAKER);
   },
   set speakerEnabled(value) {
     if (value == this.speakerEnabled) {
       return;
     }
-    gAudioManager.phoneState = Ci.nsIAudioManager.PHONE_STATE_IN_CALL; // XXX why is this needed?
-    let force = value ? Ci.nsIAudioManager.FORCE_SPEAKER :
-                        Ci.nsIAudioManager.FORCE_NONE;
-    gAudioManager.setForceUse(Ci.nsIAudioManager.USE_COMMUNICATION, force);
+    gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL; // XXX why is this needed?
+    let force = value ? nsIAudioManager.FORCE_SPEAKER :
+                        nsIAudioManager.FORCE_NONE;
+    gAudioManager.setForceUse(nsIAudioManager.USE_COMMUNICATION, force);
   },
 
   getNumberOfMessagesForText: function getNumberOfMessagesForText(text) {
     //TODO: this assumes 7bit encoding, which is incorrect. Need to look
     // for characters not supported by 7bit alphabets and then calculate
     // length in UCS2 encoding.
     return Math.ceil(text.length / 160);
   },
 
   sendSMS: function sendSMS(number, message) {
     this.worker.postMessage({type: "sendSMS",
                              number: number,
                              body: message});
   },
 
   _callbacks: null,
+  _enumerationCallbacks: null,
 
   registerCallback: function registerCallback(callback) {
+    debug("Registering callback: " + callback);
+    if (this._callbacks) {
+      if (this._callbacks.indexOf(callback) != -1) {
+        throw new Error("Already registered this callback!");
+      }
+    } else {
+      this._callbacks = [];
+    }
     this._callbacks.push(callback);
   },
 
   unregisterCallback: function unregisterCallback(callback) {
-    let index = this._callbacks.indexOf(callback);
-    if (index == -1) {
-      throw "Callback not registered!";
+    debug("Unregistering callback: " + callback);
+    let index;
+    if (this._callbacks && (index = this._callbacks.indexOf(callback) != -1)) {
+      this._callbacks.splice(index, 1);
     }
-    this._callbacks.splice(index, 1);
+  },
+
+  enumerateCalls: function enumerateCalls(callback) {
+    debug("Requesting enumeration of calls for callback: " + callback);
+    this.worker.postMessage({type: "enumerateCalls"});
+    if (!this._enumerationCallbacks) {
+      this._enumerationCallbacks = [];
+    }
+    this._enumerationCallbacks.push(callback);
   },
 
+  _deliverCallback: function _deliverCallback(name, args) {
+    // We need to worry about callback registration state mutations during the
+    // callback firing. The behaviour we want is to *not* call any callbacks
+    // that are added during the firing and to *not* call any callbacks that are
+    // removed during the firing. To address this, we make a copy of the
+    // callback list before dispatching and then double-check that each callback
+    // is still registered before calling it.
+    if (!this._callbacks) {
+      return;
+    }
+    let callbacks = this._callbacks.slice();
+    for each (let callback in callbacks) {
+      if (this._callbacks.indexOf(callback) == -1) {
+        continue;
+      }
+      let handler = callback[name];
+      if (typeof handler != "function") {
+        throw new Error("No handler for " + name);
+      }
+      try {
+        handler.apply(callback, args);
+      } catch (e) {
+        debug("callback handler for " + name + " threw an exception: " + e);
+      }
+    }
+  },
 };
 
 const NSGetFactory = XPCOMUtils.generateNSGetFactory([nsTelephonyWorker]);
 
 let debug;
 if (DEBUG) {
   debug = function (s) {
     dump("-*- TelephonyWorker component: " + s + "\n");
--- a/dom/telephony/ril_consts.js
+++ b/dom/telephony/ril_consts.js
@@ -220,36 +220,16 @@ const DOM_RADIOSTATE_READY         = "re
 const DOM_CARDSTATE_UNAVAILABLE    = "unavailable";
 const DOM_CARDSTATE_ABSENT         = "absent";
 const DOM_CARDSTATE_PIN_REQUIRED   = "pin_required";
 const DOM_CARDSTATE_PUK_REQUIRED   = "puk_required";
 const DOM_CARDSTATE_NETWORK_LOCKED = "network_locked";
 const DOM_CARDSTATE_NOT_READY      = "not_ready";
 const DOM_CARDSTATE_READY          = "ready";
 
-const DOM_CALL_READYSTATE_DIALING        = "dialing";
-const DOM_CALL_READYSTATE_RINGING        = "ringing";
-const DOM_CALL_READYSTATE_BUSY           = "busy";
-const DOM_CALL_READYSTATE_CONNECTING     = "connecting";
-const DOM_CALL_READYSTATE_CONNECTED      = "connected";
-const DOM_CALL_READYSTATE_DISCONNECTING  = "disconnecting";
-const DOM_CALL_READYSTATE_DISCONNECTED   = "disconnected";
-const DOM_CALL_READYSTATE_INCOMING       = "incoming";
-const DOM_CALL_READYSTATE_HOLDING        = "holding";
-const DOM_CALL_READYSTATE_HELD           = "held";
-
-const RIL_TO_DOM_CALL_STATE = [
-  DOM_CALL_READYSTATE_CONNECTED, // CALL_READYSTATE_ACTIVE
-  DOM_CALL_READYSTATE_HELD,      // CALL_READYSTATE_HOLDING
-  DOM_CALL_READYSTATE_DIALING,   // CALL_READYSTATE_DIALING
-  DOM_CALL_READYSTATE_RINGING,   // CALL_READYSTATE_ALERTING
-  DOM_CALL_READYSTATE_INCOMING,  // CALL_READYSTATE_INCOMING
-  DOM_CALL_READYSTATE_HELD       // CALL_READYSTATE_WAITING (XXX is this right?)
-];
-
 
 /**
  * GSM PDU constants
  */
 
 // PDU TYPE-OF-ADDRESS
 const PDU_TOA_UNKNOWN       = 0x80; // Unknown. This is used when the user or
                                     // network has no a priori information
@@ -408,8 +388,12 @@ const PDU_ALPHABET_7BIT_DEFAULT = [
   "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
   "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
   "\xe4",   // LATIN SMALL LETTER A WITH DIAERESIS
   "\xf6",   // LATIN SMALL LETTER O WITH DIAERESIS
   "\xf1",   // LATIN SMALL LETTER N WITH TILDE
   "\xfc",   // LATIN SMALL LETTER U WITH DIAERESIS
   "\xe0"    // LATIN SMALL LETTER A WITH GRAVE
 ];
+
+
+// Allow this file to be imported via Components.utils.import().
+const EXPORTED_SYMBOLS = Object.keys(this);
--- a/dom/telephony/ril_worker.js
+++ b/dom/telephony/ril_worker.js
@@ -623,23 +623,23 @@ let RIL = {
     // match the format of the binary message.
     Buf.writeUint32(0);
     Buf.sendParcel();
   },
 
   /**
    * Hang up the phone.
    *
-   * @param index
+   * @param callIndex
    *        Call index (1-based) as reported by REQUEST_GET_CURRENT_CALLS.
    */
-  hangUp: function hangUp(index) {
+  hangUp: function hangUp(callIndex) {
     Buf.newParcel(REQUEST_HANGUP);
     Buf.writeUint32(1);
-    Buf.writeUint32(index);
+    Buf.writeUint32(callIndex);
     Buf.sendParcel();
   },
 
   /**
    * Mute or unmute the radio.
    *
    * @param mute
    *        Boolean to indicate whether to mute or unmute the radio.
@@ -814,17 +814,17 @@ RIL[REQUEST_GET_CURRENT_CALLS] = functio
     Phone.onCurrentCalls(null);
     return;
   }
 
   let calls = {};
   for (let i = 0; i < calls_length; i++) {
     let call = {
       state:              Buf.readUint32(), // CALL_STATE_*
-      index:              Buf.readUint32(), // GSM index (1-based)
+      callIndex:          Buf.readUint32(), // GSM index (1-based)
       toa:                Buf.readUint32(),
       isMpty:             Boolean(Buf.readUint32()),
       isMT:               Boolean(Buf.readUint32()),
       als:                Buf.readUint32(),
       isVoice:            Boolean(Buf.readUint32()),
       isVoicePrivacy:     Boolean(Buf.readUint32()),
       somethingOrOther:   Buf.readUint32(), //XXX TODO whatziz? not in ril.h, but it's in the output...
       number:             Buf.readString(), //TODO munge with TOA
@@ -836,17 +836,17 @@ RIL[REQUEST_GET_CURRENT_CALLS] = functio
     let uusInfoPresent = Buf.readUint32();
     if (uusInfoPresent == 1) {
       call.uusInfo = {
         type:     Buf.readUint32(),
         dcs:      Buf.readUint32(),
         userData: null //XXX TODO byte array?!?
       };
     }
-    calls[call.index] = call;
+    calls[call.callIndex] = call;
   }
   Phone.onCurrentCalls(calls);
 };
 RIL[REQUEST_DIAL] = function REQUEST_DIAL(length) {
   Phone.onDial();
 };
 RIL[REQUEST_GET_IMSI] = function REQUEST_GET_IMSI(length) {
   let imsi = Buf.readString();
@@ -1118,16 +1118,48 @@ let Phone = {
   iccStatus: null,
 
   /**
    * Active calls
    */
   currentCalls: {},
 
   /**
+   * Mute or unmute the radio.
+   */
+  _muted: true,
+
+  get muted() {
+    return this._muted;
+  },
+
+  set muted(val) {
+    val = Boolean(val);
+    if (this._muted != val) {
+      RIL.setMute(val);
+      this._muted = val;
+    }
+  },
+
+  _handleChangedCallState: function _handleChangedCallState(changedCall) {
+    let message = {type: "callStateChange",
+                   call: {callIndex: changedCall.callIndex,
+                          state: changedCall.state,
+                          number: changedCall.number,
+                          name: changedCall.name}};
+    this.sendDOMMessage(message);
+  },
+
+  _handleDisconnectedCall: function _handleDisconnectedCall(disconnectedCall) {
+    let message = {type: "callDisconnected",
+                   call: {callIndex: disconnectedCall.callIndex}};
+    this.sendDOMMessage(message);
+  },
+
+  /**
    * Handlers for messages from the RIL. They all begin with on* and are called
    * from RIL object.
    */
 
   onRadioStateChanged: function onRadioStateChanged(newState) {
     debug("Radio state changed from " + this.radioState + " to " + newState);
     if (this.radioState == newState) {
       // No change in state, return.
@@ -1213,63 +1245,56 @@ let Phone = {
     this.radioState = newState;
   },
 
   onCurrentCalls: function onCurrentCalls(newCalls) {
     // Go through the calls we currently have on file and see if any of them
     // changed state. Remove them from the newCalls map as we deal with them
     // so that only new calls remain in the map after we're done.
     for each (let currentCall in this.currentCalls) {
-      let callIndex = currentCall.index;
       let newCall;
       if (newCalls) {
-        newCall = newCalls[callIndex];
-        delete newCalls[callIndex];
+        newCall = newCalls[currentCall.callIndex];
+        delete newCalls[currentCall.callIndex];
       }
 
-      if (!newCall) {
-        // Call is no longer reported by the radio. Send disconnected
-        // state change.
-        this.sendDOMMessage({type:      "callstatechange",
-                             callState:  DOM_CALL_READYSTATE_DISCONNECTED,
-                             callIndex:  callIndex,
-                             number:     currentCall.number,
-                             name:       currentCall.name});
-        delete this.currentCalls[callIndex];
-        continue;
+      if (newCall) {
+        // Call is still valid.
+        if (newCall.state != currentCall.state) {
+          // State has changed.
+          currentCall.state = newCall.state;
+          this._handleChangedCallState(currentCall);
+        }
       }
-
-      if (newCall.state == currentCall.state) {
-        continue;
+      else {
+        // Call is no longer reported by the radio. Remove from our map and
+        // send disconnected state change.
+        delete this.currentCalls[currentCall.callIndex];
+        this._handleDisconnectedCall(currentCall);
       }
-
-      this._handleChangedCallState(newCall);
     }
 
     // Go through any remaining calls that are new to us.
     for each (let newCall in newCalls) {
       if (newCall.isVoice) {
+        // Format international numbers appropriately.
+        if (newCall.number &&
+            newCall.toa == TOA_INTERNATIONAL &&
+            newCall.number[0] != "+") {
+          newCall.number = "+" + newCall.number;
+        }
+        // Add to our map.
+        this.currentCalls[newCall.callIndex] = newCall;
         this._handleChangedCallState(newCall);
       }
     }
-  },
 
-  _handleChangedCallState: function handleChangedCallState(newCall) {
-    // Format international numbers appropriately.
-    if (newCall.number &&
-        newCall.toa == TOA_INTERNATIONAL &&
-        newCall.number[0] != "+") {
-      newCall.number = "+" + newCall.number;
-    }
-    this.currentCalls[newCall.index] = newCall;
-    this.sendDOMMessage({type:      "callstatechange",
-                         callState: RIL_TO_DOM_CALL_STATE[newCall.state],
-                         callIndex: newCall.index,
-                         number:    newCall.number,
-                         name:      newCall.name});
+    // Update our mute status. If there is anything in our currentCalls map then
+    // we know it's a voice call and we should leave audio on.
+    this.muted = Object.getOwnPropertyNames(this.currentCalls).length == 0;
   },
 
   onCallStateChanged: function onCallStateChanged() {
     RIL.getCurrentCalls();
   },
 
   onCallRing: function onCallRing(info) {
     // For now we don't need to do anything here because we'll also get a
@@ -1443,16 +1468,28 @@ let Phone = {
     if (DEBUG) debug("Requesting phone state");
     RIL.getRegistrationState();
     RIL.getGPRSRegistrationState(); //TODO only GSM
     RIL.getOperator();
     RIL.getNetworkSelectionMode();
   },
 
   /**
+   * Get a list of current voice calls.
+   */
+  enumerateCalls: function enumerateCalls() {
+    if (DEBUG) debug("Sending all current calls");
+    let calls = [];
+    for each (let call in this.currentCalls) {
+      calls.push(call);
+    }
+    this.sendDOMMessage({type: "enumerateCalls", calls: calls});
+  },
+
+  /**
    * Dial the phone.
    *
    * @param number
    *        String containing the number to dial.
    */
   dial: function dial(options) {
     RIL.dial(options.number, 0, 0);
   },
@@ -1492,39 +1529,47 @@ let Phone = {
    */
   hangUp: function hangUp(options) {
     //TODO need to check whether call is holding/waiting/background
     // and then use REQUEST_HANGUP_WAITING_OR_BACKGROUND
     RIL.hangUp(options.callIndex);
   },
 
   /**
-   * Mute or unmute the radio.
+   * Answer an incoming call.
    *
-   * @param mute
-   *        Boolean to indicate whether to mute or unmute the radio.
+   * @param callIndex
+   *        Call index of the call to answer.
    */
-  setMute: function setMute(options) {
-    //TODO need to check whether call is holding/waiting/background
-    // and then use REQUEST_HANGUP_WAITING_OR_BACKGROUND
-    RIL.setMute(options.mute);
-  },
-
-  /**
-   * Answer an incoming call.
-   */
-  answerCall: function answerCall() {
-    RIL.answerCall();
+  answerCall: function answerCall(options) {
+    // Check for races. Since we dispatched the incoming call notification the
+    // incoming call may have changed. The main thread thinks that it is
+    // answering the call with the given index, so only answer if that is still
+    // incoming.
+    let call = this.currentCalls[options.callIndex];
+    if (call && call.state == CALL_STATE_INCOMING) {
+      RIL.answerCall();
+    }
   },
 
   /**
    * Reject an incoming call.
+   *
+   * @param callIndex
+   *        Call index of the call to reject.
    */
-  rejectCall: function rejectCall() {
-    RIL.rejectCall();
+  rejectCall: function rejectCall(options) {
+    // Check for races. Since we dispatched the incoming call notification the
+    // incoming call may have changed. The main thread thinks that it is
+    // rejecting the call with the given index, so only reject if that is still
+    // incoming.
+    let call = this.currentCalls[options.callIndex];
+    if (call && call.state == CALL_STATE_INCOMING) {
+      RIL.rejectCall();
+    }
   },
 
   /**
    * Send an SMS.
    *
    * @param number
    *        String containing the recipient number.
    * @param body
--- a/js/xpconnect/src/dom_quickstubs.qsconf
+++ b/js/xpconnect/src/dom_quickstubs.qsconf
@@ -499,16 +499,18 @@ irregularFilenames = {
     'nsIWebGLExtension': 'nsIDOMWebGLRenderingContext',
     'nsIWebGLExtensionStandardDerivatives' : 'nsIDOMWebGLRenderingContext',
     'nsIWebGLExtensionLoseContext' : 'nsIDOMWebGLRenderingContext',
 
     'nsIIndexedDatabaseUsageCallback': 'nsIIndexedDatabaseManager',
 
     'nsIDOMTouch': 'nsIDOMTouchEvent',
     'nsIDOMTouchList': 'nsIDOMTouchEvent',
+
+    'nsITelephoneCallback': 'nsITelephone',
     }
 
 customIncludes = [
     'nsINode.h',
     'nsIContent.h',
     'nsIDocument.h',
     'nsINodeList.h',
     'nsCSSPropertiesQS.h',