Bug 1477926 - Cancel autoplay-media permission requests when any media element waiting on a permission request plays. r?alwu draft
authorChris Pearce <cpearce@mozilla.com>
Thu, 26 Jul 2018 11:33:50 +1200
changeset 826185 e0cba4a7248f9149eac60a371cc00a748ede7776
parent 826132 b1c100e2b29a8e671678eb537ba69badc249c2fe
child 826186 eb8ff7d45f82efa05bb4e55bfbef7b6ccedcf0c7
push id118257
push userbmo:cpearce@mozilla.com
push dateFri, 03 Aug 2018 04:44:24 +0000
reviewersalwu
bugs1477926
milestone63.0a1
Bug 1477926 - Cancel autoplay-media permission requests when any media element waiting on a permission request plays. r?alwu Currently if a media element starts playing audibly when we're showing the doorhanger, we dismiss the doorhanger. But we don't yet dismiss the doorhanger when the media waiting on the doorhanger starts playing inaudibly. We also need to hande the case where we have multiple media elements waiting on the doorhanger. One media element may play by either script muting it and calling play() again, or the user may click on its play button. Then we hide the doorhanger, but what about the other media elements waiting on the door hanger? If the user clicked on the play button, that signals intent to play that video, but not others. So in that case we want to just reject the play() promises for media elements still waiting on the door hanger. So this patch changes HTMLMediaElement to tell the AutoplayPermissionManager to reject all requests its managing if a media element starts to play but it was waiting on a doorhanger. We also dispatch a different observer service notification to front the end to tell it to hide the doorhanger, as the front end used to rely on audible playback start notifications, and that won't work for inaudible playback. MozReview-Commit-ID: LmcG8LNdJSn
dom/html/AutoplayPermissionManager.cpp
dom/html/AutoplayPermissionManager.h
dom/html/HTMLMediaElement.cpp
--- a/dom/html/AutoplayPermissionManager.cpp
+++ b/dom/html/AutoplayPermissionManager.cpp
@@ -81,9 +81,32 @@ AutoplayPermissionManager::DenyPlayReque
 void
 AutoplayPermissionManager::ApprovePlayRequest()
 {
   PLAY_REQUEST_LOG("AutoplayPermissionManager %p ApprovePlayRequest()", this);
   mRequestDispatched = false;
   mPromiseHolder.ResolveIfExists(true, __func__);
 }
 
+void
+AutoplayPermissionManager::CancelAllRequests()
+{
+  DenyPlayRequest();
+
+  // Send a message to chrome that it can hide the doorhanger.
+
+  nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+  if (NS_WARN_IF(!observerService)) {
+    return;
+  }
+
+  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow);
+  if (!window) {
+    return;
+  }
+  PLAY_REQUEST_LOG("AutoplayPermissionManager %p no remaining clients, "
+                   "aborting permission request",
+                   this);
+  observerService->NotifyObservers(
+    ToSupports(window), "audio-playback", u"abortPermissionRequest");
+}
+
 } // namespace mozilla
--- a/dom/html/AutoplayPermissionManager.h
+++ b/dom/html/AutoplayPermissionManager.h
@@ -41,26 +41,31 @@ public:
   // resolves/rejects when the user grants/denies permission to autoplay.
   // If the request doorhanger is dismissed, i.e. tab closed, ESC pressed,
   // etc, the MozPromise is rejected. If there is a stored permission for this
   // window's origin, the parent process will approve/deny the
   // autoplay request using the stored permission immediately (so the MozPromise
   // rejects in the content process after an IPC round trip).
   RefPtr<GenericPromise> RequestWithPrompt();
 
+  void CancelAllRequests();
+
+private:
+  ~AutoplayPermissionManager();
+
+  // AutoplayPermissionRequest must call {Approve,Deny}PlayRequest().
+  friend class AutoplayPermissionRequest;
+
   // Called by AutoplayPermissionRequest to approve the play request,
   // and resolve the MozPromise returned by RequestWithPrompt().
   void ApprovePlayRequest();
   // Called by AutoplayPermissionRequest to deny the play request,
   // and reject the MozPromise returned by RequestWithPrompt().
   void DenyPlayRequest();
 
-private:
-  ~AutoplayPermissionManager();
-
   nsWeakPtr mWindow;
   MozPromiseHolder<GenericPromise> mPromiseHolder;
   // Tracks whether we've dispatched a request to chrome in the parent process
   // to prompt for user permission to autoplay. This flag ensures we don't
   // request multiple times concurrently.
   bool mRequestDispatched = false;
 };
 
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -6423,16 +6423,31 @@ HTMLMediaElement::DispatchAsyncEvent(con
   if (mEventDeliveryPaused) {
     mPendingEvents.AppendElement(aName);
     return;
   }
 
   nsCOMPtr<nsIRunnable> event;
 
   if (aName.EqualsLiteral("playing")) {
+    if (mAutoplayPermissionRequest.Exists()) {
+      // This media element is still waiting on a doorhanger permission prompt
+      // to play, but now the video has started to play somehow anyway. Likely
+      // either script muted it and retried, or the user clicked its play
+      // button. Cancel any other autoplay requests, and hide the doorhanger.
+      // First, disconnect the request, so that we don't get a "cancel" response
+      // on this media element, as we may not have resolved the play promise
+      // yet.
+      mAutoplayPermissionRequest.Disconnect();
+      RefPtr<AutoplayPermissionManager> manager =
+        AutoplayPolicy::RequestFor(*OwnerDoc());
+      if (manager) {
+        manager->CancelAllRequests();
+      }
+    }
     event = new nsNotifyAboutPlayingRunner(this, TakePendingPlayPromises());
   } else {
     event = new nsAsyncEventRunner(aName, this);
   }
 
   mMainThreadEventTarget->Dispatch(event.forget());
 
   if ((aName.EqualsLiteral("play") || aName.EqualsLiteral("playing"))) {