Bug 998872 - [Stingray] TV Manager API. Part 1 - DOM API & WebIDL bindings. r=ehsan, r=baku
authorSean Lin <selin@mozilla.com>
Thu, 21 Aug 2014 17:15:18 +0800
changeset 237183 0dc06ccc5ea24393ce9e7ca30f8e3e3ef6667a6e
parent 237182 236c6e9e724e121f38bd7b2711521d92fcd1271b
child 237184 1593d48890a0abfbb6ee7a1f49ba2fb7c88ebcc0
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan, baku
bugs998872
milestone36.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 998872 - [Stingray] TV Manager API. Part 1 - DOM API & WebIDL bindings. r=ehsan, r=baku
b2g/app/b2g.js
dom/apps/PermissionsTable.jsm
dom/base/Navigator.cpp
dom/base/Navigator.h
dom/base/nsGkAtomList.h
dom/events/test/test_all_synthetic_events.html
dom/moz.build
dom/tests/mochitest/general/test_interfaces.html
dom/tv/TVChannel.cpp
dom/tv/TVChannel.h
dom/tv/TVManager.cpp
dom/tv/TVManager.h
dom/tv/TVProgram.cpp
dom/tv/TVProgram.h
dom/tv/TVSource.cpp
dom/tv/TVSource.h
dom/tv/TVTuner.cpp
dom/tv/TVTuner.h
dom/tv/moz.build
dom/tv/test/file_app.sjs
dom/tv/test/file_app.template.webapp
dom/tv/test/file_tv_non_permitted_app.html
dom/tv/test/mochitest.ini
dom/tv/test/moz.build
dom/tv/test/test_tv_non_permitted_app.html
dom/webidl/Navigator.webidl
dom/webidl/TVChannel.webidl
dom/webidl/TVCurrentChannelChangedEvent.webidl
dom/webidl/TVCurrentSourceChangedEvent.webidl
dom/webidl/TVEITBroadcastedEvent.webidl
dom/webidl/TVManager.webidl
dom/webidl/TVProgram.webidl
dom/webidl/TVScanningStateChangedEvent.webidl
dom/webidl/TVSource.webidl
dom/webidl/TVTuner.webidl
dom/webidl/moz.build
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1037,8 +1037,11 @@ pref("services.mobileid.server.uri", "ht
 
 // Enable mapped array buffer.
 #ifndef XP_WIN
 pref("dom.mapped_arraybuffer.enabled", true);
 #endif
 
 // UDPSocket API
 pref("dom.udpsocket.enabled", true);
+
+// Enable TV Manager API
+pref("dom.tv.enabled", true);
--- a/dom/apps/PermissionsTable.jsm
+++ b/dom/apps/PermissionsTable.jsm
@@ -488,16 +488,21 @@ this.PermissionsTable =  { geolocation: 
                              access: ["read", "write"],
                              additional: ["settings-api"]
                            },
                            "engineering-mode": {
                              app: DENY_ACTION,
                              trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
+                           },
+                           "tv": {
+                             app: DENY_ACTION,
+                             privileged: DENY_ACTION,
+                             certified: ALLOW_ACTION
                            }
                          };
 
 /**
  * Append access modes to the permission name as suffixes.
  *   e.g. permission name 'contacts' with ['read', 'write'] =
  *   ['contacts-read', contacts-write']
  * @param string aPermName
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -34,16 +34,17 @@
 #include "mozilla/dom/PowerManager.h"
 #include "mozilla/dom/WakeLock.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/CellBroadcast.h"
 #include "mozilla/dom/MobileMessageManager.h"
 #include "mozilla/dom/ServiceWorkerContainer.h"
 #include "mozilla/dom/Telephony.h"
 #include "mozilla/dom/Voicemail.h"
+#include "mozilla/dom/TVManager.h"
 #include "mozilla/Hal.h"
 #include "nsISiteSpecificUserAgent.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/StaticPtr.h"
 #include "Connection.h"
 #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
 #include "nsGlobalWindow.h"
 #ifdef MOZ_B2G
@@ -167,16 +168,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGeolocation)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotification)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPowerManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCellBroadcast)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMobileMessageManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTelephony)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVoicemail)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTVManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConnection)
 #ifdef MOZ_B2G_RIL
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMobileConnections)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIccManager)
 #endif
 #ifdef MOZ_B2G_BT
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBluetooth)
 #endif
@@ -250,16 +252,20 @@ Navigator::Invalidate()
     mTelephony = nullptr;
   }
 
   if (mVoicemail) {
     mVoicemail->Shutdown();
     mVoicemail = nullptr;
   }
 
+  if (mTVManager) {
+    mTVManager = nullptr;
+  }
+
   if (mConnection) {
     mConnection->Shutdown();
     mConnection = nullptr;
   }
 
 #ifdef MOZ_B2G_RIL
   if (mMobileConnections) {
     mMobileConnections = nullptr;
@@ -1558,16 +1564,29 @@ Navigator::GetMozTelephony(ErrorResult& 
       return nullptr;
     }
     mTelephony = Telephony::Create(mWindow, aRv);
   }
 
   return mTelephony;
 }
 
+TVManager*
+Navigator::GetTv()
+{
+  if (!mTVManager) {
+    if (!mWindow) {
+      return nullptr;
+    }
+    mTVManager = new TVManager(mWindow);
+  }
+
+  return mTVManager;
+}
+
 #ifdef MOZ_B2G
 already_AddRefed<Promise>
 Navigator::GetMobileIdAssertion(const MobileIdOptions& aOptions,
                                 ErrorResult& aRv)
 {
   if (!mWindow || !mWindow->GetDocShell()) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
@@ -2303,16 +2322,47 @@ Navigator::HasMobileIdSupport(JSContext*
   uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
   permMgr->TestPermissionFromPrincipal(principal, "mobileid", &permission);
   return permission == nsIPermissionManager::PROMPT_ACTION ||
          permission == nsIPermissionManager::ALLOW_ACTION;
 }
 #endif
 
 /* static */
+bool
+Navigator::HasTVSupport(JSContext* aCx, JSObject* aGlobal)
+{
+  JS::Rooted<JSObject*> global(aCx, aGlobal);
+
+  nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(global);
+  if (!win) {
+    return false;
+  }
+
+  // Just for testing, we can enable TV for any kind of app.
+  if (Preferences::GetBool("dom.testing.tv_enabled_for_hosted_apps", false)) {
+    return true;
+  }
+
+  nsIDocument* doc = win->GetExtantDoc();
+  if (!doc || !doc->NodePrincipal()) {
+    return false;
+  }
+
+  nsIPrincipal* principal = doc->NodePrincipal();
+  uint16_t status;
+  if (NS_FAILED(principal->GetAppStatus(&status))) {
+    return false;
+  }
+
+  // Only support TV Manager API for certified apps for now.
+  return status == nsIPrincipal::APP_STATUS_CERTIFIED;
+}
+
+/* static */
 already_AddRefed<nsPIDOMWindow>
 Navigator::GetWindowFromGlobal(JSObject* aGlobal)
 {
   nsCOMPtr<nsPIDOMWindow> win =
     do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(aGlobal));
   MOZ_ASSERT(!win || win->IsInnerWindow());
   return win.forget();
 }
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -87,16 +87,17 @@ class BluetoothManager;
 class IccManager;
 class MobileConnectionArray;
 #endif
 
 class PowerManager;
 class CellBroadcast;
 class Telephony;
 class Voicemail;
+class TVManager;
 
 namespace time {
 class TimeManager;
 } // namespace time
 
 namespace system {
 #ifdef MOZ_AUDIO_CHANNEL_MANAGER
 class AudioChannelManager;
@@ -217,16 +218,17 @@ public:
   void GetDeviceStorages(const nsAString& aType,
                          nsTArray<nsRefPtr<nsDOMDeviceStorage> >& aStores,
                          ErrorResult& aRv);
   DesktopNotificationCenter* GetMozNotification(ErrorResult& aRv);
   CellBroadcast* GetMozCellBroadcast(ErrorResult& aRv);
   MobileMessageManager* GetMozMobileMessage();
   Telephony* GetMozTelephony(ErrorResult& aRv);
   Voicemail* GetMozVoicemail(ErrorResult& aRv);
+  TVManager* GetTv();
   network::Connection* GetConnection(ErrorResult& aRv);
   nsDOMCameraManager* GetMozCameras(ErrorResult& aRv);
   void MozSetMessageHandler(const nsAString& aType,
                             systemMessageCallback* aCallback,
                             ErrorResult& aRv);
   bool MozHasPendingMessage(const nsAString& aType, ErrorResult& aRv);
 #ifdef MOZ_B2G
   already_AddRefed<Promise> GetMobileIdAssertion(const MobileIdOptions& options,
@@ -298,16 +300,18 @@ public:
   static bool HasDataStoreSupport(nsIPrincipal* aPrincipal);
 
   static bool HasDataStoreSupport(JSContext* cx, JSObject* aGlobal);
 
 #ifdef MOZ_B2G
   static bool HasMobileIdSupport(JSContext* aCx, JSObject* aGlobal);
 #endif
 
+  static bool HasTVSupport(JSContext* aCx, JSObject* aGlobal);
+
   nsPIDOMWindow* GetParentObject() const
   {
     return GetWindow();
   }
 
   virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE;
 
 private:
@@ -327,16 +331,17 @@ private:
 #ifdef MOZ_B2G_FM
   nsRefPtr<FMRadio> mFMRadio;
 #endif
   nsRefPtr<PowerManager> mPowerManager;
   nsRefPtr<CellBroadcast> mCellBroadcast;
   nsRefPtr<MobileMessageManager> mMobileMessageManager;
   nsRefPtr<Telephony> mTelephony;
   nsRefPtr<Voicemail> mVoicemail;
+  nsRefPtr<TVManager> mTVManager;
   nsRefPtr<network::Connection> mConnection;
 #ifdef MOZ_B2G_RIL
   nsRefPtr<MobileConnectionArray> mMobileConnections;
   nsRefPtr<IccManager> mIccManager;
 #endif
 #ifdef MOZ_B2G_BT
   nsRefPtr<bluetooth::BluetoothManager> mBluetooth;
 #endif
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -702,16 +702,18 @@ GK_ATOM(oncompositionend, "oncomposition
 GK_ATOM(oncompositionstart, "oncompositionstart")
 GK_ATOM(oncompositionupdate, "oncompositionupdate")
 GK_ATOM(onconfigurationchange, "onconfigurationchange")
 GK_ATOM(onconnect, "onconnect")
 GK_ATOM(onconnected, "onconnected")
 GK_ATOM(onconnecting, "onconnecting")
 GK_ATOM(oncontextmenu, "oncontextmenu")
 GK_ATOM(oncopy, "oncopy")
+GK_ATOM(oncurrentchannelchanged, "oncurrentchannelchanged")
+GK_ATOM(oncurrentsourcechanged, "oncurrentsourcechanged")
 GK_ATOM(oncut, "oncut")
 GK_ATOM(ondatachange, "ondatachange")
 GK_ATOM(ondataerror, "ondataerror")
 GK_ATOM(ondblclick, "ondblclick")
 GK_ATOM(ondeleted, "ondeleted")
 GK_ATOM(ondeliverysuccess, "ondeliverysuccess")
 GK_ATOM(ondeliveryerror, "ondeliveryerror")
 GK_ATOM(ondevicefound, "ondevicefound")
@@ -741,16 +743,17 @@ GK_ATOM(ondragdrop, "ondragdrop")
 GK_ATOM(ondragend, "ondragend")
 GK_ATOM(ondragenter, "ondragenter")
 GK_ATOM(ondragexit, "ondragexit")
 GK_ATOM(ondraggesture, "ondraggesture")
 GK_ATOM(ondragleave, "ondragleave")
 GK_ATOM(ondragover, "ondragover")
 GK_ATOM(ondragstart, "ondragstart")
 GK_ATOM(ondrop, "ondrop")
+GK_ATOM(oneitbroadcasted, "oneitbroadcasted")
 GK_ATOM(onenabled, "onenabled")
 GK_ATOM(onenterpincodereq, "onenterpincodereq")
 GK_ATOM(onemergencycbmodechange, "onemergencycbmodechange")
 GK_ATOM(onerror, "onerror")
 GK_ATOM(onevicted, "onevicted")
 GK_ATOM(onfacesdetected, "onfacesdetected")
 GK_ATOM(onfailed, "onfailed")
 GK_ATOM(onfetch, "onfetch")
@@ -843,16 +846,17 @@ GK_ATOM(onremoteresumed, "onremoteresume
 GK_ATOM(onresourcetimingbufferfull, "onresourcetimingbufferfull")
 GK_ATOM(onretrieving, "onretrieving")
 GK_ATOM(onRequest, "onRequest")
 GK_ATOM(onrequestmediaplaystatus, "onrequestmediaplaystatus")
 GK_ATOM(onreset, "onreset")
 GK_ATOM(onresuming, "onresuming")
 GK_ATOM(onresize, "onresize")
 GK_ATOM(onrtchange, "onrtchange")
+GK_ATOM(onscanningstatechanged, "onscanningstatechanged")
 GK_ATOM(onscostatuschanged, "onscostatuschanged")
 GK_ATOM(onscroll, "onscroll")
 GK_ATOM(onselect, "onselect")
 GK_ATOM(onsending, "onsending")
 GK_ATOM(onsent, "onsent")
 GK_ATOM(onset, "onset")
 GK_ATOM(onshow, "onshow")
 GK_ATOM(onshutter, "onshutter")
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -467,16 +467,48 @@ const kEventConstructors = {
   TrackEvent:                                { create: function (aName, aProps) {
                                                          return new TrackEvent(aName, aProps);
                                                        },
                                              },
   TransitionEvent:                           { create: function (aName, aProps) {
                                                          return new TransitionEvent(aName, aProps);
                                                        },
                                              },
+  TVCurrentChannelChangedEvent:              { create: function (aName, aProps) {
+                                                         return new TVCurrentChannelChangedEvent(aName, aProps);
+                                                       },
+                                             },
+  TVCurrentProgramChangedEvent:              { create: function (aName, aProps) {
+                                                         return new TVCurrentProgramChangedEvent(aName, aProps);
+                                                       },
+                                             },
+  TVCurrentSourceChangedEvent:               { create: function (aName, aProps) {
+                                                         return new TVCurrentSourceChangedEvent(aName, aProps);
+                                                       },
+                                             },
+  TVEITBroadcastedEvent:                     { create: function (aName, aProps) {
+                                                         return new TVEITBroadcastedEvent(aName, aProps);
+                                                       },
+                                             },
+  TVParentalControlChangedEvent:             { create: function (aName, aProps) {
+                                                         return new TVParentalControlChangedEvent(aName, aProps);
+                                                       },
+                                             },
+  TVProtectionStateChangedEvent:             { create: function (aName, aProps) {
+                                                         return new TVProtectionStateChangedEvent(aName, aProps);
+                                                       },
+                                             },
+  TVScanningStateChangedEvent:               { create: function (aName, aProps) {
+                                                         return new TVScanningStateChangedEvent(aName, aProps);
+                                                       },
+                                             },
+  TVTunerChangedEvent:                       { create: function (aName, aProps) {
+                                                         return new TVTunerChangedEvent(aName, aProps);
+                                                       },
+                                             },
   UIEvent:                                   { create: function (aName, aProps) {
                                                          return new UIEvent(aName, aProps);
                                                        },
                                              },
   UserProximityEvent:                        { create: function (aName, aProps) {
                                                          return new UserProximityEvent(aName, aProps);
                                                        },
                                              },
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -88,16 +88,17 @@ DIRS += [
     'ipc',
     'identity',
     'workers',
     'camera',
     'audiochannel',
     'promise',
     'smil',
     'telephony',
+    'tv',
     'voicemail',
     'inputmethod',
     'webidl',
     'xbl',
     'xml',
     'xslt',
     'xul',
     'resourcestats',
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -1227,16 +1227,34 @@ var interfaceNamesInGlobalScope =
     {name: "TreeColumns", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "TreeContentView", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "TreeSelection", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "TreeWalker",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVChannel", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVCurrentChannelChangedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVCurrentSourceChangedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVEITBroadcastedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVManager", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVProgram", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVScanningStateChangedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVSource", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "TVTuner", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "UDPMessageEvent", pref: "dom.udpsocket.enabled", permission: "udp-socket"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "UDPSocket", pref: "dom.udpsocket.enabled", permission: "udp-socket"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "UIEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "UndoManager",
 // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVChannel.cpp
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Promise.h"
+#include "TVChannel.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(TVChannel)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TVChannel,
+                                                  DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TVChannel,
+                                                DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(TVChannel, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(TVChannel, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TVChannel)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+TVChannel::TVChannel(nsPIDOMWindow* aWindow)
+  : DOMEventTargetHelper(aWindow)
+{
+}
+
+TVChannel::~TVChannel()
+{
+}
+
+/* virtual */ JSObject*
+TVChannel::WrapObject(JSContext* aCx)
+{
+  return TVChannelBinding::Wrap(aCx, this);
+}
+
+already_AddRefed<Promise>
+TVChannel::GetPrograms(const TVGetProgramsOptions& aOptions, ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  MOZ_ASSERT(global);
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  // TODO Resolve/reject the promise in follow-up patches.
+
+  return promise.forget();
+}
+
+void
+TVChannel::GetNetworkId(nsAString& aNetworkId) const
+{
+  // TODO Implement in follow-up patches.
+}
+
+void
+TVChannel::GetTransportStreamId(nsAString& aTransportStreamId) const
+{
+  // TODO Implement in follow-up patches.
+}
+
+void
+TVChannel::GetServiceId(nsAString& aServiceId) const
+{
+  // TODO Implement in follow-up patches.
+}
+
+already_AddRefed<TVSource>
+TVChannel::Source() const
+{
+  // TODO Implement in follow-up patches.
+  return nullptr;
+}
+
+TVChannelType
+TVChannel::Type() const
+{
+  // TODO Implement in follow-up patches.
+  return TVChannelType::Tv;
+}
+
+void
+TVChannel::GetName(nsAString& aName) const
+{
+  // TODO Implement in follow-up patches.
+}
+
+void
+TVChannel::GetNumber(nsAString& aNumber) const
+{
+  // TODO Implement in follow-up patches.
+}
+
+bool
+TVChannel::IsEmergency() const
+{
+  // TODO Implement in follow-up patches.
+  return false;
+}
+
+bool
+TVChannel::IsFree() const
+{
+  // TODO Implement in follow-up patches.
+  return false;
+}
+
+already_AddRefed<Promise>
+TVChannel::GetCurrentProgram(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  MOZ_ASSERT(global);
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  // TODO Resolve/reject the promise in follow-up patches.
+
+  return promise.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVChannel.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TVChannel_h__
+#define mozilla_dom_TVChannel_h__
+
+#include "mozilla/DOMEventTargetHelper.h"
+// Include TVChannelBinding.h since enum TVChannelType can't be forward declared.
+#include "mozilla/dom/TVChannelBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+class TVProgram;
+class TVSource;
+
+class TVChannel MOZ_FINAL : public DOMEventTargetHelper
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TVChannel, DOMEventTargetHelper)
+
+  explicit TVChannel(nsPIDOMWindow* aWindow);
+
+  // WebIDL (internal functions)
+
+  virtual JSObject* WrapObject(JSContext *aCx) MOZ_OVERRIDE;
+
+  // WebIDL (public APIs)
+
+  already_AddRefed<Promise> GetPrograms(const TVGetProgramsOptions& aOptions,
+                                        ErrorResult& aRv);
+
+  already_AddRefed<Promise> GetCurrentProgram(ErrorResult& aRv);
+
+  void GetNetworkId(nsAString& aNetworkId) const;
+
+  void GetTransportStreamId(nsAString& aTransportStreamId) const;
+
+  void GetServiceId(nsAString& aServiceId) const;
+
+  already_AddRefed<TVSource> Source() const;
+
+  TVChannelType Type() const;
+
+  void GetName(nsAString& aName) const;
+
+  void GetNumber(nsAString& aNumber) const;
+
+  bool IsEmergency() const;
+
+  bool IsFree() const;
+
+private:
+  ~TVChannel();
+
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TVChannel_h__
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVManager.cpp
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/TVManagerBinding.h"
+#include "TVManager.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(TVManager)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TVManager,
+                                                  DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TVManager,
+                                                DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(TVManager, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(TVManager, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TVManager)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+TVManager::TVManager(nsPIDOMWindow* aWindow)
+  : DOMEventTargetHelper(aWindow)
+{
+}
+
+TVManager::~TVManager()
+{
+}
+
+/* virtual */ JSObject*
+TVManager::WrapObject(JSContext* aCx)
+{
+  return TVManagerBinding::Wrap(aCx, this);
+}
+
+already_AddRefed<Promise>
+TVManager::GetTuners(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  MOZ_ASSERT(global);
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  // TODO Resolve/reject the promise in follow-up patches.
+
+  return promise.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVManager.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TVManager_h__
+#define mozilla_dom_TVManager_h__
+
+#include "mozilla/DOMEventTargetHelper.h"
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+
+class TVManager MOZ_FINAL : public DOMEventTargetHelper
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TVManager, DOMEventTargetHelper)
+
+  explicit TVManager(nsPIDOMWindow* aWindow);
+
+  // WebIDL (internal functions)
+
+  virtual JSObject* WrapObject(JSContext *aCx) MOZ_OVERRIDE;
+
+  // WebIDL (public APIs)
+
+  already_AddRefed<Promise> GetTuners(ErrorResult& aRv);
+
+private:
+  ~TVManager();
+
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TVManager_h__
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVProgram.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/TVProgramBinding.h"
+#include "TVProgram.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TVProgram, mOwner)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TVProgram)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TVProgram)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TVProgram)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TVProgram::TVProgram(nsISupports* aOwner)
+  : mOwner(aOwner)
+{
+}
+
+TVProgram::~TVProgram()
+{
+}
+
+/* virtual */ JSObject*
+TVProgram::WrapObject(JSContext* aCx)
+{
+  return TVProgramBinding::Wrap(aCx, this);
+}
+
+void
+TVProgram::GetAudioLanguages(nsTArray<nsString>& aLanguages) const
+{
+  // TODO Implement in follow-up patches.
+}
+
+void
+TVProgram::GetSubtitleLanguages(nsTArray<nsString>& aLanguages) const
+{
+  // TODO Implement in follow-up patches.
+}
+
+void
+TVProgram::GetEventId(nsAString& aEventId) const
+{
+  // TODO Implement in follow-up patches.
+}
+
+already_AddRefed<TVChannel>
+TVProgram::Channel() const
+{
+  // TODO Implement in follow-up patches.
+  return nullptr;
+}
+
+void
+TVProgram::GetTitle(nsAString& aTitle) const
+{
+  // TODO Implement in follow-up patches.
+}
+
+uint64_t
+TVProgram::StartTime() const
+{
+  // TODO Implement in follow-up patches.
+  return 0;
+}
+
+uint64_t
+TVProgram::Duration() const
+{
+  // TODO Implement in follow-up patches.
+  return 0;
+}
+
+void
+TVProgram::GetDescription(nsAString& aDescription) const
+{
+  // TODO Implement in follow-up patches.
+}
+
+void
+TVProgram::GetRating(nsAString& aRating) const
+{
+  // TODO Implement in follow-up patches.
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVProgram.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TVProgram_h__
+#define mozilla_dom_TVProgram_h__
+
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class TVChannel;
+
+class TVProgram MOZ_FINAL : public nsISupports
+                          , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TVProgram)
+
+  explicit TVProgram(nsISupports* aOwner);
+
+  // WebIDL (internal functions)
+
+  nsISupports* GetParentObject() const
+  {
+    return mOwner;
+  }
+
+  virtual JSObject* WrapObject(JSContext *aCx) MOZ_OVERRIDE;
+
+  // WebIDL (public APIs)
+
+  void GetAudioLanguages(nsTArray<nsString>& aLanguages) const;
+
+  void GetSubtitleLanguages(nsTArray<nsString>& aLanguages) const;
+
+  void GetEventId(nsAString& aEventId) const;
+
+  already_AddRefed<TVChannel> Channel() const;
+
+  void GetTitle(nsAString& aTitle) const;
+
+  uint64_t StartTime() const;
+
+  uint64_t Duration() const;
+
+  void GetDescription(nsAString& aDescription) const;
+
+  void GetRating(nsAString& aRating) const;
+
+private:
+  ~TVProgram();
+
+  nsCOMPtr<nsISupports> mOwner;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TVProgram_h__
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVSource.cpp
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Promise.h"
+#include "TVSource.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(TVSource)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TVSource,
+                                                  DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TVSource,
+                                                DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(TVSource, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(TVSource, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TVSource)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+TVSource::TVSource(nsPIDOMWindow* aWindow)
+  : DOMEventTargetHelper(aWindow)
+{
+}
+
+TVSource::~TVSource()
+{
+}
+
+/* virtual */ JSObject*
+TVSource::WrapObject(JSContext* aCx)
+{
+  return TVSourceBinding::Wrap(aCx, this);
+}
+
+already_AddRefed<Promise>
+TVSource::GetChannels(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  MOZ_ASSERT(global);
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  // TODO Resolve/reject the promise in follow-up patches.
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+TVSource::SetCurrentChannel(const nsAString& aChannelNumber, ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  MOZ_ASSERT(global);
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  // TODO Resolve/reject the promise in follow-up patches.
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+TVSource::StartScanning(const TVStartScanningOptions& aOptions,
+                        ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  MOZ_ASSERT(global);
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  // TODO Resolve/reject the promise in follow-up patches.
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+TVSource::StopScanning(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  MOZ_ASSERT(global);
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  // TODO Resolve/reject the promise in follow-up patches.
+
+  return promise.forget();
+}
+
+already_AddRefed<TVTuner>
+TVSource::Tuner() const
+{
+  // TODO Implement in follow-up patches.
+  return nullptr;
+}
+
+TVSourceType
+TVSource::Type() const
+{
+  // TODO Implement in follow-up patches.
+  return TVSourceType::Dvb_t;
+}
+
+bool
+TVSource::IsScanning() const
+{
+  // TODO Implement in follow-up patches.
+  return false;
+}
+
+already_AddRefed<TVChannel>
+TVSource::GetCurrentChannel() const
+{
+  // TODO Implement in follow-up patches.
+  return nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVSource.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TVSource_h__
+#define mozilla_dom_TVSource_h__
+
+#include "mozilla/DOMEventTargetHelper.h"
+// Include TVSourceBinding.h since enum TVSourceType can't be forward declared.
+#include "mozilla/dom/TVSourceBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+class TVChannel;
+class TVTuner;
+
+class TVSource MOZ_FINAL : public DOMEventTargetHelper
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TVSource, DOMEventTargetHelper)
+
+  explicit TVSource(nsPIDOMWindow* aWindow);
+
+  // WebIDL (internal functions)
+
+  virtual JSObject* WrapObject(JSContext *aCx) MOZ_OVERRIDE;
+
+  // WebIDL (public APIs)
+
+  already_AddRefed<Promise> GetChannels(ErrorResult& aRv);
+
+  already_AddRefed<Promise> SetCurrentChannel(const nsAString& aChannelNumber,
+                                              ErrorResult& aRv);
+
+  already_AddRefed<Promise> StartScanning(const TVStartScanningOptions& aOptions,
+                                          ErrorResult& aRv);
+
+  already_AddRefed<Promise> StopScanning(ErrorResult& aRv);
+
+  already_AddRefed<TVTuner> Tuner() const;
+
+  TVSourceType Type() const;
+
+  bool IsScanning() const;
+
+  already_AddRefed<TVChannel> GetCurrentChannel() const;
+
+  IMPL_EVENT_HANDLER(currentchannelchanged);
+  IMPL_EVENT_HANDLER(eitbroadcasted);
+  IMPL_EVENT_HANDLER(scanningstatechanged);
+
+private:
+  ~TVSource();
+
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TVSource_h__
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVTuner.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Promise.h"
+#include "TVTuner.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(TVTuner)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TVTuner,
+                                                  DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TVTuner,
+                                                DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(TVTuner, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(TVTuner, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TVTuner)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+TVTuner::TVTuner(nsPIDOMWindow* aWindow)
+  : DOMEventTargetHelper(aWindow)
+{
+}
+
+TVTuner::~TVTuner()
+{
+}
+
+/* virtual */ JSObject*
+TVTuner::WrapObject(JSContext* aCx)
+{
+  return TVTunerBinding::Wrap(aCx, this);
+}
+
+void
+TVTuner::GetSupportedSourceTypes(nsTArray<TVSourceType>& aSourceTypes,
+                                 ErrorResult& aRv) const
+{
+  // TODO Implement in follow-up patches.
+}
+
+already_AddRefed<Promise>
+TVTuner::GetSources(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  MOZ_ASSERT(global);
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  // TODO Resolve/reject the promise in follow-up patches.
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+TVTuner::SetCurrentSource(const TVSourceType aSourceType, ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  MOZ_ASSERT(global);
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  // TODO Resolve/reject the promise in follow-up patches.
+
+  return promise.forget();
+}
+
+void
+TVTuner::GetId(nsAString& aId) const
+{
+  // TODO Implement in follow-up patches.
+}
+
+already_AddRefed<TVSource>
+TVTuner::GetCurrentSource() const
+{
+  // TODO Implement in follow-up patches.
+  return nullptr;
+}
+
+already_AddRefed<DOMMediaStream>
+TVTuner::GetStream() const
+{
+  // TODO Implement in follow-up patches.
+  return nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/tv/TVTuner.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TVTuner_h__
+#define mozilla_dom_TVTuner_h__
+
+#include "mozilla/DOMEventTargetHelper.h"
+// Include TVTunerBinding.h since enum TVSourceType can't be forward declared.
+#include "mozilla/dom/TVTunerBinding.h"
+
+namespace mozilla {
+
+class DOMMediaStream;
+
+namespace dom {
+
+class Promise;
+class TVSource;
+
+class TVTuner MOZ_FINAL : public DOMEventTargetHelper
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TVTuner, DOMEventTargetHelper)
+
+  explicit TVTuner(nsPIDOMWindow* aWindow);
+
+  // WebIDL (internal functions)
+
+  virtual JSObject* WrapObject(JSContext *aCx) MOZ_OVERRIDE;
+
+  // WebIDL (public APIs)
+
+  void GetSupportedSourceTypes(nsTArray<TVSourceType>& aSourceTypes,
+                               ErrorResult& aRv) const;
+
+  already_AddRefed<Promise> GetSources(ErrorResult& aRv);
+
+  already_AddRefed<Promise> SetCurrentSource(const TVSourceType aSourceType,
+                                             ErrorResult& aRv);
+
+  void GetId(nsAString& aId) const;
+
+  already_AddRefed<TVSource> GetCurrentSource() const;
+
+  already_AddRefed<DOMMediaStream> GetStream() const;
+
+  IMPL_EVENT_HANDLER(currentsourcechanged);
+
+private:
+  ~TVTuner();
+
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TVTuner_h__
new file mode 100644
--- /dev/null
+++ b/dom/tv/moz.build
@@ -0,0 +1,25 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+TEST_DIRS += ['test']
+
+EXPORTS.mozilla.dom += [
+    'TVChannel.h',
+    'TVManager.h',
+    'TVProgram.h',
+    'TVSource.h',
+    'TVTuner.h',
+]
+
+UNIFIED_SOURCES += [
+    'TVChannel.cpp',
+    'TVManager.cpp',
+    'TVProgram.cpp',
+    'TVSource.cpp',
+    'TVTuner.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
copy from dom/datastore/tests/file_app.sjs
copy to dom/tv/test/file_app.sjs
--- a/dom/datastore/tests/file_app.sjs
+++ b/dom/tv/test/file_app.sjs
@@ -1,19 +1,19 @@
-var gBasePath = "tests/dom/datastore/tests/";
+var gBasePath = "tests/dom/tv/test/";
 
 function handleRequest(request, response) {
   var query = getQuery(request);
 
   var testToken = '';
   if ('testToken' in query) {
     testToken = query.testToken;
   }
 
-  var template = 'file_app.template.webapp';
+  var template = '';
   if ('template' in query) {
     template = query.template;
   }
   var template = gBasePath + template;
   response.setHeader("Content-Type", "application/x-web-app-manifest+json", false);
   response.write(readTemplate(template).replace(/TESTTOKEN/g, testToken));
 }
 
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/file_app.template.webapp
@@ -0,0 +1,6 @@
+{
+  "name": "TV Test App (hosted)",
+  "description": "Hosted TV test app used for mochitest.",
+  "launch_path": "/tests/dom/tv/test/TESTTOKEN",
+  "icons": { "128": "default_icon" }
+}
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/file_tv_non_permitted_app.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test the availability of TV Manager API for non-permitted Apps</title>
+</head>
+<body>
+<div id="content"></div>
+  <script type="application/javascript;version=1.7">
+
+  function ok(a, msg) {
+    alert((a ? 'OK' : 'KO')+ ' ' + msg)
+  }
+
+  function finish() {
+    alert('DONE');
+  }
+
+  ok(!('tv' in navigator),
+     "navigator.tv should not exist for non-permitted app.");
+  finish();
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/mochitest.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files =
+  file_app.sjs
+  file_app.template.webapp
+  file_tv_non_permitted_app.html
+
+[test_tv_non_permitted_app.html]
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOCHITEST_MANIFESTS += ['mochitest.ini']
new file mode 100644
--- /dev/null
+++ b/dom/tv/test/test_tv_non_permitted_app.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test Non-Permitted Application for TV API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+"use strict";
+
+var gHostedManifestURL = 'http://test/tests/dom/tv/test/file_app.sjs?testToken=file_tv_non_permitted_app.html&template=file_app.template.webapp';
+var gApp;
+
+function cbError(e) {
+  ok(false, "Error callback invoked: " + this.error.name);
+  SimpleTest.finish();
+}
+
+function installApp() {
+  var request = navigator.mozApps.install(gHostedManifestURL);
+  request.onerror = cbError;
+  request.onsuccess = function() {
+    gApp = request.result;
+    runTest();
+  }
+}
+
+function uninstallApp() {
+  var request = navigator.mozApps.mgmt.uninstall(gApp);
+  request.onerror = cbError;
+  request.onsuccess = function() {
+    // All done.
+    info("All done");
+    runTest();
+  }
+}
+
+function testApp() {
+  var ifr = document.createElement('iframe');
+  ifr.setAttribute('mozbrowser', 'true');
+  ifr.setAttribute('mozapp', gApp.manifestURL);
+  ifr.setAttribute('src', gApp.manifest.launch_path);
+  var domParent = document.getElementById('content');
+
+  // Set us up to listen for messages from the app.
+  var listener = function(e) {
+    var message = e.detail.message;
+    if (/^OK/.exec(message)) {
+      ok(true, "Message from app: " + message);
+    } else if (/KO/.exec(message)) {
+      ok(false, "Message from app: " + message);
+    } else if (/DONE/.exec(message)) {
+      ok(true, "Messaging from app complete");
+      ifr.removeEventListener('mozbrowsershowmodalprompt', listener);
+      domParent.removeChild(ifr);
+      runTest();
+    }
+  }
+
+  // This event is triggered when the app calls "alert".
+  ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
+  domParent.appendChild(ifr);
+}
+
+var tests = [
+  // Installing the app
+  installApp,
+
+  // Run tests in app
+  testApp,
+
+  // Uninstall the app
+  uninstallApp
+];
+
+function runTest() {
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv({"set": [["dom.tv.enabled", true]]}, function() {
+  SpecialPowers.pushPermissions(
+    [{ "type": "tv", "allow": true, "context": document },
+     { "type": "browser", "allow": true, "context": document },
+     { "type": "embed-apps", "allow": true, "context": document },
+     { "type": "webapps-manage", "allow": true, "context": document }],
+    function(){
+      SpecialPowers.setAllAppsLaunchable(true);
+      SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
+      // No confirmation needed when an app is installed
+      SpecialPowers.autoConfirmAppInstall(() => {
+        SpecialPowers.autoConfirmAppUninstall(runTest);
+      });
+    });
+});
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -383,8 +383,13 @@ partial interface Navigator {
   readonly attribute ServiceWorkerContainer serviceWorker;
 };
 
 partial interface Navigator {
   [Throws, Pref="beacon.enabled"]
   boolean sendBeacon(DOMString url,
                      optional (ArrayBufferView or Blob or DOMString or FormData)? data = null);
 };
+
+partial interface Navigator {
+  [Pref="dom.tv.enabled", CheckPermissions="tv", Func="Navigator::HasTVSupport"]
+  readonly attribute TVManager? tv;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/TVChannel.webidl
@@ -0,0 +1,46 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://seanyhlin.github.io/TV-Manager-API/
+ */
+
+enum TVChannelType {
+  "tv",
+  "radio",
+  "data"
+};
+
+dictionary TVGetProgramsOptions {
+  unsigned long long startTime;
+  unsigned long long duration;
+};
+
+[Pref="dom.tv.enabled", CheckPermissions="tv", Func="Navigator::HasTVSupport"]
+interface TVChannel : EventTarget {
+  [Throws]
+  Promise<sequence<TVProgram>> getPrograms(optional TVGetProgramsOptions options);
+
+  [Throws]
+  Promise<TVProgram> getCurrentProgram();
+
+  readonly attribute DOMString networkId;
+
+  readonly attribute DOMString transportStreamId;
+
+  readonly attribute DOMString serviceId;
+
+  readonly attribute TVSource source;
+
+  readonly attribute TVChannelType type;
+
+  readonly attribute DOMString name;
+
+  readonly attribute DOMString number;
+
+  readonly attribute boolean isEmergency;
+
+  readonly attribute boolean isFree;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/TVCurrentChannelChangedEvent.webidl
@@ -0,0 +1,20 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://seanyhlin.github.io/TV-Manager-API/
+ */
+
+dictionary TVCurrentChannelChangedEventInit : EventInit {
+  TVChannel? channel = null;
+};
+
+[Pref="dom.tv.enabled",
+ CheckPermissions="tv",
+ Func="Navigator::HasTVSupport",
+ Constructor(DOMString type, optional TVCurrentChannelChangedEventInit eventInitDict)]
+interface TVCurrentChannelChangedEvent : Event {
+  readonly attribute TVChannel? channel;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/TVCurrentSourceChangedEvent.webidl
@@ -0,0 +1,20 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://seanyhlin.github.io/TV-Manager-API/
+ */
+
+dictionary TVCurrentSourceChangedEventInit : EventInit {
+  TVSource? source = null;
+};
+
+[Pref="dom.tv.enabled",
+ CheckPermissions="tv",
+ Func="Navigator::HasTVSupport",
+ Constructor(DOMString type, optional TVCurrentSourceChangedEventInit eventInitDict)]
+interface TVCurrentSourceChangedEvent : Event {
+  readonly attribute TVSource? source;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/TVEITBroadcastedEvent.webidl
@@ -0,0 +1,20 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://seanyhlin.github.io/TV-Manager-API/
+ */
+
+dictionary TVEITBroadcastedEventInit : EventInit {
+  sequence<TVProgram> programs = [];
+};
+
+[Pref="dom.tv.enabled",
+ CheckPermissions="tv",
+ Func="Navigator::HasTVSupport",
+ Constructor(DOMString type, optional TVEITBroadcastedEventInit eventInitDict)]
+interface TVEITBroadcastedEvent : Event {
+  [Pure, Cached] readonly attribute sequence<TVProgram> programs;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/TVManager.webidl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://seanyhlin.github.io/TV-Manager-API/
+ */
+
+[Pref="dom.tv.enabled", CheckPermissions="tv", Func="Navigator::HasTVSupport"]
+interface TVManager : EventTarget {
+  [Throws]
+  Promise<sequence<TVTuner>> getTuners();
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/TVProgram.webidl
@@ -0,0 +1,29 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://seanyhlin.github.io/TV-Manager-API/
+ */
+
+[Pref="dom.tv.enabled", CheckPermissions="tv", Func="Navigator::HasTVSupport"]
+interface TVProgram {
+  sequence<DOMString> getAudioLanguages();
+
+  sequence<DOMString> getSubtitleLanguages();
+
+  readonly attribute DOMString eventId;
+
+  readonly attribute TVChannel channel;
+
+  readonly attribute DOMString title;
+
+  readonly attribute unsigned long long startTime;
+
+  readonly attribute unsigned long long duration;
+
+  readonly attribute DOMString? description;
+
+  readonly attribute DOMString? rating;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/TVScanningStateChangedEvent.webidl
@@ -0,0 +1,29 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://seanyhlin.github.io/TV-Manager-API/
+ */
+
+enum TVScanningState {
+  "cleared",
+  "scanned",
+  "completed",
+  "stopped"
+};
+
+dictionary TVScanningStateChangedEventInit : EventInit {
+  TVScanningState state = "cleared";
+  TVChannel? channel = null;
+};
+
+[Pref="dom.tv.enabled",
+ CheckPermissions="tv",
+ Func="Navigator::HasTVSupport",
+ Constructor(DOMString type, optional TVScanningStateChangedEventInit eventInitDict)]
+interface TVScanningStateChangedEvent : Event {
+  readonly attribute TVScanningState state;
+  readonly attribute TVChannel? channel;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/TVSource.webidl
@@ -0,0 +1,61 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://seanyhlin.github.io/TV-Manager-API/
+ */
+
+enum TVSourceType {
+  "dvb-t",
+  "dvb-t2",
+  "dvb-c",
+  "dvb-c2",
+  "dvb-s",
+  "dvb-s2",
+  "dvb-h",
+  "dvb-sh",
+  "atsc",
+  "atsc-m/h",
+  "isdb-t",
+  "isdb-tb",
+  "isdb-s",
+  "isdb-c",
+  "1seg",
+  "dtmb",
+  "cmmb",
+  "t-dmb",
+  "s-dmb"
+};
+
+dictionary TVStartScanningOptions {
+  boolean isRescanned;
+};
+
+[Pref="dom.tv.enabled", CheckPermissions="tv", Func="Navigator::HasTVSupport"]
+interface TVSource : EventTarget {
+  [Throws]
+  Promise<sequence<TVChannel>> getChannels();
+
+  [Throws]
+  Promise<void> setCurrentChannel(DOMString channelNumber);
+
+  [Throws]
+  Promise<void> startScanning(optional TVStartScanningOptions options);
+
+  [Throws]
+  Promise<void> stopScanning();
+
+  readonly attribute TVTuner tuner;
+
+  readonly attribute TVSourceType type;
+
+  readonly attribute boolean isScanning;
+
+  readonly attribute TVChannel? currentChannel;
+
+  attribute EventHandler oncurrentchannelchanged;
+  attribute EventHandler oneitbroadcasted;
+  attribute EventHandler onscanningstatechanged;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/TVTuner.webidl
@@ -0,0 +1,28 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://seanyhlin.github.io/TV-Manager-API/
+ */
+
+[Pref="dom.tv.enabled", CheckPermissions="tv", Func="Navigator::HasTVSupport"]
+interface TVTuner : EventTarget {
+  [Throws]
+  sequence<TVSourceType> getSupportedSourceTypes();
+
+  [Throws]
+  Promise<sequence<TVSource>> getSources();
+
+  [Throws]
+  Promise<void> setCurrentSource(TVSourceType sourceType);
+
+  readonly attribute DOMString id;
+
+  readonly attribute TVSource? currentSource;
+
+  readonly attribute MediaStream? stream;
+
+  attribute EventHandler oncurrentsourcechanged;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -497,16 +497,21 @@ WEBIDL_FILES = [
     'Touch.webidl',
     'TouchEvent.webidl',
     'TouchList.webidl',
     'TransitionEvent.webidl',
     'TreeBoxObject.webidl',
     'TreeColumn.webidl',
     'TreeColumns.webidl',
     'TreeWalker.webidl',
+    'TVChannel.webidl',
+    'TVManager.webidl',
+    'TVProgram.webidl',
+    'TVSource.webidl',
+    'TVTuner.webidl',
     'UDPMessageEvent.webidl',
     'UDPSocket.webidl',
     'UIEvent.webidl',
     'UndoManager.webidl',
     'URL.webidl',
     'URLSearchParams.webidl',
     'URLUtils.webidl',
     'URLUtilsReadOnly.webidl',
@@ -712,16 +717,20 @@ GENERATED_EVENTS_WEBIDL_FILES = [
     'RTCPeerConnectionIdentityErrorEvent.webidl',
     'RTCPeerConnectionIdentityEvent.webidl',
     'ScrollViewChangeEvent.webidl',
     'SelectionChangeEvent.webidl',
     'StyleRuleChangeEvent.webidl',
     'StyleSheetApplicableStateChangeEvent.webidl',
     'StyleSheetChangeEvent.webidl',
     'TrackEvent.webidl',
+    'TVCurrentChannelChangedEvent.webidl',
+    'TVCurrentSourceChangedEvent.webidl',
+    'TVEITBroadcastedEvent.webidl',
+    'TVScanningStateChangedEvent.webidl',
     'UDPMessageEvent.webidl',
     'UserProximityEvent.webidl',
     'USSDReceivedEvent.webidl',
 ]
 
 if CONFIG['MOZ_WEBSPEECH']:
     GENERATED_EVENTS_WEBIDL_FILES += [
         'SpeechRecognitionEvent.webidl',