Bug 1354206 - Prevent VRDisplay.requestAnimationFrame from succeeding after shutdown draft
authorKearwood Gilbert <kgilbert@mozilla.com>
Wed, 12 Apr 2017 16:22:29 -0700
changeset 561607 37b376b6a4fc4656a2fa741d13b37cd603385f2f
parent 561311 bc086e9044e6537d18d385f417248cdb4c14c3af
child 624029 518119739b1b0aecab71eb9586ecf128d6127646
push id53790
push userbmo:kgilbert@mozilla.com
push dateWed, 12 Apr 2017 23:23:08 +0000
bugs1354206
milestone55.0a1
Bug 1354206 - Prevent VRDisplay.requestAnimationFrame from succeeding after shutdown MozReview-Commit-ID: LDw9nH60VCm
dom/vr/VRDisplay.cpp
dom/vr/VRDisplay.h
--- a/dom/vr/VRDisplay.cpp
+++ b/dom/vr/VRDisplay.cpp
@@ -351,41 +351,46 @@ VRDisplay::WrapObject(JSContext* aCx, JS
 }
 
 VRDisplay::VRDisplay(nsPIDOMWindowInner* aWindow, gfx::VRDisplayClient* aClient)
   : DOMEventTargetHelper(aWindow)
   , mClient(aClient)
   , mDepthNear(0.01f) // Default value from WebVR Spec
   , mDepthFar(10000.0f) // Default value from WebVR Spec
   , mVRNavigationEventDepth(0)
+  , mShutdown(false)
 {
   const gfx::VRDisplayInfo& info = aClient->GetDisplayInfo();
   mDisplayId = info.GetDisplayID();
   mDisplayName = NS_ConvertASCIItoUTF16(info.GetDisplayName());
   mCapabilities = new VRDisplayCapabilities(aWindow, info.GetCapabilities());
   if (info.GetCapabilities() & gfx::VRDisplayCapabilityFlags::Cap_StageParameters) {
     mStageParameters = new VRStageParameters(aWindow,
                                              info.GetSittingToStandingTransform(),
                                              info.GetStageSize());
   }
   mozilla::HoldJSObjects(this);
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (MOZ_LIKELY(obs)) {
+    obs->AddObserver(this, "inner-window-destroyed", false);
+  }
 }
 
 VRDisplay::~VRDisplay()
 {
-  ExitPresentInternal();
+  MOZ_ASSERT(mShutdown);
   mozilla::DropJSObjects(this);
 }
 
 void
 VRDisplay::LastRelease()
 {
   // We don't want to wait for the CC to free up the presentation
   // for use in other documents, so we do this in LastRelease().
-  ExitPresentInternal();
+  Shutdown();
 }
 
 already_AddRefed<VREyeParameters>
 VRDisplay::GetEyeParameters(VREye aEye)
 {
   gfx::VRDisplayInfo::Eye eye = aEye == VREye::Left ? gfx::VRDisplayInfo::Eye_Left : gfx::VRDisplayInfo::Eye_Right;
   RefPtr<VREyeParameters> params =
     new VREyeParameters(GetParentObject(),
@@ -489,19 +494,16 @@ VRDisplay::RequestPresent(const nsTArray
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   RefPtr<Promise> promise = Promise::Create(global, aRv);
   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
 
-  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-  NS_ENSURE_TRUE(obs, nullptr);
-
   if (!EventStateManager::IsHandlingUserInput() &&
       aCallerType != CallerType::System &&
       !IsHandlingVRNavigationEvent() &&
       gfxPrefs::VRRequireGesture()) {
     // The WebVR API states that if called outside of a user gesture, the
     // promise must be rejected.  We allow VR presentations to start within
     // trusted events such as vrdisplayactivate, which triggers in response to
     // HMD proximity sensors and when navigating within a VR presentation.
@@ -511,24 +513,17 @@ VRDisplay::RequestPresent(const nsTArray
     // on a first-come-first-serve basis.
     // If this Javascript context is presenting, then we can replace our
     // presentation with a new one containing new layers but we should never
     // replace the presentation of another context.
     promise->MaybeRejectWithUndefined();
   } else {
     mPresentation = mClient->BeginPresentation(aLayers);
     mFrameInfo.Clear();
-
-    nsresult rv = obs->AddObserver(this, "inner-window-destroyed", false);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      mPresentation = nullptr;
-      promise->MaybeRejectWithUndefined();
-    } else {
-      promise->MaybeResolve(JS::UndefinedHandleValue);
-    }
+    promise->MaybeResolve(JS::UndefinedHandleValue);
   }
   return promise.forget();
 }
 
 NS_IMETHODIMP
 VRDisplay::Observe(nsISupports* aSubject, const char* aTopic,
   const char16_t* aData)
 {
@@ -538,17 +533,17 @@ VRDisplay::Observe(nsISupports* aSubject
     nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
     NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
 
     uint64_t innerID;
     nsresult rv = wrapper->GetData(&innerID);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (!GetOwner() || GetOwner()->WindowID() == innerID) {
-      ExitPresentInternal();
+      Shutdown();
     }
 
     return NS_OK;
   }
 
   // This should not happen.
   return NS_ERROR_FAILURE;
 }
@@ -580,16 +575,27 @@ VRDisplay::ExitPresent(ErrorResult& aRv)
 
 void
 VRDisplay::ExitPresentInternal()
 {
   mPresentation = nullptr;
 }
 
 void
+VRDisplay::Shutdown()
+{
+  mShutdown = true;
+  ExitPresentInternal();
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (MOZ_LIKELY(obs)) {
+    obs->RemoveObserver(this, "inner-window-destroyed");
+  }
+}
+
+void
 VRDisplay::GetLayers(nsTArray<VRLayer>& result)
 {
   if (mPresentation) {
     mPresentation->GetDOMLayers(result);
   } else {
     result = nsTArray<VRLayer>();
   }
 }
@@ -602,16 +608,20 @@ VRDisplay::SubmitFrame()
   }
   mFrameInfo.Clear();
 }
 
 int32_t
 VRDisplay::RequestAnimationFrame(FrameRequestCallback& aCallback,
 ErrorResult& aError)
 {
+  if (mShutdown) {
+    return 0;
+  }
+
   gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
 
   int32_t handle;
   aError = vm->ScheduleFrameRequestCallback(aCallback, &handle);
   return handle;
 }
 
 void
--- a/dom/vr/VRDisplay.h
+++ b/dom/vr/VRDisplay.h
@@ -340,16 +340,17 @@ public:
   bool IsHandlingVRNavigationEvent();
 
 protected:
   VRDisplay(nsPIDOMWindowInner* aWindow, gfx::VRDisplayClient* aClient);
   virtual ~VRDisplay();
   virtual void LastRelease() override;
 
   void ExitPresentInternal();
+  void Shutdown();
   void UpdateFrameInfo();
 
   RefPtr<gfx::VRDisplayClient> mClient;
 
   uint32_t mDisplayId;
   nsString mDisplayName;
 
   RefPtr<VRDisplayCapabilities> mCapabilities;
@@ -367,14 +368,15 @@ protected:
   * frame.  Subsequent calls before the next SubmitFrame or ExitPresent call
   * will use these cached values.
   */
   VRFrameInfo mFrameInfo;
 
   // Time at which we began expecting VR navigation.
   TimeStamp mHandlingVRNavigationEventStart;
   int32_t mVRNavigationEventDepth;
+  bool mShutdown;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif