Bug 1041599 - Maintain a single volume control session between browser, content, and plugins on Windows. r?aklotz draft
authorJim Mathies <jmathies@mozilla.com>
Fri, 11 Mar 2016 13:05:07 -0600
changeset 339534 0ad9f6cbe0d500db8bebf37afa66e4dfcff95952
parent 339528 102886e9ac63b3fa33a6f1b394aea054abce2dfd
child 516015 3294f5ce3635d8ab1bc5c1bdcbc41fd1c3b702ad
push id12759
push userjmathies@mozilla.com
push dateFri, 11 Mar 2016 19:05:30 +0000
reviewersaklotz
bugs1041599
milestone48.0a1
Bug 1041599 - Maintain a single volume control session between browser, content, and plugins on Windows. r?aklotz MozReview-Commit-ID: BD8WADYeXo2
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/PContent.ipdl
dom/plugins/ipc/PluginModuleParent.cpp
toolkit/xre/nsAppRunner.cpp
widget/windows/AudioSession.cpp
widget/windows/nsAppShell.cpp
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -142,16 +142,17 @@
 #include "nsVolume.h"
 #include "nsVolumeService.h"
 #include "SpeakerManagerService.h"
 #endif
 
 #ifdef XP_WIN
 #include <process.h>
 #define getpid _getpid
+#include "mozilla/widget/AudioSession.h"
 #endif
 
 #ifdef MOZ_X11
 #include "mozilla/X11Util.h"
 #endif
 
 #ifdef ACCESSIBILITY
 #include "nsIAccessibilityService.h"
@@ -3038,16 +3039,20 @@ ContentChild::RecvShutdown()
   }
 
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
     os->NotifyObservers(static_cast<nsIContentChild*>(this),
                           "content-child-shutdown", nullptr);
   }
 
+#if defined XP_WIN
+    mozilla::widget::StopAudioSession();
+#endif
+
   GetIPCChannel()->SetAbortOnError(false);
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
   if (profiler_is_active()) {
     // We're shutting down while we were profiling. Send the
     // profile up to the parent so that we don't lose this
     // information.
     Unused << RecvGatherProfile();
@@ -3156,16 +3161,34 @@ ContentChild::RecvGamepadUpdate(const Ga
   RefPtr<GamepadService> svc(GamepadService::GetService());
   if (svc) {
     svc->Update(aGamepadEvent);
   }
 #endif
   return true;
 }
 
+bool
+ContentChild::RecvSetAudioSessionData(const nsID& aId,
+                                      const nsString& aDisplayName,
+                                      const nsString& aIconPath)
+{
+#if !defined XP_WIN
+    NS_RUNTIMEABORT("Not Reached!");
+    return false;
+#else
+    nsresult rv = mozilla::widget::RecvAudioSessionData(aId, aDisplayName, aIconPath);
+    NS_ENSURE_SUCCESS(rv, true); // Bail early if this fails
+
+    // Ignore failures here; we can't really do anything about them
+    mozilla::widget::StartAudioSession();
+    return true;
+#endif
+}
+
 // This code goes here rather than nsGlobalWindow.cpp because nsGlobalWindow.cpp
 // can't include ContentChild.h since it includes windows.h.
 
 static uint64_t gNextWindowID = 0;
 
 // We use only 53 bits for the window ID so that it can be converted to and from
 // a JS value without loss of precision. The upper bits of the window ID hold the
 // process ID. The lower bits identify the window.
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -605,16 +605,22 @@ public:
   AllocPContentPermissionRequestChild(const InfallibleTArray<PermissionRequest>& aRequests,
                                       const IPC::Principal& aPrincipal,
                                       const TabId& aTabId) override;
   virtual bool
   DeallocPContentPermissionRequestChild(PContentPermissionRequestChild* actor) override;
 
   virtual bool RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent) override;
 
+  // Windows specific - set up audio session
+  virtual bool
+  RecvSetAudioSessionData(const nsID& aId,
+                          const nsString& aDisplayName,
+                          const nsString& aIconPath) override;
+
 private:
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   virtual void ProcessingError(Result aCode, const char* aReason) override;
 
   /**
    * Exit *now*.  Do not shut down XPCOM, do not pass Go, do not run
    * static destructors, do not collect $200.
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -258,16 +258,20 @@ using namespace mozilla::system;
 #include "nsIProfiler.h"
 #include "nsIProfileSaveEvent.h"
 #endif
 
 #ifdef MOZ_GAMEPAD
 #include "mozilla/dom/GamepadMonitoring.h"
 #endif
 
+#ifdef XP_WIN
+#include "mozilla/widget/AudioSession.h"
+#endif
+
 #include "VRManagerParent.h"            // for VRManagerParent
 
 static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
 
 #if defined(XP_WIN)
 // e10s forced enable pref, defined in nsAppRunner.cpp
 extern const char* kForceEnableE10sPref;
 #endif
@@ -2615,16 +2619,26 @@ ContentParent::InitInternal(ProcessPrior
       MOZ_ASSERT(static_cast<const FileDescriptor&>(brokerFd).IsValid());
     }
   }
 #endif
   if (shouldSandbox && !SendSetProcessSandbox(brokerFd)) {
     KillHard("SandboxInitFailed");
   }
 #endif
+#if defined XP_WIN
+  // Send the info needed to join the browser process's audio session.
+  nsID id;
+  nsString sessionName;
+  nsString iconPath;
+  if (NS_SUCCEEDED(mozilla::widget::GetAudioSessionData(id, sessionName,
+                                                        iconPath))) {
+    Unused << SendSetAudioSessionData(id, sessionName, iconPath);
+  }
+#endif
 }
 
 bool
 ContentParent::IsAlive() const
 {
   return mIsAlive;
 }
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -737,16 +737,23 @@ child:
      */
     async PushWithData(nsCString scope, Principal principal, uint8_t[] data);
 
     /**
      * Send a `pushsubscriptionchange` event to a service worker in the child.
      */
     async PushSubscriptionChange(nsCString scope, Principal principal);
 
+    /**
+     * Windows specific: associate this content process with the browsers
+     * audio session.
+     */
+    async SetAudioSessionData(nsID aID,
+                              nsString aDisplayName,
+                              nsString aIconPath);
 parent:
     /**
      * Tell the content process some attributes of itself.  This is
      * among the first information queried by content processes after
      * startup.  (The message is sync to allow the content process to
      * control when it receives the information.)
      *
      * |id| is a unique ID among all subprocesses.  When |isForApp &&
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -2360,18 +2360,18 @@ PluginModuleChromeParent::RecvNP_Initial
         }
     } else if (aError == NPERR_NO_ERROR) {
         // Initialization steps for (e10s && !asyncInit) || !e10s
 #if defined XP_WIN
         if (mIsStartingAsync) {
             SetPluginFuncs(mNPPIface);
         }
 
-        // Send the info needed to join the chrome process's audio session to the
-        // plugin process
+        // Send the info needed to join the browser process's audio session to the
+        // plugin process.
         nsID id;
         nsString sessionName;
         nsString iconPath;
 
         if (NS_SUCCEEDED(mozilla::widget::GetAudioSessionData(id, sessionName,
                                                               iconPath))) {
             Unused << SendSetAudioSessionData(id, sessionName, iconPath);
         }
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -99,16 +99,17 @@
 
 #ifdef XP_WIN
 #include "nsIWinAppHelper.h"
 #include <windows.h>
 #include <intrin.h>
 #include <math.h>
 #include "cairo/cairo-features.h"
 #include "mozilla/WindowsVersion.h"
+#include "mozilla/widget/AudioSession.h"
 
 #ifndef PROCESS_DEP_ENABLE
 #define PROCESS_DEP_ENABLE 0x1
 #endif
 #endif
 
 #if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
 #include "nsIUUIDGenerator.h"
@@ -4415,16 +4416,20 @@ XREMain::XRE_main(int argc, char* argv[]
     if (mRemoteService) {
       mRemoteService->Shutdown();
     }
 #endif /* MOZ_ENABLE_XREMOTE */
   }
 
   mScopedXPCOM = nullptr;
 
+#if defined(XP_WIN)
+  mozilla::widget::StopAudioSession();
+#endif
+
   // unlock the profile after ScopedXPCOMStartup object (xpcom) 
   // has gone out of scope.  see bug #386739 for more details
   mProfileLock->Unlock();
   gProfileLock = nullptr;
 
 #if defined(MOZ_WIDGET_QT)
   nsQAppInstance::Release();
 #endif
--- a/widget/windows/AudioSession.cpp
+++ b/widget/windows/AudioSession.cpp
@@ -71,17 +71,17 @@ public:
   nsresult SetSessionData(const nsID& aID,
                           const nsString& aSessionName,
                           const nsString& aIconPath);
 
   enum SessionState {
     UNINITIALIZED, // Has not been initialized yet
     STARTED, // Started
     CLONED, // SetSessionInfoCalled, Start not called
-    FAILED, // The autdio session failed to start
+    FAILED, // The audio session failed to start
     STOPPED, // Stop called
     AUDIO_SESSION_DISCONNECTED // Audio session disconnected
   };
 protected:
   RefPtr<IAudioSessionControl> mAudioSessionControl;
   nsString mDisplayName;
   nsString mIconPath;
   nsID mSessionGroupingParameter;
@@ -180,26 +180,27 @@ AudioSession::Start()
              "State invariants violated");
 
   const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
   const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
   const IID IID_IAudioSessionManager = __uuidof(IAudioSessionManager);
 
   HRESULT hr;
 
-  // Don't check for errors in case something already initialized COM
-  // on this thread.
+  // There's a matching CoUninit in Stop() for this tied to a state of
+  // UNINITIALIZED.
   CoInitialize(nullptr);
 
   if (mState == UNINITIALIZED) {
     mState = FAILED;
 
-    // XXXkhuey implement this for content processes
-    if (XRE_IsContentProcess())
+    // Content processes should be CLONED
+    if (XRE_IsContentProcess()) {
       return NS_ERROR_FAILURE;
+    }
 
     MOZ_ASSERT(XRE_IsParentProcess(),
                "Should only get here in a chrome process!");
 
     nsCOMPtr<nsIStringBundleService> bundleService = 
       do_GetService(NS_STRINGBUNDLE_CONTRACTID);
     NS_ENSURE_TRUE(bundleService, NS_ERROR_FAILURE);
     nsCOMPtr<nsIStringBundle> bundle;
@@ -207,19 +208,16 @@ AudioSession::Start()
                                 getter_AddRefs(bundle));
     NS_ENSURE_TRUE(bundle, NS_ERROR_FAILURE);
 
     bundle->GetStringFromName(MOZ_UTF16("brandFullName"),
                               getter_Copies(mDisplayName));
 
     wchar_t *buffer;
     mIconPath.GetMutableData(&buffer, MAX_PATH);
-
-    // XXXkhuey we should provide a way for a xulrunner app to specify an icon
-    // that's not in the product binary.
     ::GetModuleFileNameW(nullptr, buffer, MAX_PATH);
 
     nsCOMPtr<nsIUUIDGenerator> uuidgen =
       do_GetService("@mozilla.org/uuid-generator;1");
     NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE);
     uuidgen->GenerateUUIDInPlace(&mSessionGroupingParameter);
   }
 
@@ -247,26 +245,29 @@ AudioSession::Start()
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<IAudioSessionManager> manager;
   hr = device->Activate(IID_IAudioSessionManager,
                         CLSCTX_ALL,
                         nullptr,
                         getter_AddRefs(manager));
-  if (FAILED(hr))
+  if (FAILED(hr)) {
     return NS_ERROR_FAILURE;
+  }
 
-  hr = manager->GetAudioSessionControl(nullptr,
-                                       FALSE,
+  hr = manager->GetAudioSessionControl(&GUID_NULL,
+                                       0,
                                        getter_AddRefs(mAudioSessionControl));
-  if (FAILED(hr))
+
+  if (FAILED(hr)) {
     return NS_ERROR_FAILURE;
+  }
 
-  hr = mAudioSessionControl->SetGroupingParam((LPCGUID)&mSessionGroupingParameter,
+  hr = mAudioSessionControl->SetGroupingParam((LPGUID)&mSessionGroupingParameter,
                                               nullptr);
   if (FAILED(hr)) {
     StopInternal();
     return NS_ERROR_FAILURE;
   }
 
   hr = mAudioSessionControl->SetDisplayName(mDisplayName.get(), nullptr);
   if (FAILED(hr)) {
@@ -289,44 +290,42 @@ AudioSession::Start()
   mState = STARTED;
 
   return NS_OK;
 }
 
 void
 AudioSession::StopInternal()
 {
-  static const nsID blankId = {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0} };
-
   if (mAudioSessionControl) {
-    mAudioSessionControl->SetGroupingParam((LPCGUID)&blankId, nullptr);
     mAudioSessionControl->UnregisterAudioSessionNotification(this);
     mAudioSessionControl = nullptr;
   }
 }
 
 nsresult
 AudioSession::Stop()
 {
   MOZ_ASSERT(mState == STARTED ||
              mState == UNINITIALIZED || // XXXremove this
              mState == FAILED,
              "State invariants violated");
+  SessionState state = mState;
   mState = STOPPED;
 
-  RefPtr<AudioSession> kungFuDeathGrip;
-  kungFuDeathGrip.swap(sService);
+  {
+    RefPtr<AudioSession> kungFuDeathGrip;
+    kungFuDeathGrip.swap(sService);
 
-  if (!XRE_IsContentProcess())
     StopInternal();
-
-  // At this point kungFuDeathGrip should be the only reference to AudioSession
+  }
 
-  ::CoUninitialize();
-
+  if (state != UNINITIALIZED) {
+    ::CoUninitialize();
+  }
   return NS_OK;
 }
 
 void CopynsID(nsID& lhs, const nsID& rhs)
 {
   lhs.m0 = rhs.m0;
   lhs.m1 = rhs.m1;
   lhs.m2 = rhs.m2;
--- a/widget/windows/nsAppShell.cpp
+++ b/widget/windows/nsAppShell.cpp
@@ -241,30 +241,33 @@ nsAppShell::Init()
   NS_ENSURE_STATE(mEventWnd);
 
   return nsBaseAppShell::Init();
 }
 
 NS_IMETHODIMP
 nsAppShell::Run(void)
 {
-  // Ignore failure; failing to start the application is not exactly an
-  // appropriate response to failing to start an audio session.
-  mozilla::widget::StartAudioSession();
+  // Content processes initialize audio later through PContent using audio
+  // tray id information pulled from the browser process AudioSession. This
+  // way the two share a single volume control.
+  // Note StopAudioSession() is called from nsAppRunner.cpp after xpcom is torn
+  // down to insure the browser shuts down after child processes.
+  if (XRE_IsParentProcess()) {
+    mozilla::widget::StartAudioSession();
+  }
 
   // Add an observer that disables the screen saver when requested by Gecko.
   // For example when we're playing video in the foreground tab.
   AddScreenWakeLockListener();
 
   nsresult rv = nsBaseAppShell::Run();
 
   RemoveScreenWakeLockListener();
 
-  mozilla::widget::StopAudioSession();
-
   return rv;
 }
 
 NS_IMETHODIMP
 nsAppShell::Exit(void)
 {
   return nsBaseAppShell::Exit();
 }