Bug 1634494 - part4 : notify media control action to the top level browsing context and all its children. r=chunmin
authoralwu <alwu@mozilla.com>
Thu, 27 Aug 2020 04:10:45 +0000
changeset 546617 5560c478b424f8e2f8c4637e0d26be2addcb59bf
parent 546616 4df5c6612e8802ddd84434562658d8992e271fc2
child 546618 a5f8ee649ac8583949b4ac4ec5cb4419cdb03955
push id37735
push userabutkovits@mozilla.com
push dateThu, 27 Aug 2020 21:29:40 +0000
treeherdermozilla-central@109f3a4de567 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerschunmin
bugs1634494
milestone82.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 1634494 - part4 : notify media control action to the top level browsing context and all its children. r=chunmin As we want to have an ability to do different operations on different browsing contexts for those actions with default handlers (play/pause/stop), so in D87142 we have changed `ContentMediaController` per browsing context. Techically it's per inner window, but it means each browsing context would have one corresponding `ContentMediaController` if the web content is in that browsing context. The approach to achieve the goal is that, when getting those actions, we would notify to the top level browsing context, then go through its children to ensure that each browsing context can be operated correctly. By doing so, we can trigger default action handler and customized action handler on different browsing context. Eg. A page has a child iframe, which has active media session with an action handler for `pause`. A page and iframe are both playing media. When pressing `pause`, default action handler would be run on main frame, and the customized action handler would be run on iframe. Differential Revision: https://phabricator.services.mozilla.com/D87143
docshell/base/CanonicalBrowsingContext.cpp
dom/media/mediacontrol/ContentPlaybackController.cpp
dom/media/mediacontrol/MediaController.cpp
dom/media/mediacontrol/MediaController.h
--- a/docshell/base/CanonicalBrowsingContext.cpp
+++ b/docshell/base/CanonicalBrowsingContext.cpp
@@ -574,16 +574,19 @@ uint32_t CanonicalBrowsingContext::Count
     });
   }
 
   return uniqueSiteOrigins.Count();
 }
 
 void CanonicalBrowsingContext::UpdateMediaControlAction(
     const MediaControlAction& aAction) {
+  if (IsDiscarded()) {
+    return;
+  }
   ContentMediaControlKeyHandler::HandleMediaControlAction(this, aAction);
   Group()->EachParent([&](ContentParent* aParent) {
     Unused << aParent->SendUpdateMediaControlAction(this, aAction);
   });
 }
 
 void CanonicalBrowsingContext::LoadURI(const nsAString& aURI,
                                        const LoadURIOptions& aOptions,
--- a/dom/media/mediacontrol/ContentPlaybackController.cpp
+++ b/dom/media/mediacontrol/ContentPlaybackController.cpp
@@ -34,32 +34,33 @@ MediaSession* ContentPlaybackController:
   return navigator->HasCreatedMediaSession() ? navigator->MediaSession()
                                              : nullptr;
 }
 
 void ContentPlaybackController::NotifyContentMediaControlKeyReceiver(
     MediaControlKey aKey) {
   if (RefPtr<ContentMediaControlKeyReceiver> receiver =
           ContentMediaControlKeyReceiver::Get(mBC)) {
-    LOG("Handle '%s' in default behavior", ToMediaControlKeyStr(aKey));
+    LOG("Handle '%s' in default behavior for BC %" PRIu64,
+        ToMediaControlKeyStr(aKey), mBC->Id());
     receiver->HandleMediaKey(aKey);
   }
 }
 
 void ContentPlaybackController::NotifyMediaSession(MediaSessionAction aAction) {
   MediaSessionActionDetails details;
   details.mAction = aAction;
   NotifyMediaSession(details);
 }
 
 void ContentPlaybackController::NotifyMediaSession(
     const MediaSessionActionDetails& aDetails) {
   if (RefPtr<MediaSession> session = GetMediaSession()) {
-    LOG("Handle '%s' in media session behavior",
-        ToMediaSessionActionStr(aDetails.mAction));
+    LOG("Handle '%s' in media session behavior for BC %" PRIu64,
+        ToMediaSessionActionStr(aDetails.mAction), mBC->Id());
     MOZ_ASSERT(session->IsActive(), "Notify inactive media session!");
     session->NotifyHandler(aDetails);
   }
 }
 
 void ContentPlaybackController::NotifyMediaSessionWhenActionIsSupported(
     MediaSessionAction aAction) {
   if (IsMediaSessionActionSupported(aAction)) {
@@ -138,16 +139,21 @@ void ContentPlaybackController::SeekTo(d
   }
   if (IsMediaSessionActionSupported(details.mAction)) {
     NotifyMediaSession(details);
   }
 }
 
 void ContentMediaControlKeyHandler::HandleMediaControlAction(
     BrowsingContext* aContext, const MediaControlAction& aAction) {
+  MOZ_ASSERT(aContext);
+  // The web content doesn't exist in this browsing context.
+  if (!aContext->GetDocShell()) {
+    return;
+  }
   ContentPlaybackController controller(aContext);
   switch (aAction.mKey) {
     case MediaControlKey::Focus:
       controller.Focus();
       return;
     case MediaControlKey::Play:
       controller.Play();
       return;
@@ -173,14 +179,14 @@ void ContentMediaControlKeyHandler::Hand
       controller.SkipAd();
       return;
     case MediaControlKey::Seekto: {
       const SeekDetails& details = *aAction.mDetails;
       controller.SeekTo(details.mSeekTime, details.mFastSeek);
       return;
     }
     default:
-      MOZ_ASSERT_UNREACHABLE("Invalid event.");
+      MOZ_ASSERT_UNREACHABLE("Invalid media control key.");
   };
 }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/media/mediacontrol/MediaController.cpp
+++ b/dom/media/mediacontrol/MediaController.cpp
@@ -171,32 +171,52 @@ void MediaController::Stop() {
 uint64_t MediaController::Id() const { return mTopLevelBrowsingContextId; }
 
 bool MediaController::IsAudible() const { return IsMediaAudible(); }
 
 bool MediaController::IsPlaying() const { return IsMediaPlaying(); }
 
 bool MediaController::IsActive() const { return mIsActive; };
 
+bool MediaController::ShouldPropagateActionToAllContexts(
+    const MediaControlAction& aAction) const {
+  // These three actions have default action handler for each frame, so we
+  // need to propagate to all contexts. We would handle default handlers in
+  // `ContentMediaController::HandleMediaKey`.
+  return aAction.mKey == MediaControlKey::Play ||
+         aAction.mKey == MediaControlKey::Pause ||
+         aAction.mKey == MediaControlKey::Stop;
+}
+
 void MediaController::UpdateMediaControlActionToContentMediaIfNeeded(
     const MediaControlAction& aAction) {
   // If the controller isn't active or it has been shutdown, we don't need to
   // update media action to the content process.
   if (!mIsActive || mShutdown) {
     return;
   }
-  // If we have an active media session, then we should directly notify the
-  // browsing context where active media session exists in order to let the
-  // session handle media control key events. Otherwises, we would notify the
-  // top-level browsing context to let it handle events.
-  RefPtr<BrowsingContext> context =
-      mActiveMediaSessionContextId
-          ? BrowsingContext::Get(*mActiveMediaSessionContextId)
-          : BrowsingContext::Get(Id());
-  if (context && !context->IsDiscarded()) {
+
+  // For some actions which have default action handler, we want to propagate
+  // them on all contexts in order to trigger the default handler on each
+  // context separately. Otherwise, other action should only be propagated to
+  // the context where active media session exists.
+  const bool propateToAll = ShouldPropagateActionToAllContexts(aAction);
+  const uint64_t targetContextId = propateToAll || !mActiveMediaSessionContextId
+                                       ? Id()
+                                       : *mActiveMediaSessionContextId;
+  RefPtr<BrowsingContext> context = BrowsingContext::Get(targetContextId);
+  if (!context || context->IsDiscarded()) {
+    return;
+  }
+
+  if (propateToAll) {
+    context->PreOrderWalk([&](BrowsingContext* bc) {
+      bc->Canonical()->UpdateMediaControlAction(aAction);
+    });
+  } else {
     context->Canonical()->UpdateMediaControlAction(aAction);
   }
 }
 
 void MediaController::Shutdown() {
   MOZ_ASSERT(!mShutdown, "Do not call shutdown twice!");
   // The media controller would be removed from the service when we receive a
   // notification from the content process about all controlled media has been
--- a/dom/media/mediacontrol/MediaController.h
+++ b/dom/media/mediacontrol/MediaController.h
@@ -171,16 +171,19 @@ class MediaController final : public DOM
 
   void DispatchAsyncEvent(const nsAString& aName);
   void DispatchAsyncEvent(Event* aEvent);
 
   bool IsMainController() const;
   void ForceToBecomeMainControllerIfNeeded();
   bool ShouldRequestForMainController() const;
 
+  bool ShouldPropagateActionToAllContexts(
+      const MediaControlAction& aAction) const;
+
   bool mIsActive = false;
   bool mShutdown = false;
   bool mIsInPictureInPictureMode = false;
   bool mIsInFullScreenMode = false;
 
   // We would monitor the change of media session actions and convert them to
   // the media keys, then determine the supported media keys.
   MediaEventListener mSupportedActionsChangedListener;