tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ContentMediaController.cpp (14408B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "ContentMediaController.h"
      6 
      7 #include "MediaControlUtils.h"
      8 #include "mozilla/ClearOnShutdown.h"
      9 #include "mozilla/StaticPtr.h"
     10 #include "mozilla/ToString.h"
     11 #include "mozilla/dom/BrowsingContext.h"
     12 #include "mozilla/dom/CanonicalBrowsingContext.h"
     13 #include "mozilla/dom/ContentChild.h"
     14 #include "nsGlobalWindowInner.h"
     15 
     16 namespace mozilla::dom {
     17 
     18 #undef LOG
     19 #define LOG(msg, ...)                        \
     20  MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
     21          ("ContentMediaController=%p, " msg, this, ##__VA_ARGS__))
     22 
     23 static Maybe<bool> sXPCOMShutdown;
     24 
     25 static void InitXPCOMShutdownMonitor() {
     26  if (sXPCOMShutdown) {
     27    return;
     28  }
     29  sXPCOMShutdown.emplace(false);
     30  RunOnShutdown([&] { sXPCOMShutdown = Some(true); });
     31 }
     32 
     33 static ContentMediaController* GetContentMediaControllerFromBrowsingContext(
     34    BrowsingContext* aBrowsingContext) {
     35  MOZ_ASSERT(NS_IsMainThread());
     36  InitXPCOMShutdownMonitor();
     37  if (!aBrowsingContext || aBrowsingContext->IsDiscarded()) {
     38    return nullptr;
     39  }
     40 
     41  nsPIDOMWindowOuter* outer = aBrowsingContext->GetDOMWindow();
     42  if (!outer) {
     43    return nullptr;
     44  }
     45 
     46  nsGlobalWindowInner* inner =
     47      nsGlobalWindowInner::Cast(outer->GetCurrentInnerWindow());
     48  return inner ? inner->GetContentMediaController() : nullptr;
     49 }
     50 
     51 static already_AddRefed<BrowsingContext> GetBrowsingContextForAgent(
     52    uint64_t aBrowsingContextId) {
     53  // If XPCOM has been shutdown, then we're not able to access browsing context.
     54  if (sXPCOMShutdown && *sXPCOMShutdown) {
     55    return nullptr;
     56  }
     57  return BrowsingContext::Get(aBrowsingContextId);
     58 }
     59 
     60 /* static */
     61 ContentMediaControlKeyReceiver* ContentMediaControlKeyReceiver::Get(
     62    BrowsingContext* aBC) {
     63  MOZ_ASSERT(NS_IsMainThread());
     64  return GetContentMediaControllerFromBrowsingContext(aBC);
     65 }
     66 
     67 /* static */
     68 ContentMediaAgent* ContentMediaAgent::Get(BrowsingContext* aBC) {
     69  MOZ_ASSERT(NS_IsMainThread());
     70  return GetContentMediaControllerFromBrowsingContext(aBC);
     71 }
     72 
     73 void ContentMediaAgent::NotifyMediaPlaybackChanged(uint64_t aBrowsingContextId,
     74                                                   MediaPlaybackState aState) {
     75  MOZ_ASSERT(NS_IsMainThread());
     76  RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
     77  if (!bc || bc->IsDiscarded()) {
     78    return;
     79  }
     80 
     81  LOG("Notify media %s in BC %" PRId64, ToString(aState).c_str(), bc->Id());
     82  if (XRE_IsContentProcess()) {
     83    ContentChild* contentChild = ContentChild::GetSingleton();
     84    (void)contentChild->SendNotifyMediaPlaybackChanged(bc, aState);
     85  } else {
     86    // Currently this only happen when we disable e10s, otherwise all controlled
     87    // media would be run in the content process.
     88    if (RefPtr<IMediaInfoUpdater> updater =
     89            bc->Canonical()->GetMediaController()) {
     90      updater->NotifyMediaPlaybackChanged(bc->Id(), aState);
     91    }
     92  }
     93 }
     94 
     95 void ContentMediaAgent::NotifyMediaAudibleChanged(uint64_t aBrowsingContextId,
     96                                                  MediaAudibleState aState) {
     97  MOZ_ASSERT(NS_IsMainThread());
     98  RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
     99  if (!bc || bc->IsDiscarded()) {
    100    return;
    101  }
    102 
    103  LOG("Notify media became %s in BC %" PRId64,
    104      aState == MediaAudibleState::eAudible ? "audible" : "inaudible",
    105      bc->Id());
    106  if (XRE_IsContentProcess()) {
    107    ContentChild* contentChild = ContentChild::GetSingleton();
    108    (void)contentChild->SendNotifyMediaAudibleChanged(bc, aState);
    109  } else {
    110    // Currently this only happen when we disable e10s, otherwise all controlled
    111    // media would be run in the content process.
    112    if (RefPtr<IMediaInfoUpdater> updater =
    113            bc->Canonical()->GetMediaController()) {
    114      updater->NotifyMediaAudibleChanged(bc->Id(), aState);
    115    }
    116  }
    117 }
    118 
    119 void ContentMediaAgent::SetIsInPictureInPictureMode(
    120    uint64_t aBrowsingContextId, bool aIsInPictureInPictureMode) {
    121  MOZ_ASSERT(NS_IsMainThread());
    122  RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
    123  if (!bc || bc->IsDiscarded()) {
    124    return;
    125  }
    126 
    127  LOG("Notify media Picture-in-Picture mode '%s' in BC %" PRId64,
    128      aIsInPictureInPictureMode ? "enabled" : "disabled", bc->Id());
    129  if (XRE_IsContentProcess()) {
    130    ContentChild* contentChild = ContentChild::GetSingleton();
    131    (void)contentChild->SendNotifyPictureInPictureModeChanged(
    132        bc, aIsInPictureInPictureMode);
    133  } else {
    134    // Currently this only happen when we disable e10s, otherwise all controlled
    135    // media would be run in the content process.
    136    if (RefPtr<IMediaInfoUpdater> updater =
    137            bc->Canonical()->GetMediaController()) {
    138      updater->SetIsInPictureInPictureMode(bc->Id(), aIsInPictureInPictureMode);
    139    }
    140  }
    141 }
    142 
    143 void ContentMediaAgent::SetDeclaredPlaybackState(
    144    uint64_t aBrowsingContextId, MediaSessionPlaybackState aState) {
    145  RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
    146  if (!bc || bc->IsDiscarded()) {
    147    return;
    148  }
    149 
    150  LOG("Notify declared playback state  '%s' in BC %" PRId64,
    151      ToMediaSessionPlaybackStateStr(aState), bc->Id());
    152  if (XRE_IsContentProcess()) {
    153    ContentChild* contentChild = ContentChild::GetSingleton();
    154    (void)contentChild->SendNotifyMediaSessionPlaybackStateChanged(bc, aState);
    155    return;
    156  }
    157  // This would only happen when we disable e10s.
    158  if (RefPtr<IMediaInfoUpdater> updater =
    159          bc->Canonical()->GetMediaController()) {
    160    updater->SetDeclaredPlaybackState(bc->Id(), aState);
    161  }
    162 }
    163 
    164 void ContentMediaAgent::NotifySessionCreated(uint64_t aBrowsingContextId) {
    165  RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
    166  if (!bc || bc->IsDiscarded()) {
    167    return;
    168  }
    169 
    170  LOG("Notify media session being created in BC %" PRId64, bc->Id());
    171  if (XRE_IsContentProcess()) {
    172    ContentChild* contentChild = ContentChild::GetSingleton();
    173    (void)contentChild->SendNotifyMediaSessionUpdated(bc, true);
    174    return;
    175  }
    176  // This would only happen when we disable e10s.
    177  if (RefPtr<IMediaInfoUpdater> updater =
    178          bc->Canonical()->GetMediaController()) {
    179    updater->NotifySessionCreated(bc->Id());
    180  }
    181 }
    182 
    183 void ContentMediaAgent::NotifySessionDestroyed(uint64_t aBrowsingContextId) {
    184  RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
    185  if (!bc || bc->IsDiscarded()) {
    186    return;
    187  }
    188 
    189  LOG("Notify media session being destroyed in BC %" PRId64, bc->Id());
    190  if (XRE_IsContentProcess()) {
    191    ContentChild* contentChild = ContentChild::GetSingleton();
    192    (void)contentChild->SendNotifyMediaSessionUpdated(bc, false);
    193    return;
    194  }
    195  // This would only happen when we disable e10s.
    196  if (RefPtr<IMediaInfoUpdater> updater =
    197          bc->Canonical()->GetMediaController()) {
    198    updater->NotifySessionDestroyed(bc->Id());
    199  }
    200 }
    201 
    202 void ContentMediaAgent::UpdateMetadata(
    203    uint64_t aBrowsingContextId, const Maybe<MediaMetadataBase>& aMetadata) {
    204  RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
    205  if (!bc || bc->IsDiscarded()) {
    206    return;
    207  }
    208 
    209  LOG("Notify media session metadata change in BC %" PRId64, bc->Id());
    210  if (XRE_IsContentProcess()) {
    211    ContentChild* contentChild = ContentChild::GetSingleton();
    212    (void)contentChild->SendNotifyUpdateMediaMetadata(bc, aMetadata);
    213    return;
    214  }
    215  // This would only happen when we disable e10s.
    216  if (RefPtr<IMediaInfoUpdater> updater =
    217          bc->Canonical()->GetMediaController()) {
    218    updater->UpdateMetadata(bc->Id(), aMetadata);
    219  }
    220 }
    221 
    222 void ContentMediaAgent::EnableAction(uint64_t aBrowsingContextId,
    223                                     MediaSessionAction aAction) {
    224  RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
    225  if (!bc || bc->IsDiscarded()) {
    226    return;
    227  }
    228 
    229  LOG("Notify to enable action '%s' in BC %" PRId64,
    230      GetEnumString(aAction).get(), bc->Id());
    231  if (XRE_IsContentProcess()) {
    232    ContentChild* contentChild = ContentChild::GetSingleton();
    233    (void)contentChild->SendNotifyMediaSessionSupportedActionChanged(
    234        bc, aAction, true);
    235    return;
    236  }
    237  // This would only happen when we disable e10s.
    238  if (RefPtr<IMediaInfoUpdater> updater =
    239          bc->Canonical()->GetMediaController()) {
    240    updater->EnableAction(bc->Id(), aAction);
    241  }
    242 }
    243 
    244 void ContentMediaAgent::DisableAction(uint64_t aBrowsingContextId,
    245                                      MediaSessionAction aAction) {
    246  RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
    247  if (!bc || bc->IsDiscarded()) {
    248    return;
    249  }
    250 
    251  LOG("Notify to disable action '%s' in BC %" PRId64,
    252      GetEnumString(aAction).get(), bc->Id());
    253  if (XRE_IsContentProcess()) {
    254    ContentChild* contentChild = ContentChild::GetSingleton();
    255    (void)contentChild->SendNotifyMediaSessionSupportedActionChanged(
    256        bc, aAction, false);
    257    return;
    258  }
    259  // This would only happen when we disable e10s.
    260  if (RefPtr<IMediaInfoUpdater> updater =
    261          bc->Canonical()->GetMediaController()) {
    262    updater->DisableAction(bc->Id(), aAction);
    263  }
    264 }
    265 
    266 void ContentMediaAgent::NotifyMediaFullScreenState(uint64_t aBrowsingContextId,
    267                                                   bool aIsInFullScreen) {
    268  RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
    269  if (!bc || bc->IsDiscarded()) {
    270    return;
    271  }
    272 
    273  LOG("Notify %s fullscreen in BC %" PRId64,
    274      aIsInFullScreen ? "entered" : "left", bc->Id());
    275  if (XRE_IsContentProcess()) {
    276    ContentChild* contentChild = ContentChild::GetSingleton();
    277    (void)contentChild->SendNotifyMediaFullScreenState(bc, aIsInFullScreen);
    278    return;
    279  }
    280  // This would only happen when we disable e10s.
    281  if (RefPtr<IMediaInfoUpdater> updater =
    282          bc->Canonical()->GetMediaController()) {
    283    updater->NotifyMediaFullScreenState(bc->Id(), aIsInFullScreen);
    284  }
    285 }
    286 
    287 void ContentMediaAgent::UpdatePositionState(
    288    uint64_t aBrowsingContextId, const Maybe<PositionState>& aState) {
    289  RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
    290  if (!bc || bc->IsDiscarded()) {
    291    return;
    292  }
    293  if (XRE_IsContentProcess()) {
    294    ContentChild* contentChild = ContentChild::GetSingleton();
    295    (void)contentChild->SendNotifyPositionStateChanged(bc, aState);
    296    return;
    297  }
    298  // This would only happen when we disable e10s.
    299  if (RefPtr<IMediaInfoUpdater> updater =
    300          bc->Canonical()->GetMediaController()) {
    301    updater->UpdatePositionState(bc->Id(), aState);
    302  }
    303 }
    304 
    305 void ContentMediaAgent::UpdateGuessedPositionState(
    306    uint64_t aBrowsingContextId, const nsID& aMediaId,
    307    const Maybe<PositionState>& aState) {
    308  RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
    309  if (!bc || bc->IsDiscarded()) {
    310    return;
    311  }
    312 
    313  if (aState) {
    314    LOG("Update guessed position state for BC %" PRId64
    315        " media id %s (duration=%f, playbackRate=%f, position=%f)",
    316        bc->Id(), aMediaId.ToString().get(), aState->mDuration,
    317        aState->mPlaybackRate, aState->mLastReportedPlaybackPosition);
    318  } else {
    319    LOG("Clear guessed position state for BC %" PRId64 " media id %s", bc->Id(),
    320        aMediaId.ToString().get());
    321  }
    322 
    323  if (XRE_IsContentProcess()) {
    324    ContentChild* contentChild = ContentChild::GetSingleton();
    325    (void)contentChild->SendNotifyGuessedPositionStateChanged(bc, aMediaId,
    326                                                              aState);
    327    return;
    328  }
    329  // This would only happen when we disable e10s.
    330  if (RefPtr<IMediaInfoUpdater> updater =
    331          bc->Canonical()->GetMediaController()) {
    332    updater->UpdateGuessedPositionState(bc->Id(), aMediaId, aState);
    333  }
    334 }
    335 
    336 ContentMediaController::ContentMediaController(uint64_t aId) {
    337  LOG("Create content media controller for BC %" PRId64, aId);
    338 }
    339 
    340 void ContentMediaController::AddReceiver(
    341    ContentMediaControlKeyReceiver* aListener) {
    342  MOZ_ASSERT(NS_IsMainThread());
    343  mReceivers.AppendElement(aListener);
    344 }
    345 
    346 void ContentMediaController::RemoveReceiver(
    347    ContentMediaControlKeyReceiver* aListener) {
    348  MOZ_ASSERT(NS_IsMainThread());
    349  mReceivers.RemoveElement(aListener);
    350 }
    351 
    352 void ContentMediaController::HandleMediaKey(MediaControlKey aKey,
    353                                            Maybe<SeekDetails> aDetails) {
    354  MOZ_ASSERT(NS_IsMainThread());
    355  if (mReceivers.IsEmpty()) {
    356    return;
    357  }
    358  LOG("Handle '%s' event, receiver num=%zu", GetEnumString(aKey).get(),
    359      mReceivers.Length());
    360  // We have default handlers for these actions
    361  // https://w3c.github.io/mediasession/#ref-for-dom-mediasessionaction-play%E2%91%A3
    362  switch (aKey) {
    363    case MediaControlKey::Pause:
    364      PauseOrStopMedia();
    365      return;
    366    case MediaControlKey::Play:
    367    case MediaControlKey::Stop:
    368    case MediaControlKey::Seekto:
    369    case MediaControlKey::Seekforward:
    370    case MediaControlKey::Seekbackward:
    371      // When receiving `Stop`, the amount of receiver would vary during the
    372      // iteration, so we use the backward iteration to avoid accessing the
    373      // index which is over the array length.
    374      for (auto& receiver : Reversed(mReceivers)) {
    375        receiver->HandleMediaKey(aKey, aDetails);
    376      }
    377      return;
    378    default:
    379      MOZ_ASSERT_UNREACHABLE("Not supported media key for default handler");
    380  }
    381 }
    382 
    383 void ContentMediaController::PauseOrStopMedia() {
    384  // When receiving `pause`, if a page contains playing media and paused media
    385  // at that moment, that means a user intends to pause those playing
    386  // media, not the already paused ones. Then, we're going to stop those already
    387  // paused media and keep those latest paused media in `mReceivers`.
    388  // The reason for doing that is, when resuming paused media, we only want to
    389  // resume latest paused media, not all media, in order to get a better user
    390  // experience, which matches Chrome's behavior.
    391  bool isAnyMediaPlaying = false;
    392  for (const auto& receiver : mReceivers) {
    393    if (receiver->IsPlaying()) {
    394      isAnyMediaPlaying = true;
    395      break;
    396    }
    397  }
    398 
    399  for (auto& receiver : Reversed(mReceivers)) {
    400    if (isAnyMediaPlaying && !receiver->IsPlaying()) {
    401      receiver->HandleMediaKey(MediaControlKey::Stop);
    402    } else {
    403      receiver->HandleMediaKey(MediaControlKey::Pause);
    404    }
    405  }
    406 }
    407 
    408 }  // namespace mozilla::dom