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 bac673bc7211
parent 84062 1927c7905f5e
child 84064 b575f4ce9273
push id21820
push usermak77@bonardo.net
push dateTue, 10 Jan 2012 09:03:21 +0000
treeherdermozilla-central@01d69766026d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersphilikon, sicking
bugs674726
milestone12.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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',