Bug 1250244 - Part 9: Implement WebVR DOM Events draft
authorkearwood
Mon, 04 Jul 2016 15:54:08 -0700
changeset 383705 d01c23a1d9812455bbb8907feecf24370a5b190e
parent 383704 b7e93afeacc8f01676fe54ede85f5fa7f4a61ead
child 524542 9d0da5a5a992b0aa6956ed40c127eea7806ba842
push id22091
push userkgilbert@mozilla.com
push dateMon, 04 Jul 2016 22:54:30 +0000
bugs1250244
milestone50.0a1
Bug 1250244 - Part 9: Implement WebVR DOM Events MozReview-Commit-ID: DLpd5T6a24l
dom/base/Navigator.cpp
dom/base/nsGkAtomList.h
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/base/nsPIDOMWindow.h
dom/events/EventListenerManager.cpp
dom/events/EventNameList.h
dom/vr/VREventObserver.cpp
dom/vr/VREventObserver.h
dom/vr/moz.build
dom/webidl/Window.webidl
gfx/vr/ipc/VRManagerChild.cpp
gfx/vr/ipc/VRManagerChild.h
widget/EventMessageList.h
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -2047,24 +2047,27 @@ Navigator::RequestGamepadServiceTest()
 already_AddRefed<Promise>
 Navigator::GetVRDisplays(ErrorResult& aRv)
 {
   if (!mWindow || !mWindow->GetDocShell()) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
+  nsGlobalWindow* win = nsGlobalWindow::Cast(mWindow);
+  win->SetHasVREventListener(true);
+
   nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
   RefPtr<Promise> p = Promise::Create(go, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
-  // We pass ourself to RefreshVRDisplays, so NotifyVRDisplaysUpdated will
-  // be called asynchronously, resolving the promises in mVRGetDisplaysPromises.
+  // We pass ourself to RefreshVRDisplays, so NotifyVRDisplaysUpdated will
+  // be called asynchronously, resolving the promises in mVRGetDisplaysPromises.
   if (!VRDisplay::RefreshVRDisplays(this)) {
     p->MaybeReject(NS_ERROR_FAILURE);
     return p.forget();
   }
 
   mVRGetDisplaysPromises.AppendElement(p);
   return p.forget();
 }
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -941,16 +941,19 @@ GK_ATOM(onunderflow, "onunderflow")
 GK_ATOM(onunload, "onunload")
 GK_ATOM(onupdatefound, "onupdatefound")
 GK_ATOM(onupdateready, "onupdateready")
 GK_ATOM(onupgradeneeded, "onupgradeneeded")
 GK_ATOM(onussdreceived, "onussdreceived")
 GK_ATOM(onversionchange, "onversionchange")
 GK_ATOM(onvoicechange, "onvoicechange")
 GK_ATOM(onvoiceschanged, "onvoiceschanged")
+GK_ATOM(onvrdisplayconnected, "onvrdisplayconnected")
+GK_ATOM(onvrdisplaydisconnected, "onvrdisplaydisconnected")
+GK_ATOM(onvrdisplaypresentchange, "onvrdisplaypresentchange")
 GK_ATOM(onwebkitAnimationEnd, "onwebkitAnimationEnd")
 GK_ATOM(onwebkitAnimationIteration, "onwebkitAnimationIteration")
 GK_ATOM(onwebkitAnimationStart, "onwebkitAnimationStart")
 GK_ATOM(onwebkitTransitionEnd, "onwebkitTransitionEnd")
 GK_ATOM(onwebsocket, "onwebsocket")
 GK_ATOM(onwheel, "onwheel")
 GK_ATOM(open, "open")
 GK_ATOM(optgroup, "optgroup")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -188,16 +188,17 @@
 #include "mozilla/dom/Promise.h"
 
 #ifdef MOZ_GAMEPAD
 #include "mozilla/dom/Gamepad.h"
 #include "mozilla/dom/GamepadManager.h"
 #endif
 
 #include "mozilla/dom/VRDisplay.h"
+#include "mozilla/dom/VREventObserver.h"
 
 #include "nsRefreshDriver.h"
 #include "Layers.h"
 
 #include "mozilla/AddonPathService.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
@@ -1202,16 +1203,17 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW
     mCreatingInnerWindow(false),
     mIsChrome(false),
     mCleanMessageManager(false),
     mNeedsFocus(true),
     mHasFocus(false),
     mShowFocusRingForContent(false),
     mFocusByKeyOccurred(false),
     mHasGamepad(false),
+    mHasVREvents(false),
 #ifdef MOZ_GAMEPAD
     mHasSeenGamepadInput(false),
 #endif
     mNotifiedIDDestroyed(false),
     mAllowScriptsToClose(false),
     mTimeoutInsertionPoint(nullptr),
     mTimeoutPublicIdCounter(1),
     mTimeoutFiringDepth(0),
@@ -1581,16 +1583,18 @@ nsGlobalWindow::CleanUp()
 #ifdef MOZ_WEBSPEECH
   mSpeechSynthesis = nullptr;
 #endif
 
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
   mOrientationChangeObserver = nullptr;
 #endif
 
+  mVREventObserver = nullptr;
+
   ClearControllers();
 
   mOpener = nullptr;             // Forces Release
   if (mContext) {
     mContext = nullptr;            // Forces Release
   }
   mChromeEventHandler = nullptr; // Forces Release
   mParentTarget = nullptr;
@@ -1600,21 +1604,24 @@ nsGlobalWindow::CleanUp()
     if (inner) {
       inner->CleanUp();
     }
   }
 
   if (IsInnerWindow()) {
     DisableGamepadUpdates();
     mHasGamepad = false;
+    DisableVRUpdates();
+    mHasVREvents = false;
 #ifdef MOZ_B2G
     DisableTimeChangeNotifications();
 #endif
   } else {
     MOZ_ASSERT(!mHasGamepad);
+    MOZ_ASSERT(!mHasVREvents);
   }
 
   if (mCleanMessageManager) {
     MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned");
     nsGlobalChromeWindow *asChrome = static_cast<nsGlobalChromeWindow*>(this);
     if (asChrome->mMessageManager) {
       static_cast<nsFrameMessageManager*>(
         asChrome->mMessageManager.get())->Disconnect();
@@ -1705,16 +1712,18 @@ nsGlobalWindow::FreeInnerObjects()
   if (mScreen) {
     mScreen = nullptr;
   }
 
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
   mOrientationChangeObserver = nullptr;
 #endif
 
+  mVREventObserver = nullptr;
+
   if (mDoc) {
     // Remember the document's principal and URI.
     mDocumentPrincipal = mDoc->NodePrincipal();
     mDocumentURI = mDoc->GetDocumentURI();
     mDocBaseURI = mDoc->GetDocBaseURI();
 
     while (mDoc->EventHandlingSuppressed()) {
       mDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eEvents, false);
@@ -1743,16 +1752,18 @@ nsGlobalWindow::FreeInnerObjects()
   }
   mAudioContexts.Clear();
 
 #ifdef MOZ_GAMEPAD
   DisableGamepadUpdates();
   mHasGamepad = false;
   mGamepads.Clear();
 #endif
+  DisableVRUpdates();
+  mHasVREvents = false;
   mVRDisplays.Clear();
 }
 
 //*****************************************************************************
 // nsGlobalWindow::nsISupports
 //*****************************************************************************
 
 // QueryInterface implementation for nsGlobalWindow
@@ -9938,16 +9949,34 @@ nsGlobalWindow::DisableGamepadUpdates()
     if (gamepadManager) {
       gamepadManager->RemoveListener(this);
     }
 #endif
   }
 }
 
 void
+nsGlobalWindow::EnableVRUpdates()
+{
+  MOZ_ASSERT(IsInnerWindow());
+
+  if (mHasVREvents && !mVREventObserver) {
+    mVREventObserver = new mozilla::dom::VREventObserver(this);
+  }
+}
+
+void
+nsGlobalWindow::DisableVRUpdates()
+{
+  MOZ_ASSERT(IsInnerWindow());
+
+  mVREventObserver = nullptr;
+}
+
+void
 nsGlobalWindow::SetChromeEventHandler(EventTarget* aChromeEventHandler)
 {
   MOZ_ASSERT(IsOuterWindow());
 
   SetChromeEventHandlerInternal(aChromeEventHandler);
   // update the chrome event handler on all our inner windows
   for (nsGlobalWindow *inner = (nsGlobalWindow *)PR_LIST_HEAD(this);
        inner != this;
@@ -13069,16 +13098,17 @@ nsGlobalWindow::ResumeTimeouts(bool aTha
 
   if (shouldResume) {
     nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
     if (ac) {
       for (uint32_t i = 0; i < mEnabledSensors.Length(); i++)
         ac->AddWindowListener(mEnabledSensors[i], this);
     }
     EnableGamepadUpdates();
+    EnableVRUpdates();
 
     // Resume all of the AudioContexts for this window
     for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
       ErrorResult dummy;
       RefPtr<Promise> d = mAudioContexts[i]->Resume(dummy);
     }
 
     // Thaw or resume all of the workers for this window.
@@ -13258,16 +13288,26 @@ nsGlobalWindow::SetHasGamepadEventListen
   MOZ_ASSERT(IsInnerWindow());
   mHasGamepad = aHasGamepad;
   if (aHasGamepad) {
     EnableGamepadUpdates();
   }
 }
 
 void
+nsGlobalWindow::SetHasVREventListener(bool aHasVREvents/* = true*/)
+{
+  MOZ_ASSERT(IsInnerWindow());
+  mHasVREvents = aHasVREvents;
+  if (aHasVREvents) {
+    EnableVRUpdates();
+  }
+}
+
+void
 nsGlobalWindow::EnableTimeChangeNotifications()
 {
   mozilla::time::AddWindowListener(AsInner());
 }
 
 void
 nsGlobalWindow::DisableTimeChangeNotifications()
 {
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -117,16 +117,17 @@ class OwningExternalOrWindowProxy;
 class Promise;
 class PostMessageEvent;
 struct RequestInit;
 class RequestOrUSVString;
 class Selection;
 class SpeechSynthesis;
 class U2F;
 class VRDisplay;
+class VREventObserver;
 class WakeLock;
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
 class WindowOrientationObserver;
 #endif
 namespace cache {
 class CacheStorage;
 } // namespace cache
 class IDBFactory;
@@ -491,16 +492,17 @@ public:
     FullscreenReason aReason, bool aIsFullscreen) override final;
   virtual void FinishFullscreenChange(bool aIsFullscreen) override final;
   bool SetWidgetFullscreen(FullscreenReason aReason, bool aIsFullscreen,
                            nsIWidget* aWidget, nsIScreen* aScreen);
   bool FullScreen() const;
 
   // Inner windows only.
   virtual void SetHasGamepadEventListener(bool aHasGamepad = true) override;
+  virtual void SetHasVREventListener(bool aHasVREvents = true) override;
 
   // nsIInterfaceRequestor
   NS_DECL_NSIINTERFACEREQUESTOR
 
   // WebIDL interface.
   already_AddRefed<nsPIDOMWindowOuter> IndexedGetterOuter(uint32_t aIndex);
   already_AddRefed<nsPIDOMWindowOuter> IndexedGetter(uint32_t aIndex);
 
@@ -796,16 +798,21 @@ public:
   void SyncGamepadState();
 #endif
 
   // Inner windows only.
   // Enable/disable updates for gamepad input.
   void EnableGamepadUpdates();
   void DisableGamepadUpdates();
 
+  // Inner windows only.
+  // Enable/disable updates for VR
+  void EnableVRUpdates();
+  void DisableVRUpdates();
+
   // Update the VR displays for this window
   bool UpdateVRDisplays(nsTArray<RefPtr<mozilla::dom::VRDisplay>>& aDisplays);
 
 #define EVENT(name_, id_, type_, struct_)                                     \
   mozilla::dom::EventHandlerNonNull* GetOn##name_()                           \
   {                                                                           \
     mozilla::EventListenerManager* elm = GetExistingListenerManager();        \
     return elm ? elm->GetEventHandler(nsGkAtoms::on##name_, EmptyString())    \
@@ -1760,16 +1767,20 @@ protected:
 
   // true if tab navigation has occurred for this window. Focus rings
   // should be displayed.
   bool                   mFocusByKeyOccurred : 1;
 
   // Inner windows only.
   // Indicates whether this window wants gamepad input events
   bool                   mHasGamepad : 1;
+
+  // Inner windows only.
+  // Indicates whether this window wants VR events
+  bool                   mHasVREvents : 1;
 #ifdef MOZ_GAMEPAD
   nsCheapSet<nsUint32HashKey> mGamepadIndexSet;
   nsRefPtrHashtable<nsUint32HashKey, mozilla::dom::Gamepad> mGamepads;
   bool mHasSeenGamepadInput;
 #endif
 
   // whether we've sent the destroy notification for our window id
   bool                   mNotifiedIDDestroyed : 1;
@@ -1904,16 +1915,18 @@ protected:
 #endif
 
   // This is the CC generation the last time we called CanSkip.
   uint32_t mCanSkipCCGeneration;
 
   // The VR Displays for this window
   nsTArray<RefPtr<mozilla::dom::VRDisplay>> mVRDisplays;
 
+  nsAutoPtr<mozilla::dom::VREventObserver> mVREventObserver;
+
   friend class nsDOMScriptableHelper;
   friend class nsDOMWindowUtils;
   friend class mozilla::dom::PostMessageEvent;
   friend class DesktopNotification;
 
   static WindowByIdTable* sWindowsById;
   static bool sWarnedAboutWindowInternal;
 };
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -466,16 +466,23 @@ public:
   /**
    * Tell this window that there is an observer for gamepad input
    *
    * Inner windows only.
    */
   virtual void SetHasGamepadEventListener(bool aHasGamepad = true) = 0;
 
   /**
+  * Tell this window that there is an observer for VR events
+  *
+  * Inner windows only.
+  */
+  virtual void SetHasVREventListener(bool aHasVREvents = true) = 0;
+
+  /**
    * Set a arguments for this window. This will be set on the window
    * right away (if there's an existing document) and it will also be
    * installed on the window when the next document is loaded.
    *
    * This function serves double-duty for passing both |arguments| and
    * |dialogArguments| back from nsWindowWatcher to nsGlobalWindow. For the
    * latter, the array is an array of length 0 whose only element is a
    * DialogArgumentsHolder representing the JS value passed to showModalDialog.
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -414,16 +414,22 @@ EventListenerManager::AddEventListenerIn
     }
   } else if (aTypeAtom == nsGkAtoms::oncompositionend ||
              aTypeAtom == nsGkAtoms::oncompositionstart ||
              aTypeAtom == nsGkAtoms::oncompositionupdate ||
              aTypeAtom == nsGkAtoms::oninput) {
     if (!aFlags.mInSystemGroup) {
       mMayHaveInputOrCompositionEventListener = true;
     }
+  } else if (aTypeAtom == nsGkAtoms::onvrdisplayconnected ||
+             aTypeAtom == nsGkAtoms::onvrdisplaydisconnected ||
+             aTypeAtom == nsGkAtoms::onvrdisplaypresentchange) {
+    if (nsPIDOMWindowInner* window = GetInnerWindowForTarget()) {
+      window->SetHasVREventListener();
+    }
   }
 
   if (IsApzAwareListener(listener)) {
     ProcessApzAwareEventListenerAdd();
   }
 
   if (aTypeAtom && mTarget) {
     mTarget->EventListenerAdded(aTypeAtom);
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -583,16 +583,28 @@ WINDOW_ONLY_EVENT(userproximity,
                   eUserProximity,
                   EventNameType_None,
                   eBasicEventClass)
 WINDOW_ONLY_EVENT(devicelight,
                   eDeviceLight,
                   EventNameType_None,
                   eBasicEventClass)
 
+WINDOW_ONLY_EVENT(vrdisplayconnected,
+                  eVRDisplayConnected,
+                  EventNameType_None,
+                  eBasicEventClass)
+WINDOW_ONLY_EVENT(vrdisplaydisconnected,
+                  eVRDisplayDisconnected,
+                  EventNameType_None,
+                  eBasicEventClass)
+WINDOW_ONLY_EVENT(vrdisplaypresentchange,
+                  eVRDisplayPresentChange,
+                  EventNameType_None,
+                  eBasicEventClass)
 // Install events as per W3C Manifest spec
 WINDOW_ONLY_EVENT(install,
                   eInstall,
                   EventNameType_None,
                   eBasicEventClass)
 
 
 #ifdef MOZ_B2G
new file mode 100644
--- /dev/null
+++ b/dom/vr/VREventObserver.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 "VREventObserver.h"
+
+#include "nsGlobalWindow.h"
+#include "VRManagerChild.h"
+
+using namespace mozilla::dom;
+
+/**
+* This class is used by nsGlobalWindow to implement window.onvrdisplayconnected,
+* window.onvrdisplaydisconnected, and window.onvrdisplaypresentchange.
+*/
+VREventObserver::VREventObserver(
+  nsGlobalWindow* aGlobalWindow)
+  : mWindow(aGlobalWindow)
+{
+  MOZ_ASSERT(aGlobalWindow && aGlobalWindow->IsInnerWindow());
+
+  gfx::VRManagerChild* vmc = gfx::VRManagerChild::Get();
+  if (vmc) {
+    vmc->AddListener(this);
+  }
+}
+
+VREventObserver::~VREventObserver()
+{
+  gfx::VRManagerChild* vmc = gfx::VRManagerChild::Get();
+  if (vmc) {
+    vmc->RemoveListener(this);
+  }
+}
+
+void
+VREventObserver::NotifyVRDisplayConnected()
+{
+  if (mWindow->AsInner()->IsCurrentInnerWindow()) {
+    mWindow->GetOuterWindow()->DispatchCustomEvent(NS_LITERAL_STRING("vrdisplayconnected"));
+  }
+}
+
+void
+VREventObserver::NotifyVRDisplayDisconnected()
+{
+  if (mWindow->AsInner()->IsCurrentInnerWindow()) {
+    mWindow->GetOuterWindow()->DispatchCustomEvent(NS_LITERAL_STRING("vrdisplaydisconnected"));
+  }
+}
+
+void
+VREventObserver::NotifyVRDisplayPresentChange()
+{
+  // Background tabs should not be allowed to see when a foreground tab
+  // has started or ended vr presentation.
+  if (mWindow->AsInner()->IsCurrentInnerWindow() && !mWindow->GetOuterWindow()->IsBackground()) {
+    mWindow->GetOuterWindow()->DispatchCustomEvent(NS_LITERAL_STRING("vrdisplaypresentchange"));
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/vr/VREventObserver.h
@@ -0,0 +1,33 @@
+/* -*- 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_VREventObserver_h
+#define mozilla_dom_VREventObserver_h
+
+class nsGlobalWindow;
+
+namespace mozilla {
+namespace dom {
+class VREventObserver final
+{
+public:
+  ~VREventObserver();
+  explicit VREventObserver(nsGlobalWindow* aGlobalWindow);
+
+  void NotifyVRDisplayConnected();
+  void NotifyVRDisplayDisconnected();
+  void NotifyVRDisplayPresentChange();
+
+private:
+  
+  // Weak pointer, instance is owned by mWindow.
+  nsGlobalWindow* MOZ_NON_OWNING_REF mWindow;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_VREventObserver_h
--- a/dom/vr/moz.build
+++ b/dom/vr/moz.build
@@ -1,20 +1,22 @@
 # -*- 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/.
 
 EXPORTS.mozilla.dom += [
     'VRDisplay.h',
+    'VREventObserver.h',
     ]
 
 UNIFIED_SOURCES = [
     'VRDisplay.cpp',
+    'VREventObserver.cpp',
     ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base'
 ]
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -496,11 +496,20 @@ interface ChromeWindow {
    * The optional panel argument should be set when moving a panel.
    *
    * Throws NS_ERROR_NOT_IMPLEMENTED if the OS doesn't support this.
    */
   [Throws, Func="nsGlobalWindow::IsPrivilegedChromeWindow"]
   void beginWindowMove(Event mouseDownEvent, optional Element? panel = null);
 };
 
+partial interface Window {
+  [Pref="dom.vr.enabled"]
+  attribute EventHandler onvrdisplayconnected;
+  [Pref="dom.vr.enabled"]
+  attribute EventHandler onvrdisplaydisconnected;
+  [Pref="dom.vr.enabled"]
+  attribute EventHandler onvrdisplaypresentchange;
+};
+
 Window implements ChromeWindow;
 Window implements GlobalFetch;
 Window implements ImageBitmapFactories;
--- a/gfx/vr/ipc/VRManagerChild.cpp
+++ b/gfx/vr/ipc/VRManagerChild.cpp
@@ -6,21 +6,27 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "VRManagerChild.h"
 #include "VRManagerParent.h"
 #include "VRDisplayClient.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/layers/CompositorThread.h" // for CompositorThread
 #include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/VREventObserver.h"
 #include "mozilla/dom/WindowBinding.h" // for FrameRequestCallback
 #include "mozilla/layers/TextureClient.h"
 
 using layers::TextureClient;
 
+namespace {
+const nsTArray<RefPtr<dom::VREventObserver> >::index_type NoIndex =
+  nsTArray<RefPtr<dom::VREventObserver> >::NoIndex;
+} // namespace
+
 namespace mozilla {
 namespace gfx {
 
 static StaticRefPtr<VRManagerChild> sVRManagerChildSingleton;
 static StaticRefPtr<VRManagerParent> sVRManagerParentSingleton;
 
 void ReleaseVRManagerParentSingleton() {
   sVRManagerParentSingleton = nullptr;
@@ -453,21 +459,62 @@ VRManagerChild::RunFrameRequestCallbacks
   for (auto& callback : callbacks) {
     callback.mCallback->Call(timeStamp);
   }
 }
 
 void
 VRManagerChild::FireDOMVRDisplayConnectedEvent()
 {
+  for (auto& listener : mListeners) {
+    listener->NotifyVRDisplayConnected();
+  }
 }
 
 void
 VRManagerChild::FireDOMVRDisplayDisconnectedEvent()
 {
+  for (auto& listener : mListeners) {
+    listener->NotifyVRDisplayDisconnected();
+  }
 }
 
 void
 VRManagerChild::FireDOMVRDisplayPresentChangeEvent()
 {
+  for (auto& listener : mListeners) {
+    listener->NotifyVRDisplayPresentChange();
+  }
 }
+
+void
+VRManagerChild::AddListener(dom::VREventObserver* aObserver)
+{
+  MOZ_ASSERT(aObserver);
+
+  if (mListeners.IndexOf(aObserver) != NoIndex) {
+    return; // already exists
+  }
+
+  mListeners.AppendElement(aObserver);
+  if (mListeners.Length() == 1) {
+    Unused << SendSetHaveEventListener(true);
+  }
+}
+
+void
+VRManagerChild::RemoveListener(dom::VREventObserver* aObserver)
+{
+  MOZ_ASSERT(aObserver);
+
+  if (mListeners.IndexOf(aObserver) == NoIndex) {
+    return; // doesn't exist
+  }
+
+  mListeners.RemoveElement(aObserver);
+
+  if (mListeners.Length() == 0) {
+    Unused << SendSetHaveEventListener(false);
+  }
+}
+
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/vr/ipc/VRManagerChild.h
+++ b/gfx/vr/ipc/VRManagerChild.h
@@ -14,16 +14,17 @@
 #include "mozilla/layers/ISurfaceAllocator.h"  // for ISurfaceAllocator
 #include "mozilla/layers/LayersTypes.h"  // for LayersBackend
 #include "mozilla/layers/TextureForwarder.h"
 
 namespace mozilla {
 namespace dom {
 class Navigator;
 class VRDisplay;
+class VREventObserver;
 } // namespace dom
 namespace layers {
 class PCompositableChild;
 class TextureClient;
 }
 namespace gfx {
 class VRLayerChild;
 class VRDisplayClient;
@@ -32,16 +33,21 @@ class VRManagerChild : public PVRManager
                      , public layers::TextureForwarder
                      , public layers::ShmemAllocator
 {
 public:
 //  NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION(VRManagerChild)
 
   static VRManagerChild* Get();
 
+  // Indicate that an observer wants to receive VR events.
+  void AddListener(dom::VREventObserver* aObserver);
+  // Indicate that an observer should no longer receive VR events.
+  void RemoveListener(dom::VREventObserver* aObserver);
+
   int GetInputFrameID();
   bool GetVRDisplays(nsTArray<RefPtr<VRDisplayClient> >& aDisplays);
   bool RefreshVRDisplaysWithCallback(dom::Navigator* aNavigator);
   static VRManagerChild* StartUpInChildProcess(Transport* aTransport,
                                                ProcessId aOtherProcess);
 
   static void StartUpSameProcess();
   static void ShutDown();
@@ -142,16 +148,20 @@ private:
 
   nsTArray<FrameRequest> mFrameRequestCallbacks;
   /**
   * The current frame request callback handle
   */
   int32_t mFrameRequestCallbackCounter;
   mozilla::TimeStamp mStartTimeStamp;
 
+
+  // Array of Weak pointers, instance is owned by nsGlobalWindow::mVREventObserver.
+  nsTArray<dom::VREventObserver*> mListeners;
+
   /**
   * Hold TextureClients refs until end of their usages on host side.
   * It defer calling of TextureClient recycle callback.
   */
   nsDataHashtable<nsUint64HashKey, RefPtr<layers::TextureClient> > mTexturesWaitingRecycled;
 
   layers::LayersBackend mBackend;
 
--- a/widget/EventMessageList.h
+++ b/widget/EventMessageList.h
@@ -369,16 +369,21 @@ NS_EVENT_MESSAGE(eAbsoluteDeviceOrientat
 NS_EVENT_MESSAGE(eDeviceMotion)
 NS_EVENT_MESSAGE(eDeviceProximity)
 NS_EVENT_MESSAGE(eUserProximity)
 NS_EVENT_MESSAGE(eDeviceLight)
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
 NS_EVENT_MESSAGE(eOrientationChange)
 #endif
 
+// WebVR events
+NS_EVENT_MESSAGE(eVRDisplayConnected)
+NS_EVENT_MESSAGE(eVRDisplayDisconnected)
+NS_EVENT_MESSAGE(eVRDisplayPresentChange)
+
 NS_EVENT_MESSAGE(eShow)
 
 // Fullscreen DOM API
 NS_EVENT_MESSAGE(eFullscreenChange)
 NS_EVENT_MESSAGE(eFullscreenError)
 NS_EVENT_MESSAGE(eMozFullscreenChange)
 NS_EVENT_MESSAGE(eMozFullscreenError)