Bug 1041599 - Maintain a single volume control session between browser, content, and plugins on Windows. r=aklotz
authorJim Mathies <jmathies@mozilla.com>
Sun, 13 Mar 2016 08:25:23 -0500
changeset 288508 bcff334f8746e76b550b4226f0e54d0094bce159
parent 288507 c35e08d87d0c46d6ecb45dbb044bd2d55ac50800
child 288509 4261e08ba51aa5241a05d5a6da8c7557e870ef47
push id30082
push userryanvm@gmail.com
push dateSun, 13 Mar 2016 23:08:35 +0000
treeherdermozilla-central@f0c0480732d3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaklotz
bugs1041599
milestone48.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 1041599 - Maintain a single volume control session between browser, content, and plugins on Windows. r=aklotz MozReview-Commit-ID: 2LuYciKfsWn
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,36 @@ 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)
+    if (NS_FAILED(mozilla::widget::RecvAudioSessionData(aId, aDisplayName,
+                                                        aIconPath))) {
+      return true;
+    }
+
+    // Ignore failures here; we can't really do anything about them
+    mozilla::widget::StartAudioSession();
+    return true;
+#else
+    NS_RUNTIMEABORT("Not Reached!");
+    return false;
+#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,28 @@ 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.
-  CoInitialize(nullptr);
+  // There's a matching CoUninit in Stop() for this tied to a state of
+  // UNINITIALIZED.
+  hr = CoInitialize(nullptr);
+  MOZ_ASSERT(SUCCEEDED(hr), "CoInitialize failure in audio session control, unexpected");
 
   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 +209,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 +246,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 +291,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();
 }