Bug 1250244 - Part 8: Implement WebVR DOM Events,r=bz
authorkearwood
Mon, 04 Jul 2016 15:46:49 -0700
changeset 309341 cc38fe6bde478931ea9e64fd156fdc8eed32b7a5
parent 309340 7b1349cb7487ce1b03a6f91c016315f9e8fdd0ec
child 309342 ec7229cceafcb353f5293347aa653f4242219c88
push id30561
push userkwierso@gmail.com
push dateMon, 15 Aug 2016 21:20:49 +0000
treeherdermozilla-central@91a319101587 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1250244
milestone51.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 1250244 - Part 8: Implement WebVR DOM Events,r=bz MozReview-Commit-ID: 4Fk0WszVTBR
dom/base/Navigator.cpp
dom/base/nsGkAtomList.h
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/events/EventNameList.h
dom/vr/VREventObserver.cpp
dom/vr/VREventObserver.h
dom/vr/moz.build
dom/webidl/Window.webidl
gfx/vr/VRManager.cpp
gfx/vr/ipc/PVRManager.ipdl
gfx/vr/ipc/VRManagerChild.cpp
gfx/vr/ipc/VRManagerChild.h
widget/EventMessageList.h
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -2039,43 +2039,46 @@ 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->NotifyVREventListenerAdded();
+
   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();
 }
 
 void
 Navigator::NotifyVRDisplaysUpdated()
 {
   // Synchronize the VR devices and resolve the promises in
   // mVRGetDisplaysPromises
   nsGlobalWindow* win = nsGlobalWindow::Cast(mWindow);
 
-  nsTArray<RefPtr<VRDisplay>> vrDisplays;
-  if (win->UpdateVRDisplays(vrDisplays)) {
-    for (auto p : mVRGetDisplaysPromises) {
+  nsTArray<RefPtr<VRDisplay>> vrDisplays;
+  if (win->UpdateVRDisplays(vrDisplays)) {
+    for (auto p : mVRGetDisplaysPromises) {
       p->MaybeResolve(vrDisplays);
     }
   } else {
     for (auto p : mVRGetDisplaysPromises) {
       p->MaybeReject(NS_ERROR_FAILURE);
     }
   }
   mVRGetDisplaysPromises.Clear();
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -943,16 +943,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(onvrdisplayconnect, "onvrdisplayconnect")
+GK_ATOM(onvrdisplaydisconnect, "onvrdisplaydisconnect")
+GK_ATOM(onvrdisplaypresentchange, "onvrdisplaypresentchange")
 GK_ATOM(onwebkitAnimationEnd, "onwebkitAnimationEnd")
 GK_ATOM(onwebkitAnimationIteration, "onwebkitAnimationIteration")
 GK_ATOM(onwebkitAnimationStart, "onwebkitAnimationStart")
 GK_ATOM(onwebkitTransitionEnd, "onwebkitTransitionEnd")
 GK_ATOM(onwebkitanimationend, "onwebkitanimationend")
 GK_ATOM(onwebkitanimationiteration, "onwebkitanimationiteration")
 GK_ATOM(onwebkitanimationstart, "onwebkitanimationstart")
 GK_ATOM(onwebkittransitionend, "onwebkittransitionend")
--- 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"
@@ -1195,16 +1196,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),
@@ -1594,21 +1596,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();
@@ -1738,16 +1743,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
@@ -9879,16 +9886,34 @@ nsGlobalWindow::DisableGamepadUpdates()
     if (gamepadManager) {
       gamepadManager->RemoveListener(this);
     }
 #endif
   }
 }
 
 void
+nsGlobalWindow::EnableVRUpdates()
+{
+  MOZ_ASSERT(IsInnerWindow());
+
+  if (mHasVREvents && !mVREventObserver) {
+    mVREventObserver = new 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;
@@ -12928,16 +12953,17 @@ nsGlobalWindow::SuspendTimeouts(uint32_t
 
   if (!suspended) {
     nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
     if (ac) {
       for (uint32_t i = 0; i < mEnabledSensors.Length(); i++)
         ac->RemoveWindowListener(mEnabledSensors[i], this);
     }
     DisableGamepadUpdates();
+    DisableVRUpdates();
 
     // Freeze or suspend all of the workers for this window.
     if (aFreezeWorkers) {
       mozilla::dom::workers::FreezeWorkersForWindow(AsInner());
     } else {
       mozilla::dom::workers::SuspendWorkersForWindow(AsInner());
     }
 
@@ -13014,16 +13040,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);
     }
 
     // Restore all of the timeouts, using the stored time remaining
@@ -13204,16 +13231,35 @@ nsGlobalWindow::SetHasGamepadEventListen
 {
   MOZ_ASSERT(IsInnerWindow());
   mHasGamepad = aHasGamepad;
   if (aHasGamepad) {
     EnableGamepadUpdates();
   }
 }
 
+
+void
+nsGlobalWindow::EventListenerAdded(nsIAtom* aType)
+{
+  if (aType == nsGkAtoms::onvrdisplayconnect ||
+      aType == nsGkAtoms::onvrdisplaydisconnect ||
+      aType == nsGkAtoms::onvrdisplaypresentchange) {
+    NotifyVREventListenerAdded();
+  }
+}
+
+void
+nsGlobalWindow::NotifyVREventListenerAdded()
+{
+  MOZ_ASSERT(IsInnerWindow());
+  mHasVREvents = true;
+  EnableVRUpdates();
+}
+
 void
 nsGlobalWindow::EnableTimeChangeNotifications()
 {
   mozilla::time::AddWindowListener(AsInner());
 }
 
 void
 nsGlobalWindow::DisableTimeChangeNotifications()
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -118,16 +118,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,18 @@ 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;
+  void NotifyVREventListenerAdded();
+  virtual void EventListenerAdded(nsIAtom* aType) override;
 
   // nsIInterfaceRequestor
   NS_DECL_NSIINTERFACEREQUESTOR
 
   // WebIDL interface.
   already_AddRefed<nsPIDOMWindowOuter> IndexedGetterOuter(uint32_t aIndex);
   already_AddRefed<nsPIDOMWindowOuter> IndexedGetter(uint32_t aIndex);
 
@@ -796,16 +799,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())    \
@@ -1761,16 +1769,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;
@@ -1906,16 +1918,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/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -594,17 +594,28 @@ WINDOW_ONLY_EVENT(deviceproximity,
 WINDOW_ONLY_EVENT(userproximity,
                   eUserProximity,
                   EventNameType_None,
                   eBasicEventClass)
 WINDOW_ONLY_EVENT(devicelight,
                   eDeviceLight,
                   EventNameType_None,
                   eBasicEventClass)
-
+WINDOW_ONLY_EVENT(vrdisplayconnect,
+                  eVRDisplayConnect,
+                  EventNameType_None,
+                  eBasicEventClass)
+WINDOW_ONLY_EVENT(vrdisplaydisconnect,
+                  eVRDisplayDisconnect,
+                  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,72 @@
+/* -*- 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 "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "VRManagerChild.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace gfx;
+
+/**
+ * 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());
+
+  VRManagerChild* vmc = VRManagerChild::Get();
+  if (vmc) {
+    vmc->AddListener(this);
+  }
+}
+
+VREventObserver::~VREventObserver()
+{
+  VRManagerChild* vmc = VRManagerChild::Get();
+  if (vmc) {
+    vmc->RemoveListener(this);
+  }
+}
+
+void
+VREventObserver::NotifyVRDisplayConnect()
+{
+  if (mWindow->AsInner()->IsCurrentInnerWindow()) {
+    MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+    mWindow->GetOuterWindow()->DispatchCustomEvent(
+      NS_LITERAL_STRING("vrdisplayconnected"));
+  }
+}
+
+void
+VREventObserver::NotifyVRDisplayDisconnect()
+{
+  if (mWindow->AsInner()->IsCurrentInnerWindow()) {
+    MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+    mWindow->GetOuterWindow()->DispatchCustomEvent(
+      NS_LITERAL_STRING("vrdisplaydisconnected"));
+  }
+}
+
+void
+VREventObserver::NotifyVRDisplayPresentChange()
+{
+  if (mWindow->AsInner()->IsCurrentInnerWindow()) {
+    MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+    mWindow->GetOuterWindow()->DispatchCustomEvent(
+      NS_LITERAL_STRING("vrdisplaypresentchange"));
+  }
+}
+
+} // namespace dom
+} // namespace mozilla
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 NotifyVRDisplayConnect();
+  void NotifyVRDisplayDisconnect();
+  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; 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
@@ -498,11 +498,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 onvrdisplayconnect;
+  [Pref="dom.vr.enabled"]
+  attribute EventHandler onvrdisplaydisconnect;
+  [Pref="dom.vr.enabled"]
+  attribute EventHandler onvrdisplaypresentchange;
+};
+
 Window implements ChromeWindow;
 Window implements GlobalFetch;
 Window implements ImageBitmapFactories;
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -136,17 +136,17 @@ VRManager::NotifyVsync(const TimeStamp& 
   for (auto iter = mVRDisplays.Iter(); !iter.Done(); iter.Next()) {
     gfx::VRDisplayHost* display = iter.UserData();
     display->NotifyVSync();
   }
 
   if (bHaveEventListener) {
     // If content has set an EventHandler to be notified of VR display events
     // we must continually refresh the VR display enumeration to check
-    // for events that we must fire such as Window.onvrdisplayconnected
+    // for events that we must fire such as Window.onvrdisplayconnect
     // Note that enumeration itself may activate display hardware, such
     // as Oculus, so we only do this when we know we are displaying content
     // that is looking for VR displays.
     if (mLastRefreshTime.IsNull()) {
       // This is the first vsync, must refresh VR displays
       RefreshVRDisplays();
     } else {
       // We don't have to do this every frame, so check if we
--- a/gfx/vr/ipc/PVRManager.ipdl
+++ b/gfx/vr/ipc/PVRManager.ipdl
@@ -45,17 +45,17 @@ parent:
   async RefreshDisplays();
 
   // Reset the sensor of the display identified by aDisplayID so that the current
   // sensor state is the "Zero" position.
   async ResetSensor(uint32_t aDisplayID);
 
   sync GetSensorState(uint32_t aDisplayID) returns(VRHMDSensorState aState);
   sync GetImmediateSensorState(uint32_t aDisplayID) returns(VRHMDSensorState aState);
-  async SetHaveEventListener(bool aHaveEventListener);
+  sync SetHaveEventListener(bool aHaveEventListener);
 
 child:
 
   async ParentAsyncMessages(AsyncParentMessageData[] aMessages);
 
   // Notify children of updated VR display enumeration and details.  This will
   // be sent to all children when the parent receives RefreshDisplays, even
   // if no changes have been detected.  This ensures that Promises exposed
--- a/gfx/vr/ipc/VRManagerChild.cpp
+++ b/gfx/vr/ipc/VRManagerChild.cpp
@@ -6,21 +6,28 @@
  * 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"
+#include "nsContentUtils.h"
 
 using layers::TextureClient;
 
+namespace {
+const nsTArray<RefPtr<dom::VREventObserver>>::index_type kNoIndex =
+  nsTArray<RefPtr<dom::VREventObserver> >::NoIndex;
+} // namespace
+
 namespace mozilla {
 namespace gfx {
 
 static StaticRefPtr<VRManagerChild> sVRManagerChildSingleton;
 static StaticRefPtr<VRManagerParent> sVRManagerParentSingleton;
 
 void ReleaseVRManagerParentSingleton() {
   sVRManagerParentSingleton = nullptr;
@@ -236,20 +243,20 @@ VRManagerChild::RecvUpdateDisplayInfo(ns
     // the promise returned by Navigator.GetVRDevices
     // can resolve.  This must happen even if no changes
     // to VRDisplays have been detected here.
     nav->NotifyVRDisplaysUpdated();
   }
   mNavigatorCallbacks.Clear();
 
   if (bDisplayConnected) {
-    FireDOMVRDisplayConnectedEvent();
+    FireDOMVRDisplayConnectEvent();
   }
   if (bDisplayDisconnected) {
-    FireDOMVRDisplayDisconnectedEvent();
+    FireDOMVRDisplayDisconnectEvent();
   }
 
   return true;
 }
 
 bool
 VRManagerChild::GetVRDisplays(nsTArray<RefPtr<VRDisplayClient>>& aDisplays)
 {
@@ -456,23 +463,80 @@ VRManagerChild::RunFrameRequestCallbacks
   callbacks.AppendElements(mFrameRequestCallbacks);
   mFrameRequestCallbacks.Clear();
   for (auto& callback : callbacks) {
     callback.mCallback->Call(timeStamp);
   }
 }
 
 void
-VRManagerChild::FireDOMVRDisplayConnectedEvent()
+VRManagerChild::FireDOMVRDisplayConnectEvent()
 {
+  nsContentUtils::AddScriptRunner(NewRunnableMethod(this,
+    &VRManagerChild::FireDOMVRDisplayConnectEventInternal));
 }
 
 void
-VRManagerChild::FireDOMVRDisplayDisconnectedEvent()
+VRManagerChild::FireDOMVRDisplayDisconnectEvent()
 {
+  nsContentUtils::AddScriptRunner(NewRunnableMethod(this,
+    &VRManagerChild::FireDOMVRDisplayDisconnectEventInternal));
 }
 
 void
 VRManagerChild::FireDOMVRDisplayPresentChangeEvent()
 {
+  nsContentUtils::AddScriptRunner(NewRunnableMethod(this,
+    &VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal));
 }
+
+void
+VRManagerChild::FireDOMVRDisplayConnectEventInternal()
+{
+  for (auto& listener : mListeners) {
+    listener->NotifyVRDisplayConnect();
+  }
+}
+
+void
+VRManagerChild::FireDOMVRDisplayDisconnectEventInternal()
+{
+  for (auto& listener : mListeners) {
+    listener->NotifyVRDisplayDisconnect();
+  }
+}
+
+void
+VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal()
+{
+  for (auto& listener : mListeners) {
+    listener->NotifyVRDisplayPresentChange();
+  }
+}
+
+void
+VRManagerChild::AddListener(dom::VREventObserver* aObserver)
+{
+  MOZ_ASSERT(aObserver);
+
+  if (mListeners.IndexOf(aObserver) != kNoIndex) {
+    return; // already exists
+  }
+
+  mListeners.AppendElement(aObserver);
+  if (mListeners.Length() == 1) {
+    Unused << SendSetHaveEventListener(true);
+  }
+}
+
+void
+VRManagerChild::RemoveListener(dom::VREventObserver* aObserver)
+{
+  MOZ_ASSERT(aObserver);
+
+  mListeners.RemoveElement(aObserver);
+  if (mListeners.IsEmpty()) {
+    Unused << SendSetHaveEventListener(false);
+  }
+}
+
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/vr/ipc/VRManagerChild.h
+++ b/gfx/vr/ipc/VRManagerChild.h
@@ -14,32 +14,38 @@
 #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;
 
 class VRManagerChild : public PVRManagerChild
                      , public layers::TextureForwarder
                      , public layers::ShmemAllocator
 {
 public:
   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 void InitSameProcess();
   static void InitWithGPUProcess(Endpoint<PVRManagerChild>&& aEndpoint);
   static bool InitForContent(Endpoint<PVRManagerChild>&& aEndpoint);
   static void ShutDown();
@@ -61,18 +67,18 @@ public:
   virtual MessageLoop* GetMessageLoop() const override { return mMessageLoop; }
   virtual base::ProcessId GetParentPid() const override { return OtherPid(); }
 
   nsresult ScheduleFrameRequestCallback(mozilla::dom::FrameRequestCallback& aCallback,
     int32_t *aHandle);
   void CancelFrameRequestCallback(int32_t aHandle);
   void RunFrameRequestCallbacks();
 
-  void FireDOMVRDisplayConnectedEvent();
-  void FireDOMVRDisplayDisconnectedEvent();
+  void FireDOMVRDisplayConnectEvent();
+  void FireDOMVRDisplayDisconnectEvent();
   void FireDOMVRDisplayPresentChangeEvent();
 
 protected:
   explicit VRManagerChild();
   ~VRManagerChild();
   void Destroy();
   static void DeferredDestroy(RefPtr<VRManagerChild> aVRManagerChild);
 
@@ -119,16 +125,20 @@ protected:
   {
     return OtherPid() == base::GetCurrentProcId();
   }
 
   friend class layers::CompositorBridgeChild;
 
 private:
 
+  void FireDOMVRDisplayConnectEventInternal();
+  void FireDOMVRDisplayDisconnectEventInternal();
+  void FireDOMVRDisplayPresentChangeEventInternal();
+
   void DeliverFence(uint64_t aTextureId, FenceHandle& aReleaseFenceHandle);
   /**
   * Notify id of Texture When host side end its use. Transaction id is used to
   * make sure if there is no newer usage.
   */
   void NotifyNotUsed(uint64_t aTextureId, uint64_t aFwdTransactionId);
 
   nsTArray<RefPtr<VRDisplayClient> > mDisplays;
@@ -142,16 +152,19 @@ 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;
   RefPtr<layers::SyncObject> mSyncObject;
--- a/widget/EventMessageList.h
+++ b/widget/EventMessageList.h
@@ -367,16 +367,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(eVRDisplayConnect)
+NS_EVENT_MESSAGE(eVRDisplayDisconnect)
+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)