Bug 1335606 - Add 'display' value to Navigator.vrdisplayconnect, Navigator.vrdisplaydisconnect, and Navigator.vrdisplaypresentchange events r=smaug
authorKearwood Gilbert <kgilbert@mozilla.com>
Tue, 14 Feb 2017 11:21:09 -0800
changeset 372765 af8dc8f5668cf9832d1a25f00e38448cf77adaea
parent 372764 4b77e19251aed0e5f4d9806a22f8dce144068c61
child 372766 b53a9cea93fd598ea2baa8314e5cad63cfa4bf5d
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1335606
milestone54.0a1
Bug 1335606 - Add 'display' value to Navigator.vrdisplayconnect, Navigator.vrdisplaydisconnect, and Navigator.vrdisplaypresentchange events r=smaug MozReview-Commit-ID: FLZ7u98mqqi
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/vr/VREventObserver.cpp
dom/vr/VREventObserver.h
gfx/vr/VRDisplayClient.cpp
gfx/vr/ipc/VRManagerChild.cpp
gfx/vr/ipc/VRManagerChild.h
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -10439,18 +10439,20 @@ nsGlobalWindow::EnableVRUpdates()
     mVREventObserver = new VREventObserver(this);
   }
 }
 
 void
 nsGlobalWindow::DisableVRUpdates()
 {
   MOZ_ASSERT(IsInnerWindow());
-
-  mVREventObserver = nullptr;
+  if (mVREventObserver) {
+    mVREventObserver->DisconnectFromOwner();
+    mVREventObserver = nullptr;
+  }
 }
 
 void
 nsGlobalWindow::SetChromeEventHandler(EventTarget* aChromeEventHandler)
 {
   MOZ_ASSERT(IsOuterWindow());
 
   SetChromeEventHandlerInternal(aChromeEventHandler);
@@ -13467,65 +13469,158 @@ nsGlobalWindow::NotifyActiveVRDisplaysCh
     mNavigator->NotifyActiveVRDisplaysChanged();
   }
 }
 
 void
 nsGlobalWindow::DispatchVRDisplayActivate(uint32_t aDisplayID,
                                           mozilla::dom::VRDisplayEventReason aReason)
 {
+  // Search for the display identified with aDisplayID and fire the
+  // event if found.
   for (auto display : mVRDisplays) {
     if (display->DisplayId() == aDisplayID
         && !display->IsAnyPresenting()) {
       // We only want to trigger this event if nobody is presenting to the
       // display already.
 
       VRDisplayEventInit init;
-      init.mBubbles = true;
+      init.mBubbles = false;
       init.mCancelable = false;
       init.mDisplay = display;
       init.mReason.Construct(aReason);
 
       RefPtr<VRDisplayEvent> event =
         VRDisplayEvent::Constructor(this,
                                     NS_LITERAL_STRING("vrdisplayactivate"),
                                     init);
       // vrdisplayactivate is a trusted event, allowing VRDisplay.requestPresent
       // to be used in response to link traversal, user request (chrome UX), and
       // HMD mounting detection sensors.
       event->SetTrusted(true);
       bool defaultActionEnabled;
       Unused << DispatchEvent(event, &defaultActionEnabled);
-      break;
+      // Once we dispatch the event, we must not access any members as an event
+      // listener can do anything, including closing windows.
+      return;
     }
   }
 }
 
 void
 nsGlobalWindow::DispatchVRDisplayDeactivate(uint32_t aDisplayID,
                                             mozilla::dom::VRDisplayEventReason aReason)
 {
+  // Search for the display identified with aDisplayID and fire the
+  // event if found.
   for (auto display : mVRDisplays) {
     if (display->DisplayId() == aDisplayID && display->IsPresenting()) {
       // We only want to trigger this event to content that is presenting to
       // the display already.
 
       VRDisplayEventInit init;
-      init.mBubbles = true;
+      init.mBubbles = false;
       init.mCancelable = false;
       init.mDisplay = display;
       init.mReason.Construct(aReason);
 
       RefPtr<VRDisplayEvent> event =
         VRDisplayEvent::Constructor(this,
                                     NS_LITERAL_STRING("vrdisplaydeactivate"),
                                     init);
+      event->SetTrusted(true);
+      bool defaultActionEnabled;
+      Unused << DispatchEvent(event, &defaultActionEnabled);
+      // Once we dispatch the event, we must not access any members as an event
+      // listener can do anything, including closing windows.
+      return;
+    }
+  }
+}
+
+void
+nsGlobalWindow::DispatchVRDisplayConnect(uint32_t aDisplayID)
+{
+  // Search for the display identified with aDisplayID and fire the
+  // event if found.
+  for (auto display : mVRDisplays) {
+    if (display->DisplayId() == aDisplayID) {
+      // Fire event even if not presenting to the display.
+      VRDisplayEventInit init;
+      init.mBubbles = false;
+      init.mCancelable = false;
+      init.mDisplay = display;
+      // VRDisplayEvent.reason is not set for vrdisplayconnect
+
+      RefPtr<VRDisplayEvent> event =
+        VRDisplayEvent::Constructor(this,
+                                    NS_LITERAL_STRING("vrdisplayconnect"),
+                                    init);
+      event->SetTrusted(true);
       bool defaultActionEnabled;
       Unused << DispatchEvent(event, &defaultActionEnabled);
-      break;
+      // Once we dispatch the event, we must not access any members as an event
+      // listener can do anything, including closing windows.
+      return;
+    }
+  }
+}
+
+void
+nsGlobalWindow::DispatchVRDisplayDisconnect(uint32_t aDisplayID)
+{
+  // Search for the display identified with aDisplayID and fire the
+  // event if found.
+  for (auto display : mVRDisplays) {
+    if (display->DisplayId() == aDisplayID) {
+      // Fire event even if not presenting to the display.
+      VRDisplayEventInit init;
+      init.mBubbles = false;
+      init.mCancelable = false;
+      init.mDisplay = display;
+      // VRDisplayEvent.reason is not set for vrdisplaydisconnect
+
+      RefPtr<VRDisplayEvent> event =
+        VRDisplayEvent::Constructor(this,
+                                    NS_LITERAL_STRING("vrdisplaydisconnect"),
+                                    init);
+      event->SetTrusted(true);
+      bool defaultActionEnabled;
+      Unused << DispatchEvent(event, &defaultActionEnabled);
+      // Once we dispatch the event, we must not access any members as an event
+      // listener can do anything, including closing windows.
+      return;
+    }
+  }
+}
+
+void
+nsGlobalWindow::DispatchVRDisplayPresentChange(uint32_t aDisplayID)
+{
+  // Search for the display identified with aDisplayID and fire the
+  // event if found.
+  for (auto display : mVRDisplays) {
+    if (display->DisplayId() == aDisplayID) {
+      // Fire event even if not presenting to the display.
+      VRDisplayEventInit init;
+      init.mBubbles = false;
+      init.mCancelable = false;
+      init.mDisplay = display;
+      // VRDisplayEvent.reason is not set for vrdisplaypresentchange
+
+      RefPtr<VRDisplayEvent> event =
+        VRDisplayEvent::Constructor(this,
+                                    NS_LITERAL_STRING("vrdisplaypresentchange"),
+                                    init);
+      event->SetTrusted(true);
+      bool defaultActionEnabled;
+      Unused << DispatchEvent(event, &defaultActionEnabled);
+      // Once we dispatch the event, we must not access any members as an event
+      // listener can do anything, including closing windows.
+      return;
     }
   }
 }
 
 // nsGlobalChromeWindow implementation
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalChromeWindow)
 
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -763,16 +763,19 @@ public:
   // Inner windows only.
   // Called to inform that the set of active VR displays has changed.
   void NotifyActiveVRDisplaysChanged();
 
   void DispatchVRDisplayActivate(uint32_t aDisplayID,
                                  mozilla::dom::VRDisplayEventReason aReason);
   void DispatchVRDisplayDeactivate(uint32_t aDisplayID,
                                    mozilla::dom::VRDisplayEventReason aReason);
+  void DispatchVRDisplayConnect(uint32_t aDisplayID);
+  void DispatchVRDisplayDisconnect(uint32_t aDisplayID);
+  void DispatchVRDisplayPresentChange(uint32_t aDisplayID);
 
 #define EVENT(name_, id_, type_, struct_)                                     \
   mozilla::dom::EventHandlerNonNull* GetOn##name_()                           \
   {                                                                           \
     mozilla::EventListenerManager* elm = GetExistingListenerManager();        \
     return elm ? elm->GetEventHandler(nsGkAtoms::on##name_, EmptyString())    \
                : nullptr;                                                     \
   }                                                                           \
@@ -2022,17 +2025,17 @@ 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;
+  RefPtr<mozilla::dom::VREventObserver> mVREventObserver;
 
   friend class nsDOMScriptableHelper;
   friend class nsDOMWindowUtils;
   friend class mozilla::dom::PostMessageEvent;
   friend class DesktopNotification;
   friend class mozilla::dom::TimeoutManager;
   friend class IdleRequestExecutor;
 
--- a/dom/vr/VREventObserver.cpp
+++ b/dom/vr/VREventObserver.cpp
@@ -28,93 +28,102 @@ VREventObserver::VREventObserver(nsGloba
   VRManagerChild* vmc = VRManagerChild::Get();
   if (vmc) {
     vmc->AddListener(this);
   }
 }
 
 VREventObserver::~VREventObserver()
 {
+  DisconnectFromOwner();
+}
+
+void
+VREventObserver::DisconnectFromOwner()
+{
+  // In the event that nsGlobalWindow is deallocated, VREventObserver may
+  // still be AddRef'ed elsewhere.  Ensure that we don't UAF by
+  // dereferencing mWindow.
+  mWindow = nullptr;
+
+  // Unregister from VRManagerChild
   VRManagerChild* vmc = VRManagerChild::Get();
   if (vmc) {
     vmc->RemoveListener(this);
   }
 }
 
 void
 VREventObserver::NotifyVRDisplayMounted(uint32_t aDisplayID)
 {
-  if (mWindow->AsInner()->IsCurrentInnerWindow()) {
+  if (mWindow && mWindow->AsInner()->IsCurrentInnerWindow()) {
     MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
     mWindow->DispatchVRDisplayActivate(aDisplayID,
                                        VRDisplayEventReason::Mounted);
   }
 }
 
 void
 VREventObserver::NotifyVRDisplayNavigation(uint32_t aDisplayID)
 {
-  if (mWindow->AsInner()->IsCurrentInnerWindow()) {
+  if (mWindow && mWindow->AsInner()->IsCurrentInnerWindow()) {
     MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
     mWindow->DispatchVRDisplayActivate(aDisplayID,
                                        VRDisplayEventReason::Navigation);
   }
 }
 
 void
 VREventObserver::NotifyVRDisplayRequested(uint32_t aDisplayID)
 {
-  if (mWindow->AsInner()->IsCurrentInnerWindow()) {
+  if (mWindow && mWindow->AsInner()->IsCurrentInnerWindow()) {
     MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
     mWindow->DispatchVRDisplayActivate(aDisplayID,
                                        VRDisplayEventReason::Requested);
   }
 }
 
 void
 VREventObserver::NotifyVRDisplayUnmounted(uint32_t aDisplayID)
 {
-  if (mWindow->AsInner()->IsCurrentInnerWindow()) {
+  if (mWindow && mWindow->AsInner()->IsCurrentInnerWindow()) {
     MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
     mWindow->DispatchVRDisplayDeactivate(aDisplayID,
                                          VRDisplayEventReason::Unmounted);
   }
 }
 
 void
-VREventObserver::NotifyVRDisplayConnect()
+VREventObserver::NotifyVRDisplayConnect(uint32_t aDisplayID)
 {
   /**
    * We do not call nsGlobalWindow::NotifyActiveVRDisplaysChanged here, as we
    * can assume that a newly enumerated display is not presenting WebVR
    * content.
    */
-  if (mWindow->AsInner()->IsCurrentInnerWindow()) {
+  if (mWindow && mWindow->AsInner()->IsCurrentInnerWindow()) {
     MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
-    mWindow->GetOuterWindow()->DispatchCustomEvent(
-      NS_LITERAL_STRING("vrdisplayconnected"));
+    mWindow->DispatchVRDisplayConnect(aDisplayID);
   }
 }
 
 void
-VREventObserver::NotifyVRDisplayDisconnect()
+VREventObserver::NotifyVRDisplayDisconnect(uint32_t aDisplayID)
 {
-  if (mWindow->AsInner()->IsCurrentInnerWindow()) {
+  if (mWindow && mWindow->AsInner()->IsCurrentInnerWindow()) {
     mWindow->NotifyActiveVRDisplaysChanged();
     MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
-    mWindow->GetOuterWindow()->DispatchCustomEvent(
-      NS_LITERAL_STRING("vrdisplaydisconnected"));
+    mWindow->DispatchVRDisplayDisconnect(aDisplayID);
   }
 }
 
 void
-VREventObserver::NotifyVRDisplayPresentChange()
+VREventObserver::NotifyVRDisplayPresentChange(uint32_t aDisplayID)
 {
-  if (mWindow->AsInner()->IsCurrentInnerWindow()) {
+  if (mWindow && mWindow->AsInner()->IsCurrentInnerWindow()) {
     mWindow->NotifyActiveVRDisplaysChanged();
     MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
-    mWindow->GetOuterWindow()->DispatchCustomEvent(
-      NS_LITERAL_STRING("vrdisplaypresentchange"));
+    mWindow->DispatchVRDisplayPresentChange(aDisplayID);
   }
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/vr/VREventObserver.h
+++ b/dom/vr/VREventObserver.h
@@ -3,37 +3,42 @@
 /* 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
 
 #include "mozilla/dom/VRDisplayEventBinding.h"
+#include "nsISupportsImpl.h" // for NS_INLINE_DECL_REFCOUNTING
 
 class nsGlobalWindow;
 
 namespace mozilla {
 namespace dom {
 
 class VREventObserver final
 {
 public:
-  ~VREventObserver();
+  NS_INLINE_DECL_REFCOUNTING(VREventObserver)
   explicit VREventObserver(nsGlobalWindow* aGlobalWindow);
 
   void NotifyVRDisplayMounted(uint32_t aDisplayID);
   void NotifyVRDisplayUnmounted(uint32_t aDisplayID);
   void NotifyVRDisplayNavigation(uint32_t aDisplayID);
   void NotifyVRDisplayRequested(uint32_t aDisplayID);
-  void NotifyVRDisplayConnect();
-  void NotifyVRDisplayDisconnect();
-  void NotifyVRDisplayPresentChange();
+  void NotifyVRDisplayConnect(uint32_t aDisplayID);
+  void NotifyVRDisplayDisconnect(uint32_t aDisplayID);
+  void NotifyVRDisplayPresentChange(uint32_t aDisplayID);
+
+  void DisconnectFromOwner();
 
 private:
+  ~VREventObserver();
+
   // Weak pointer, instance is owned by mWindow.
   nsGlobalWindow* MOZ_NON_OWNING_REF mWindow;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_VREventObserver_h
--- a/gfx/vr/VRDisplayClient.cpp
+++ b/gfx/vr/VRDisplayClient.cpp
@@ -108,17 +108,17 @@ VRDisplayClient::NotifyVsync()
   if (bShouldCallback) {
     vm->RunFrameRequestCallbacks();
     mLastVSyncTime = TimeStamp::Now();
   }
 
   // Check if we need to trigger onVRDisplayPresentChange event
   if (bLastEventWasPresenting != isPresenting) {
     bLastEventWasPresenting = isPresenting;
-    vm->FireDOMVRDisplayPresentChangeEvent();
+    vm->FireDOMVRDisplayPresentChangeEvent(mDisplayInfo.mDisplayID);
   }
 
   // Check if we need to trigger onvrdisplayactivate event
   if (!bLastEventWasMounted && mDisplayInfo.mIsMounted) {
     bLastEventWasMounted = true;
     vm->FireDOMVRDisplayMountedEvent(mDisplayInfo.mDisplayID);
   }
 
--- a/gfx/vr/ipc/VRManagerChild.cpp
+++ b/gfx/vr/ipc/VRManagerChild.cpp
@@ -198,67 +198,69 @@ VRManagerChild::DeallocPVRLayerChild(PVR
 {
   delete actor;
   return true;
 }
 
 void
 VRManagerChild::UpdateDisplayInfo(nsTArray<VRDisplayInfo>& aDisplayUpdates)
 {
-  bool bDisplayConnected = false;
-  bool bDisplayDisconnected = false;
+  nsTArray<uint32_t> disconnectedDisplays;
+  nsTArray<uint32_t> connectedDisplays;
 
   // Check if any displays have been disconnected
   for (auto& display : mDisplays) {
     bool found = false;
     for (auto& displayUpdate : aDisplayUpdates) {
       if (display->GetDisplayInfo().GetDisplayID() == displayUpdate.GetDisplayID()) {
         found = true;
         break;
       }
     }
     if (!found) {
       display->NotifyDisconnected();
-      bDisplayDisconnected = true;
+      disconnectedDisplays.AppendElement(display->GetDisplayInfo().GetDisplayID());
     }
   }
 
   // mDisplays could be a hashed container for more scalability, but not worth
   // it now as we expect < 10 entries.
   nsTArray<RefPtr<VRDisplayClient>> displays;
   for (VRDisplayInfo& displayUpdate : aDisplayUpdates) {
     bool isNewDisplay = true;
     for (auto& display : mDisplays) {
       const VRDisplayInfo& prevInfo = display->GetDisplayInfo();
       if (prevInfo.GetDisplayID() == displayUpdate.GetDisplayID()) {
         if (displayUpdate.GetIsConnected() && !prevInfo.GetIsConnected()) {
-          bDisplayConnected = true;
+          connectedDisplays.AppendElement(displayUpdate.GetDisplayID());
         }
         if (!displayUpdate.GetIsConnected() && prevInfo.GetIsConnected()) {
-          bDisplayDisconnected = true;
+          disconnectedDisplays.AppendElement(displayUpdate.GetDisplayID());
         }
         display->UpdateDisplayInfo(displayUpdate);
         displays.AppendElement(display);
         isNewDisplay = false;
         break;
       }
     }
     if (isNewDisplay) {
       displays.AppendElement(new VRDisplayClient(displayUpdate));
-      bDisplayConnected = true;
+      connectedDisplays.AppendElement(displayUpdate.GetDisplayID());
     }
   }
 
   mDisplays = displays;
 
-  if (bDisplayConnected) {
-    FireDOMVRDisplayConnectEvent();
+  // We wish to fire the events only after mDisplays is updated
+  for (uint32_t displayID : disconnectedDisplays) {
+    FireDOMVRDisplayDisconnectEvent(displayID);
   }
-  if (bDisplayDisconnected) {
-    FireDOMVRDisplayDisconnectEvent();
+
+  for (uint32_t displayID : connectedDisplays) {
+    FireDOMVRDisplayConnectEvent(displayID);
   }
 
   mDisplaysInitialized = true;
 }
 
 mozilla::ipc::IPCResult
 VRManagerChild::RecvUpdateDisplayInfo(nsTArray<VRDisplayInfo>&& aDisplayUpdates)
 {
@@ -517,73 +519,91 @@ void
 VRManagerChild::FireDOMVRDisplayUnmountedEvent(uint32_t aDisplayID)
 {
   nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>(this,
     &VRManagerChild::FireDOMVRDisplayUnmountedEventInternal,
     aDisplayID));
 }
 
 void
-VRManagerChild::FireDOMVRDisplayConnectEvent()
+VRManagerChild::FireDOMVRDisplayConnectEvent(uint32_t aDisplayID)
 {
-  nsContentUtils::AddScriptRunner(NewRunnableMethod(this,
-    &VRManagerChild::FireDOMVRDisplayConnectEventInternal));
+  nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>(this,
+    &VRManagerChild::FireDOMVRDisplayConnectEventInternal,
+    aDisplayID));
 }
 
 void
-VRManagerChild::FireDOMVRDisplayDisconnectEvent()
+VRManagerChild::FireDOMVRDisplayDisconnectEvent(uint32_t aDisplayID)
 {
-  nsContentUtils::AddScriptRunner(NewRunnableMethod(this,
-    &VRManagerChild::FireDOMVRDisplayDisconnectEventInternal));
+  nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>(this,
+    &VRManagerChild::FireDOMVRDisplayDisconnectEventInternal,
+    aDisplayID));
 }
 
 void
-VRManagerChild::FireDOMVRDisplayPresentChangeEvent()
+VRManagerChild::FireDOMVRDisplayPresentChangeEvent(uint32_t aDisplayID)
 {
-  nsContentUtils::AddScriptRunner(NewRunnableMethod(this,
-    &VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal));
+  nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>(this,
+    &VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal,
+    aDisplayID));
 }
 
 void
 VRManagerChild::FireDOMVRDisplayMountedEventInternal(uint32_t aDisplayID)
 {
-  for (auto& listener : mListeners) {
+  // Iterate over a copy of mListeners, as dispatched events may modify it.
+  nsTArray<RefPtr<dom::VREventObserver>> listeners;
+  listeners = mListeners;
+  for (auto& listener : listeners) {
     listener->NotifyVRDisplayMounted(aDisplayID);
   }
 }
 
 void
 VRManagerChild::FireDOMVRDisplayUnmountedEventInternal(uint32_t aDisplayID)
 {
-  for (auto& listener : mListeners) {
+  // Iterate over a copy of mListeners, as dispatched events may modify it.
+  nsTArray<RefPtr<dom::VREventObserver>> listeners;
+  listeners = mListeners;
+  for (auto& listener : listeners) {
     listener->NotifyVRDisplayUnmounted(aDisplayID);
   }
 }
 
 void
-VRManagerChild::FireDOMVRDisplayConnectEventInternal()
+VRManagerChild::FireDOMVRDisplayConnectEventInternal(uint32_t aDisplayID)
 {
-  for (auto& listener : mListeners) {
-    listener->NotifyVRDisplayConnect();
+  // Iterate over a copy of mListeners, as dispatched events may modify it.
+  nsTArray<RefPtr<dom::VREventObserver>> listeners;
+  listeners = mListeners;
+  for (auto& listener : listeners) {
+    listener->NotifyVRDisplayConnect(aDisplayID);
   }
 }
 
 void
-VRManagerChild::FireDOMVRDisplayDisconnectEventInternal()
+VRManagerChild::FireDOMVRDisplayDisconnectEventInternal(uint32_t aDisplayID)
 {
-  for (auto& listener : mListeners) {
-    listener->NotifyVRDisplayDisconnect();
+  // Iterate over a copy of mListeners, as dispatched events may modify it.
+  nsTArray<RefPtr<dom::VREventObserver>> listeners;
+  listeners = mListeners;
+  for (auto& listener : listeners) {
+    listener->NotifyVRDisplayDisconnect(aDisplayID);
   }
 }
 
 void
-VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal()
+VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal(uint32_t aDisplayID)
 {
-  for (auto& listener : mListeners) {
-    listener->NotifyVRDisplayPresentChange();
+  // Iterate over a copy of mListeners, as dispatched events may modify it.
+  nsTArray<RefPtr<dom::VREventObserver>> listeners;
+  listeners = mListeners;
+  for (auto& listener : listeners) {
+    listener->NotifyVRDisplayPresentChange(aDisplayID);
   }
 }
 
 void
 VRManagerChild::AddListener(dom::VREventObserver* aObserver)
 {
   MOZ_ASSERT(aObserver);
 
--- a/gfx/vr/ipc/VRManagerChild.h
+++ b/gfx/vr/ipc/VRManagerChild.h
@@ -76,19 +76,19 @@ public:
   nsresult ScheduleFrameRequestCallback(mozilla::dom::FrameRequestCallback& aCallback,
     int32_t *aHandle);
   void CancelFrameRequestCallback(int32_t aHandle);
   void RunFrameRequestCallbacks();
 
   void UpdateDisplayInfo(nsTArray<VRDisplayInfo>& aDisplayUpdates);
   void FireDOMVRDisplayMountedEvent(uint32_t aDisplayID);
   void FireDOMVRDisplayUnmountedEvent(uint32_t aDisplayID);
-  void FireDOMVRDisplayConnectEvent();
-  void FireDOMVRDisplayDisconnectEvent();
-  void FireDOMVRDisplayPresentChangeEvent();
+  void FireDOMVRDisplayConnectEvent(uint32_t aDisplayID);
+  void FireDOMVRDisplayDisconnectEvent(uint32_t aDisplayID);
+  void FireDOMVRDisplayPresentChangeEvent(uint32_t aDisplayID);
 
   virtual void HandleFatalError(const char* aName, const char* aMsg) const override;
 
 protected:
   explicit VRManagerChild();
   ~VRManagerChild();
   void Destroy();
   static void DeferredDestroy(RefPtr<VRManagerChild> aVRManagerChild);
@@ -136,19 +136,19 @@ protected:
   }
 
   friend class layers::CompositorBridgeChild;
 
 private:
 
   void FireDOMVRDisplayMountedEventInternal(uint32_t aDisplayID);
   void FireDOMVRDisplayUnmountedEventInternal(uint32_t aDisplayID);
-  void FireDOMVRDisplayConnectEventInternal();
-  void FireDOMVRDisplayDisconnectEventInternal();
-  void FireDOMVRDisplayPresentChangeEventInternal();
+  void FireDOMVRDisplayConnectEventInternal(uint32_t aDisplayID);
+  void FireDOMVRDisplayDisconnectEventInternal(uint32_t aDisplayID);
+  void FireDOMVRDisplayPresentChangeEventInternal(uint32_t aDisplayID);
   /**
   * 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;
   bool mDisplaysInitialized;
@@ -162,18 +162,17 @@ 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;
+  nsTArray<RefPtr<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;