tor-browser

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

AudioChannelService.cpp (17523B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "AudioChannelService.h"
      8 
      9 #include "base/basictypes.h"
     10 #include "mozilla/Preferences.h"
     11 #include "mozilla/Services.h"
     12 #include "mozilla/StaticPtr.h"
     13 #include "mozilla/dom/Document.h"
     14 #include "nsComponentManagerUtils.h"
     15 #include "nsContentUtils.h"
     16 #include "nsHashPropertyBag.h"
     17 #include "nsIObserverService.h"
     18 #include "nsISupportsPrimitives.h"
     19 #include "nsPIDOMWindow.h"
     20 #include "nsServiceManagerUtils.h"
     21 #include "nsThreadUtils.h"
     22 
     23 using namespace mozilla;
     24 using namespace mozilla::dom;
     25 
     26 mozilla::LazyLogModule gAudioChannelLog("AudioChannel");
     27 
     28 namespace {
     29 
     30 bool sXPCOMShuttingDown = false;
     31 
     32 class AudioPlaybackRunnable final : public Runnable {
     33 public:
     34  AudioPlaybackRunnable(nsPIDOMWindowOuter* aWindow, bool aActive,
     35                        AudioChannelService::AudibleChangedReasons aReason)
     36      : mozilla::Runnable("AudioPlaybackRunnable"),
     37        mWindow(aWindow),
     38        mActive(aActive),
     39        mReason(aReason) {}
     40 
     41  NS_IMETHOD Run() override {
     42    nsCOMPtr<nsIObserverService> observerService =
     43        services::GetObserverService();
     44    if (NS_WARN_IF(!observerService)) {
     45      return NS_ERROR_FAILURE;
     46    }
     47 
     48    nsAutoString state;
     49    GetActiveState(state);
     50 
     51    observerService->NotifyObservers(ToSupports(mWindow), "audio-playback",
     52                                     state.get());
     53 
     54    MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
     55            ("AudioPlaybackRunnable, active = %s, reason = %s\n",
     56             mActive ? "true" : "false",
     57             AudioChannelService::EnumValueToString(mReason)));
     58 
     59    return NS_OK;
     60  }
     61 
     62 private:
     63  void GetActiveState(nsAString& aState) {
     64    if (mActive) {
     65      aState.AssignLiteral("active");
     66    } else {
     67      if (mReason ==
     68          AudioChannelService::AudibleChangedReasons::ePauseStateChanged) {
     69        aState.AssignLiteral("inactive-pause");
     70      } else {
     71        aState.AssignLiteral("inactive-nonaudible");
     72      }
     73    }
     74  }
     75 
     76  nsCOMPtr<nsPIDOMWindowOuter> mWindow;
     77  bool mActive;
     78  AudioChannelService::AudibleChangedReasons mReason;
     79 };
     80 
     81 }  // anonymous namespace
     82 
     83 namespace mozilla::dom {
     84 
     85 const char* SuspendTypeToStr(const nsSuspendedTypes& aSuspend) {
     86  MOZ_ASSERT(aSuspend == nsISuspendedTypes::NONE_SUSPENDED ||
     87             aSuspend == nsISuspendedTypes::SUSPENDED_BLOCK);
     88 
     89  switch (aSuspend) {
     90    case nsISuspendedTypes::NONE_SUSPENDED:
     91      return "none";
     92    case nsISuspendedTypes::SUSPENDED_BLOCK:
     93      return "block";
     94    default:
     95      return "unknown";
     96  }
     97 }
     98 
     99 StaticRefPtr<AudioChannelService> gAudioChannelService;
    100 
    101 /* static */
    102 void AudioChannelService::CreateServiceIfNeeded() {
    103  MOZ_ASSERT(NS_IsMainThread());
    104 
    105  if (!gAudioChannelService) {
    106    gAudioChannelService = new AudioChannelService();
    107  }
    108 }
    109 
    110 /* static */
    111 already_AddRefed<AudioChannelService> AudioChannelService::GetOrCreate() {
    112  if (sXPCOMShuttingDown) {
    113    return nullptr;
    114  }
    115 
    116  CreateServiceIfNeeded();
    117  RefPtr<AudioChannelService> service = gAudioChannelService.get();
    118  return service.forget();
    119 }
    120 
    121 /* static */
    122 already_AddRefed<AudioChannelService> AudioChannelService::Get() {
    123  if (sXPCOMShuttingDown) {
    124    return nullptr;
    125  }
    126 
    127  RefPtr<AudioChannelService> service = gAudioChannelService.get();
    128  return service.forget();
    129 }
    130 
    131 /* static */
    132 LogModule* AudioChannelService::GetAudioChannelLog() {
    133  return gAudioChannelLog;
    134 }
    135 
    136 /* static */
    137 void AudioChannelService::Shutdown() {
    138  if (gAudioChannelService) {
    139    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    140    if (obs) {
    141      obs->RemoveObserver(gAudioChannelService, "xpcom-shutdown");
    142      obs->RemoveObserver(gAudioChannelService, "outer-window-destroyed");
    143    }
    144 
    145    gAudioChannelService->mWindows.Clear();
    146 
    147    gAudioChannelService = nullptr;
    148  }
    149 }
    150 
    151 NS_INTERFACE_MAP_BEGIN(AudioChannelService)
    152  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
    153  NS_INTERFACE_MAP_ENTRY(nsIObserver)
    154 NS_INTERFACE_MAP_END
    155 
    156 NS_IMPL_ADDREF(AudioChannelService)
    157 NS_IMPL_RELEASE(AudioChannelService)
    158 
    159 AudioChannelService::AudioChannelService() {
    160  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    161  if (obs) {
    162    obs->AddObserver(this, "xpcom-shutdown", false);
    163    obs->AddObserver(this, "outer-window-destroyed", false);
    164  }
    165 }
    166 
    167 AudioChannelService::~AudioChannelService() = default;
    168 
    169 void AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
    170                                                    AudibleState aAudible) {
    171  MOZ_ASSERT(aAgent);
    172 
    173  uint64_t windowID = aAgent->WindowID();
    174  AudioChannelWindow* winData = GetWindowData(windowID);
    175  if (!winData) {
    176    winData = new AudioChannelWindow(windowID);
    177    mWindows.AppendElement(WrapUnique(winData));
    178  }
    179 
    180  // To make sure agent would be alive because AppendAgent() would trigger the
    181  // callback function of AudioChannelAgentOwner that means the agent might be
    182  // released in their callback.
    183  RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
    184  winData->AppendAgent(aAgent, aAudible);
    185 }
    186 
    187 void AudioChannelService::UnregisterAudioChannelAgent(
    188    AudioChannelAgent* aAgent) {
    189  MOZ_ASSERT(aAgent);
    190 
    191  AudioChannelWindow* winData = GetWindowData(aAgent->WindowID());
    192  if (!winData) {
    193    return;
    194  }
    195 
    196  // To make sure agent would be alive because AppendAgent() would trigger the
    197  // callback function of AudioChannelAgentOwner that means the agent might be
    198  // released in their callback.
    199  RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
    200  winData->RemoveAgent(aAgent);
    201 }
    202 
    203 AudioPlaybackConfig AudioChannelService::GetMediaConfig(
    204    nsPIDOMWindowOuter* aWindow) const {
    205  AudioPlaybackConfig config(1.0, false, nsISuspendedTypes::NONE_SUSPENDED);
    206 
    207  if (!aWindow) {
    208    config.mVolume = 0.0;
    209    config.mMuted = true;
    210    config.mSuspend = nsISuspendedTypes::SUSPENDED_BLOCK;
    211    return config;
    212  }
    213 
    214  AudioChannelWindow* winData = nullptr;
    215  nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
    216 
    217  // The volume must be calculated based on the window hierarchy. Here we go up
    218  // to the top window and we calculate the volume and the muted flag.
    219  do {
    220    winData = GetWindowData(window->WindowID());
    221    if (winData) {
    222      config.mVolume *= winData->mConfig.mVolume;
    223      config.mMuted = config.mMuted || winData->mConfig.mMuted;
    224      config.mCapturedAudio = winData->mIsAudioCaptured;
    225    }
    226 
    227    config.mMuted = config.mMuted || window->GetAudioMuted();
    228    if (window->ShouldDelayMediaFromStart()) {
    229      config.mSuspend = nsISuspendedTypes::SUSPENDED_BLOCK;
    230    }
    231 
    232    nsCOMPtr<nsPIDOMWindowOuter> win =
    233        window->GetInProcessScriptableParentOrNull();
    234    if (!win) {
    235      break;
    236    }
    237 
    238    window = win;
    239 
    240    // If there is no parent, or we are the toplevel we don't continue.
    241  } while (window && window != aWindow);
    242 
    243  return config;
    244 }
    245 
    246 void AudioChannelService::AudioAudibleChanged(AudioChannelAgent* aAgent,
    247                                              AudibleState aAudible,
    248                                              AudibleChangedReasons aReason) {
    249  MOZ_ASSERT(aAgent);
    250 
    251  uint64_t windowID = aAgent->WindowID();
    252  AudioChannelWindow* winData = GetWindowData(windowID);
    253  if (winData) {
    254    winData->AudioAudibleChanged(aAgent, aAudible, aReason);
    255  }
    256 }
    257 
    258 NS_IMETHODIMP
    259 AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic,
    260                             const char16_t* aData) {
    261  if (!strcmp(aTopic, "xpcom-shutdown")) {
    262    sXPCOMShuttingDown = true;
    263    Shutdown();
    264  } else if (!strcmp(aTopic, "outer-window-destroyed")) {
    265    nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
    266    NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
    267 
    268    uint64_t outerID;
    269    nsresult rv = wrapper->GetData(&outerID);
    270    if (NS_WARN_IF(NS_FAILED(rv))) {
    271      return rv;
    272    }
    273 
    274    UniquePtr<AudioChannelWindow> winData;
    275    {
    276      nsTObserverArray<UniquePtr<AudioChannelWindow>>::ForwardIterator iter(
    277          mWindows);
    278      while (iter.HasMore()) {
    279        auto& next = iter.GetNext();
    280        if (next->mWindowID == outerID) {
    281          winData = std::move(next);
    282          iter.Remove();
    283          break;
    284        }
    285      }
    286    }
    287 
    288    if (winData) {
    289      for (AudioChannelAgent* agent : winData->mAgents.ForwardRange()) {
    290        agent->WindowVolumeChanged(winData->mConfig.mVolume,
    291                                   winData->mConfig.mMuted);
    292      }
    293    }
    294  }
    295 
    296  return NS_OK;
    297 }
    298 
    299 void AudioChannelService::RefreshAgents(
    300    nsPIDOMWindowOuter* aWindow,
    301    const std::function<void(AudioChannelAgent*)>& aFunc) {
    302  MOZ_ASSERT(aWindow);
    303 
    304  nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
    305  if (!topWindow) {
    306    return;
    307  }
    308 
    309  AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
    310  if (!winData) {
    311    return;
    312  }
    313 
    314  for (AudioChannelAgent* agent : winData->mAgents.ForwardRange()) {
    315    aFunc(agent);
    316  }
    317 }
    318 
    319 void AudioChannelService::RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow,
    320                                              float aVolume, bool aMuted) {
    321  RefreshAgents(aWindow, [aVolume, aMuted](AudioChannelAgent* agent) {
    322    agent->WindowVolumeChanged(aVolume, aMuted);
    323  });
    324 }
    325 
    326 void AudioChannelService::RefreshAgentsSuspend(nsPIDOMWindowOuter* aWindow,
    327                                               nsSuspendedTypes aSuspend) {
    328  RefreshAgents(aWindow, [aSuspend](AudioChannelAgent* agent) {
    329    agent->WindowSuspendChanged(aSuspend);
    330  });
    331 }
    332 
    333 void AudioChannelService::SetWindowAudioCaptured(nsPIDOMWindowOuter* aWindow,
    334                                                 uint64_t aInnerWindowID,
    335                                                 bool aCapture) {
    336  MOZ_ASSERT(NS_IsMainThread());
    337  MOZ_ASSERT(aWindow);
    338 
    339  MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
    340          ("AudioChannelService, SetWindowAudioCaptured, window = %p, "
    341           "aCapture = %d\n",
    342           aWindow, aCapture));
    343 
    344  nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
    345  if (!topWindow) {
    346    return;
    347  }
    348 
    349  AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
    350 
    351  // This can happen, but only during shutdown, because the the outer window
    352  // changes ScriptableTop, so that its ID is different.
    353  // In this case either we are capturing, and it's too late because the window
    354  // has been closed anyways, or we are un-capturing, and everything has already
    355  // been cleaned up by the HTMLMediaElements or the AudioContexts.
    356  if (!winData) {
    357    return;
    358  }
    359 
    360  if (aCapture != winData->mIsAudioCaptured) {
    361    winData->mIsAudioCaptured = aCapture;
    362    for (AudioChannelAgent* agent : winData->mAgents.ForwardRange()) {
    363      agent->WindowAudioCaptureChanged(aInnerWindowID, aCapture);
    364    }
    365  }
    366 }
    367 
    368 AudioChannelService::AudioChannelWindow*
    369 AudioChannelService::GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow) {
    370  MOZ_ASSERT(NS_IsMainThread());
    371  MOZ_ASSERT(aWindow);
    372 
    373  AudioChannelWindow* winData = GetWindowData(aWindow->WindowID());
    374  if (!winData) {
    375    winData = new AudioChannelWindow(aWindow->WindowID());
    376    mWindows.AppendElement(WrapUnique(winData));
    377  }
    378 
    379  return winData;
    380 }
    381 
    382 AudioChannelService::AudioChannelWindow* AudioChannelService::GetWindowData(
    383    uint64_t aWindowID) const {
    384  const auto [begin, end] = mWindows.NonObservingRange();
    385  const auto foundIt = std::find_if(begin, end, [aWindowID](const auto& next) {
    386    return next->mWindowID == aWindowID;
    387  });
    388  return foundIt != end ? foundIt->get() : nullptr;
    389 }
    390 
    391 bool AudioChannelService::IsWindowActive(nsPIDOMWindowOuter* aWindow) {
    392  MOZ_ASSERT(NS_IsMainThread());
    393 
    394  auto* window = nsPIDOMWindowOuter::From(aWindow)->GetInProcessScriptableTop();
    395  if (!window) {
    396    return false;
    397  }
    398 
    399  AudioChannelWindow* winData = GetWindowData(window->WindowID());
    400  if (!winData) {
    401    return false;
    402  }
    403 
    404  return !winData->mAudibleAgents.IsEmpty();
    405 }
    406 
    407 void AudioChannelService::NotifyResumingDelayedMedia(
    408    nsPIDOMWindowOuter* aWindow) {
    409  MOZ_ASSERT(aWindow);
    410 
    411  nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
    412  if (!topWindow) {
    413    return;
    414  }
    415 
    416  AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
    417  if (!winData) {
    418    return;
    419  }
    420 
    421  winData->NotifyMediaBlockStop(aWindow);
    422  RefreshAgentsSuspend(aWindow, nsISuspendedTypes::NONE_SUSPENDED);
    423 }
    424 
    425 void AudioChannelService::AudioChannelWindow::AppendAgent(
    426    AudioChannelAgent* aAgent, AudibleState aAudible) {
    427  MOZ_ASSERT(aAgent);
    428 
    429  AppendAgentAndIncreaseAgentsNum(aAgent);
    430  AudioAudibleChanged(aAgent, aAudible,
    431                      AudibleChangedReasons::eDataAudibleChanged);
    432 }
    433 
    434 void AudioChannelService::AudioChannelWindow::RemoveAgent(
    435    AudioChannelAgent* aAgent) {
    436  MOZ_ASSERT(aAgent);
    437 
    438  RemoveAgentAndReduceAgentsNum(aAgent);
    439  AudioAudibleChanged(aAgent, AudibleState::eNotAudible,
    440                      AudibleChangedReasons::ePauseStateChanged);
    441 }
    442 
    443 void AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop(
    444    nsPIDOMWindowOuter* aWindow) {
    445  if (mShouldSendActiveMediaBlockStopEvent) {
    446    mShouldSendActiveMediaBlockStopEvent = false;
    447    nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
    448    NS_DispatchToCurrentThread(NS_NewRunnableFunction(
    449        "dom::AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop",
    450        [window]() -> void {
    451          nsCOMPtr<nsIObserverService> observerService =
    452              services::GetObserverService();
    453          if (NS_WARN_IF(!observerService)) {
    454            return;
    455          }
    456 
    457          observerService->NotifyObservers(ToSupports(window), "audio-playback",
    458                                           u"activeMediaBlockStop");
    459        }));
    460  }
    461 }
    462 
    463 void AudioChannelService::AudioChannelWindow::AppendAgentAndIncreaseAgentsNum(
    464    AudioChannelAgent* aAgent) {
    465  MOZ_ASSERT(aAgent);
    466  MOZ_ASSERT(!mAgents.Contains(aAgent));
    467 
    468  mAgents.AppendElement(aAgent);
    469 
    470  ++mConfig.mNumberOfAgents;
    471 }
    472 
    473 void AudioChannelService::AudioChannelWindow::RemoveAgentAndReduceAgentsNum(
    474    AudioChannelAgent* aAgent) {
    475  MOZ_ASSERT(aAgent);
    476  MOZ_ASSERT(mAgents.Contains(aAgent));
    477 
    478  mAgents.RemoveElement(aAgent);
    479 
    480  MOZ_ASSERT(mConfig.mNumberOfAgents > 0);
    481  --mConfig.mNumberOfAgents;
    482 }
    483 
    484 void AudioChannelService::AudioChannelWindow::AudioAudibleChanged(
    485    AudioChannelAgent* aAgent, AudibleState aAudible,
    486    AudibleChangedReasons aReason) {
    487  MOZ_ASSERT(aAgent);
    488 
    489  if (aAudible == AudibleState::eAudible) {
    490    AppendAudibleAgentIfNotContained(aAgent, aReason);
    491  } else {
    492    RemoveAudibleAgentIfContained(aAgent, aReason);
    493  }
    494 
    495  if (aAudible != AudibleState::eNotAudible) {
    496    MaybeNotifyMediaBlockStart(aAgent);
    497  }
    498 }
    499 
    500 void AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(
    501    AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
    502  MOZ_ASSERT(aAgent);
    503  MOZ_ASSERT(mAgents.Contains(aAgent));
    504 
    505  if (!mAudibleAgents.Contains(aAgent)) {
    506    mAudibleAgents.AppendElement(aAgent);
    507    if (IsFirstAudibleAgent()) {
    508      NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eAudible,
    509                                aReason);
    510    }
    511  }
    512 }
    513 
    514 void AudioChannelService::AudioChannelWindow::RemoveAudibleAgentIfContained(
    515    AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
    516  MOZ_ASSERT(aAgent);
    517 
    518  if (mAudibleAgents.Contains(aAgent)) {
    519    mAudibleAgents.RemoveElement(aAgent);
    520    if (IsLastAudibleAgent()) {
    521      NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eNotAudible,
    522                                aReason);
    523    }
    524  }
    525 }
    526 
    527 bool AudioChannelService::AudioChannelWindow::IsFirstAudibleAgent() const {
    528  return (mAudibleAgents.Length() == 1);
    529 }
    530 
    531 bool AudioChannelService::AudioChannelWindow::IsLastAudibleAgent() const {
    532  return mAudibleAgents.IsEmpty();
    533 }
    534 
    535 void AudioChannelService::AudioChannelWindow::NotifyAudioAudibleChanged(
    536    nsPIDOMWindowOuter* aWindow, AudibleState aAudible,
    537    AudibleChangedReasons aReason) {
    538  RefPtr<AudioPlaybackRunnable> runnable = new AudioPlaybackRunnable(
    539      aWindow, aAudible == AudibleState::eAudible, aReason);
    540  DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
    541  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
    542 }
    543 
    544 void AudioChannelService::AudioChannelWindow::MaybeNotifyMediaBlockStart(
    545    AudioChannelAgent* aAgent) {
    546  nsCOMPtr<nsPIDOMWindowOuter> window = aAgent->Window();
    547  if (!window) {
    548    return;
    549  }
    550 
    551  nsCOMPtr<nsPIDOMWindowInner> inner = window->GetCurrentInnerWindow();
    552  if (!inner) {
    553    return;
    554  }
    555 
    556  nsCOMPtr<Document> doc = inner->GetExtantDoc();
    557  if (!doc) {
    558    return;
    559  }
    560 
    561  if (!window->ShouldDelayMediaFromStart() || !doc->Hidden()) {
    562    return;
    563  }
    564 
    565  if (!mShouldSendActiveMediaBlockStopEvent) {
    566    mShouldSendActiveMediaBlockStopEvent = true;
    567    NS_DispatchToCurrentThread(NS_NewRunnableFunction(
    568        "dom::AudioChannelService::AudioChannelWindow::"
    569        "MaybeNotifyMediaBlockStart",
    570        [window]() -> void {
    571          nsCOMPtr<nsIObserverService> observerService =
    572              services::GetObserverService();
    573          if (NS_WARN_IF(!observerService)) {
    574            return;
    575          }
    576 
    577          observerService->NotifyObservers(ToSupports(window), "audio-playback",
    578                                           u"activeMediaBlockStart");
    579        }));
    580  }
    581 }
    582 
    583 }  // namespace mozilla::dom