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
--- 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"))) {