Bug 1251202 - Implement Default Audio Device Notifications for NPAPI plugins on Windows. r=jimm

Adds plugin variables NPPVpluginRequiresAudioDeviceChanges and NPNVaudioDeviceChangeDetails on Windows.

--HG--
extra : rebase_source : ff18bbca9772352b18be1dfcde90570775e8d5cc
This commit is contained in:
David Parks 2016-09-19 14:05:41 -07:00
parent f0fc76e6d7
commit 17683f933c
12 changed files with 504 additions and 0 deletions

View File

@ -259,6 +259,26 @@ typedef struct
#endif /* XP_UNIX */
#if defined(XP_WIN)
/*
* Windows specific structures and definitions
*/
/*
* Information about the default audio device. These values share meaning with
* the parameters to the Windows API IMMNotificationClient object.
* This is the value of the NPNVaudioDeviceChangeDetails variable.
*/
typedef struct _NPAudioDeviceChangeDetails
{
int32_t flow;
int32_t role;
const wchar_t* defaultDevice; // this pointer is only valid during the call
// to NPPSetValue.
} NPAudioDeviceChangeDetails;
#endif /* XP_WIN */
typedef enum {
NPDrawingModelDUMMY
#if defined(XP_MACOSX)
@ -379,6 +399,10 @@ typedef enum {
#endif
/* Notification that the plugin just started or stopped playing audio */
, NPPVpluginIsPlayingAudio = 4000
#if defined(XP_WIN)
/* Notification that the plugin requests notification when the default audio device has changed */
, NPPVpluginRequiresAudioDeviceChanges = 4001
#endif
} NPPVariable;
@ -442,6 +466,9 @@ typedef enum {
Cocoa text input specification. */
#endif
, NPNVmuteAudioBool = 4000 /* Request that the browser wants to mute or unmute the plugin */
#if defined(XP_WIN)
, NPNVaudioDeviceChangeDetails = 4001 /* Provides information about the new default audio device */
#endif
#if defined(XP_MACOSX)
, NPNVsupportsCompositingCoreAnimationPluginsBool = 74656 /* TRUE if the browser supports
CA model compositing */

View File

@ -14,6 +14,7 @@ using NPNVariable from "npapi.h";
using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
using class mac_plugin_interposing::NSCursorInfo from "mozilla/plugins/PluginMessageUtils.h";
using struct nsID from "nsID.h";
using struct mozilla::plugins::NPAudioDeviceChangeDetailsIPC from "mozilla/plugins/PluginMessageUtils.h";
namespace mozilla {
namespace plugins {
@ -106,6 +107,8 @@ child:
async SettingChanged(PluginSettings settings);
async NPP_SetValue_NPNVaudioDeviceChangeDetails(NPAudioDeviceChangeDetailsIPC changeDetails);
parent:
async NP_InitializeResult(NPError aError);
@ -159,6 +162,9 @@ parent:
intr GetKeyState(int32_t aVirtKey)
returns (int16_t aState);
intr NPN_SetValue_NPPVpluginRequiresAudioDeviceChanges(bool shouldRegister)
returns (NPError result);
};
} // namespace plugins

View File

@ -224,6 +224,11 @@ PluginInstanceChild::~PluginInstanceChild()
if (GetQuirks() & QUIRK_UNITY_FIXUP_MOUSE_CAPTURE) {
ClearUnityHooks();
}
// In the event that we registered for audio device changes, stop.
PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome();
if (chromeInstance) {
NPError rv = chromeInstance->PluginRequiresAudioDeviceChanges(this, false);
}
#endif
#if defined(MOZ_WIDGET_COCOA)
if (mShColorSpace) {
@ -684,6 +689,23 @@ PluginInstanceChild::NPN_SetValue(NPPVariable aVar, void* aValue)
return rv;
}
#ifdef XP_WIN
case NPPVpluginRequiresAudioDeviceChanges: {
// Many other NPN_SetValue variables are forwarded to our
// PluginInstanceParent, which runs on a content process. We
// instead forward this message to the PluginModuleParent, which runs
// on the chrome process. This is because our audio
// API calls should run the chrome proc, not content.
NPError rv = NPERR_GENERIC_ERROR;
PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome();
if (chromeInstance) {
rv = chromeInstance->PluginRequiresAudioDeviceChanges(this,
(NPBool)(intptr_t)aValue);
}
return rv;
}
#endif
default:
MOZ_LOG(GetPluginLog(), LogLevel::Warning,
("In PluginInstanceChild::NPN_SetValue: Unhandled NPPVariable %i (%s)",
@ -856,6 +878,18 @@ PluginInstanceChild::AnswerNPP_SetValue_NPNVmuteAudioBool(const bool& value,
return true;
}
#if defined(XP_WIN)
NPError
PluginInstanceChild::DefaultAudioDeviceChanged(NPAudioDeviceChangeDetails& details)
{
if (!mPluginIface->setvalue) {
return NPERR_GENERIC_ERROR;
}
return mPluginIface->setvalue(GetNPP(), NPNVaudioDeviceChangeDetails, (void*)&details);
}
#endif
bool
PluginInstanceChild::AnswerNPP_HandleEvent(const NPRemoteEvent& event,
int16_t* handled)

View File

@ -278,6 +278,10 @@ public:
const NativeEventData& aKeyEventData,
const bool& aIsConsumed) override;
#if defined(XP_WIN)
NPError DefaultAudioDeviceChanged(NPAudioDeviceChangeDetails& details);
#endif
private:
friend class PluginModuleChild;

View File

@ -98,6 +98,17 @@ struct NPRemoteWindow
#endif
};
// This struct is like NPAudioDeviceChangeDetails, only it uses a
// std::wstring instead of a const wchar_t* for the defaultDevice.
// This gives us the necessary memory-ownership semantics without
// requiring C++ objects in npapi.h.
struct NPAudioDeviceChangeDetailsIPC
{
int32_t flow;
int32_t role;
std::wstring defaultDevice;
};
#ifdef XP_WIN
typedef HWND NativeWindowHandle;
#elif defined(MOZ_X11)
@ -152,6 +163,10 @@ NPPVariableToString(NPPVariable aVar)
VARSTR(NPPVpluginEventModel);
#endif
#ifdef XP_WIN
VARSTR(NPPVpluginRequiresAudioDeviceChanges);
#endif
default: return "???";
}
}
@ -182,6 +197,10 @@ NPNVariableToString(NPNVariable aVar)
VARSTR(NPNVprivateModeBool);
VARSTR(NPNVdocumentOrigin);
#ifdef XP_WIN
VARSTR(NPNVaudioDeviceChangeDetails);
#endif
default: return "???";
}
}
@ -674,6 +693,40 @@ struct ParamTraits<NPCoordinateSpace>
}
};
template <>
struct ParamTraits<mozilla::plugins::NPAudioDeviceChangeDetailsIPC>
{
typedef mozilla::plugins::NPAudioDeviceChangeDetailsIPC paramType;
static void Write(Message* aMsg, const paramType& aParam)
{
WriteParam(aMsg, aParam.flow);
WriteParam(aMsg, aParam.role);
WriteParam(aMsg, aParam.defaultDevice);
}
static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
{
int32_t flow, role;
std::wstring defaultDevice;
if (ReadParam(aMsg, aIter, &flow) &&
ReadParam(aMsg, aIter, &role) &&
ReadParam(aMsg, aIter, &defaultDevice)) {
aResult->flow = flow;
aResult->role = role;
aResult->defaultDevice = defaultDevice;
return true;
}
return false;
}
static void Log(const paramType& aParam, std::wstring* aLog)
{
aLog->append(StringPrintf(L"[%d, %d, %S]", aParam.flow, aParam.role,
aParam.defaultDevice.c_str()));
}
};
} /* namespace IPC */

View File

@ -2565,3 +2565,62 @@ PluginModuleChild::RecvGatherProfile()
Unused << SendProfile(profileCString);
return true;
}
NPError
PluginModuleChild::PluginRequiresAudioDeviceChanges(
PluginInstanceChild* aInstance,
NPBool aShouldRegister)
{
#ifdef XP_WIN
// Maintain a set of PluginInstanceChildren that we need to tell when the
// default audio device has changed.
NPError rv = NPERR_NO_ERROR;
if (aShouldRegister) {
if (mAudioNotificationSet.IsEmpty()) {
// We are registering the first plugin. Notify the PluginModuleParent
// that it needs to start sending us audio device notifications.
if (!CallNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges(
aShouldRegister, &rv)) {
return NPERR_GENERIC_ERROR;
}
}
if (rv == NPERR_NO_ERROR) {
mAudioNotificationSet.PutEntry(aInstance);
}
}
else if (!mAudioNotificationSet.IsEmpty()) {
mAudioNotificationSet.RemoveEntry(aInstance);
if (mAudioNotificationSet.IsEmpty()) {
// We released the last plugin. Unregister from the PluginModuleParent.
if (!CallNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges(
aShouldRegister, &rv)) {
return NPERR_GENERIC_ERROR;
}
}
}
return rv;
#else
NS_RUNTIMEABORT("PluginRequiresAudioDeviceChanges is not available on this platform.");
return NPERR_GENERIC_ERROR;
#endif // XP_WIN
}
bool
PluginModuleChild::RecvNPP_SetValue_NPNVaudioDeviceChangeDetails(
const NPAudioDeviceChangeDetailsIPC& detailsIPC)
{
#if defined(XP_WIN)
NPAudioDeviceChangeDetails details;
details.flow = detailsIPC.flow;
details.role = detailsIPC.role;
details.defaultDevice = detailsIPC.defaultDevice.c_str();
for (auto iter = mAudioNotificationSet.ConstIter(); !iter.Done(); iter.Next()) {
PluginInstanceChild* pluginInst = iter.Get()->GetKey();
pluginInst->DefaultAudioDeviceChanged(details);
}
return true;
#else
NS_RUNTIMEABORT("NPP_SetValue_NPNVaudioDeviceChangeDetails is a Windows-only message");
return false;
#endif
}

View File

@ -239,6 +239,11 @@ public:
const PluginSettings& Settings() const { return mCachedSettings; }
NPError PluginRequiresAudioDeviceChanges(PluginInstanceChild* aInstance,
NPBool aShouldRegister);
bool RecvNPP_SetValue_NPNVaudioDeviceChangeDetails(
const NPAudioDeviceChangeDetailsIPC& detailsIPC) override;
private:
NPError DoNP_Initialize(const PluginSettings& aSettings);
void AddQuirk(PluginQuirks quirk) {
@ -323,6 +328,13 @@ private:
# endif
#endif
#if defined(XP_WIN)
typedef nsTHashtable<nsPtrHashKey<PluginInstanceChild>> PluginInstanceSet;
// Set of plugins that have registered to be notified when the audio device
// changes.
PluginInstanceSet mAudioNotificationSet;
#endif
public: // called by PluginInstanceChild
/**
* Dealloc an NPObject after last-release or when the associated instance

View File

@ -44,6 +44,7 @@
#include "mozilla/plugins/PluginSurfaceParent.h"
#include "mozilla/widget/AudioSession.h"
#include "PluginHangUIParent.h"
#include "PluginUtilsWin.h"
#endif
#ifdef MOZ_ENABLE_PROFILER_SPS
@ -776,6 +777,12 @@ PluginModuleChromeParent::~PluginModuleChromeParent()
ShutdownPluginProfiling();
#endif
#ifdef XP_WIN
// If we registered for audio notifications, stop.
mozilla::plugins::PluginUtilsWin::RegisterForAudioDeviceChanges(this,
false);
#endif
if (!mShutdown) {
NS_WARNING("Plugin host deleted the module without shutting down.");
NPError err;
@ -1906,6 +1913,26 @@ PluginModuleParent::NPP_SetValue(NPP instance, NPNVariable variable,
RESOLVE_AND_CALL(instance, NPP_SetValue(variable, value));
}
bool
PluginModuleChromeParent::AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges(
const bool& shouldRegister, NPError* result)
{
#ifdef XP_WIN
*result = NPERR_NO_ERROR;
nsresult err =
mozilla::plugins::PluginUtilsWin::RegisterForAudioDeviceChanges(this,
shouldRegister);
if (err != NS_OK) {
*result = NPERR_GENERIC_ERROR;
}
return true;
#else
NS_RUNTIMEABORT("NPPVpluginRequiresAudioDeviceChanges is not valid on this platform.");
*result = NPERR_GENERIC_ERROR;
return true;
#endif
}
bool
PluginModuleParent::RecvBackUpXResources(const FileDescriptor& aXSocketFd)
{
@ -3135,6 +3162,17 @@ PluginModuleParent::EnsureTextureAllocator()
return mTextureAllocator;
}
bool
PluginModuleParent::AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges(
const bool& shouldRegister,
NPError* result) {
NS_RUNTIMEABORT("SetValue_NPPVpluginRequiresAudioDeviceChanges is only valid "
"with PluginModuleChromeParent");
*result = NPERR_GENERIC_ERROR;
return true;
}
#ifdef MOZ_CRASHREPORTER_INJECTOR
// We only add the crash reporter to subprocess which have the filename

View File

@ -198,6 +198,11 @@ protected:
static BrowserStreamParent* StreamCast(NPP instance, NPStream* s,
PluginAsyncSurrogate** aSurrogate = nullptr);
virtual bool
AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges(
const bool& shouldRegister,
NPError* result) override;
protected:
void SetChildTimeout(const int32_t aChildTimeout);
static void TimeoutChanged(const char* aPref, void* aModule);
@ -569,6 +574,11 @@ private:
static void CachedSettingChanged(const char* aPref, void* aModule);
virtual bool
AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges(
const bool& shouldRegister,
NPError* result) override;
PluginProcessParent* mSubprocess;
uint32_t mPluginId;

View File

@ -0,0 +1,237 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* PluginUtilsWin.cpp - top-level Windows plugin management code */
#include <mmdeviceapi.h>
#include "PluginUtilsWin.h"
#include "PluginModuleParent.h"
#include "mozilla/StaticMutex.h"
namespace mozilla {
namespace plugins {
namespace PluginUtilsWin {
typedef nsTHashtable<nsPtrHashKey<PluginModuleParent>> PluginModuleSet;
StaticMutex sMutex;
class AudioDeviceChangedRunnable : public Runnable
{
public:
explicit AudioDeviceChangedRunnable(const PluginModuleSet* aAudioNotificationSet,
NPAudioDeviceChangeDetailsIPC aChangeDetails) :
mChangeDetails(aChangeDetails)
, mAudioNotificationSet(aAudioNotificationSet)
{}
NS_IMETHOD Run() override
{
StaticMutexAutoLock lock(sMutex);
PLUGIN_LOG_DEBUG(("Notifying %d plugins of audio device change.",
mAudioNotificationSet->Count()));
for (auto iter = mAudioNotificationSet->ConstIter(); !iter.Done(); iter.Next()) {
PluginModuleParent* pluginModule = iter.Get()->GetKey();
pluginModule->SendNPP_SetValue_NPNVaudioDeviceChangeDetails(mChangeDetails);
}
return NS_OK;
}
protected:
NPAudioDeviceChangeDetailsIPC mChangeDetails;
const PluginModuleSet* mAudioNotificationSet;
};
class AudioNotification : public IMMNotificationClient
{
public:
AudioNotification() :
mRefCt(1)
, mIsRegistered(false)
{
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&mDeviceEnum));
if (FAILED(hr)) {
mDeviceEnum = nullptr;
return;
}
hr = mDeviceEnum->RegisterEndpointNotificationCallback(this);
if (FAILED(hr)) {
mDeviceEnum->Release();
mDeviceEnum = nullptr;
return;
}
mIsRegistered = true;
}
~AudioNotification()
{
MOZ_ASSERT(!mIsRegistered,
"Destroying AudioNotification without first calling Unregister");
if (mDeviceEnum) {
mDeviceEnum->Release();
}
}
// IMMNotificationClient Implementation
HRESULT STDMETHODCALLTYPE
OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id) override
{
NPAudioDeviceChangeDetailsIPC changeDetails;
changeDetails.flow = (int32_t)flow;
changeDetails.role = (int32_t)role;
changeDetails.defaultDevice = std::wstring(device_id);
// Make sure that plugin is notified on the main thread.
RefPtr<AudioDeviceChangedRunnable> runnable =
new AudioDeviceChangedRunnable(&mAudioNotificationSet, changeDetails);
NS_DispatchToMainThread(runnable);
return S_OK;
}
HRESULT STDMETHODCALLTYPE
OnDeviceAdded(LPCWSTR device_id) override
{
return S_OK;
};
HRESULT STDMETHODCALLTYPE
OnDeviceRemoved(LPCWSTR device_id) override
{
return S_OK;
}
HRESULT STDMETHODCALLTYPE
OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state) override
{
return S_OK;
}
HRESULT STDMETHODCALLTYPE
OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) override
{
return S_OK;
}
// IUnknown Implementation
ULONG STDMETHODCALLTYPE
AddRef() override
{
return InterlockedIncrement(&mRefCt);
}
ULONG STDMETHODCALLTYPE
Release() override
{
ULONG ulRef = InterlockedDecrement(&mRefCt);
if (0 == ulRef) {
delete this;
}
return ulRef;
}
HRESULT STDMETHODCALLTYPE
QueryInterface(REFIID riid, VOID **ppvInterface) override
{
if (__uuidof(IUnknown) == riid) {
AddRef();
*ppvInterface = (IUnknown*)this;
} else if (__uuidof(IMMNotificationClient) == riid) {
AddRef();
*ppvInterface = (IMMNotificationClient*)this;
} else {
*ppvInterface = NULL;
return E_NOINTERFACE;
}
return S_OK;
}
/*
* A Valid instance must be Unregistered before Releasing it.
*/
void Unregister()
{
if (mDeviceEnum) {
mDeviceEnum->UnregisterEndpointNotificationCallback(this);
}
mIsRegistered = false;
}
/*
* True whenever the notification server is set to report events to this object.
*/
bool IsRegistered() {
return mIsRegistered;
}
void AddModule(PluginModuleParent* aModule) {
StaticMutexAutoLock lock(sMutex);
mAudioNotificationSet.PutEntry(aModule);
}
void RemoveModule(PluginModuleParent* aModule) {
StaticMutexAutoLock lock(sMutex);
mAudioNotificationSet.RemoveEntry(aModule);
}
/*
* Are any modules registered for audio notifications?
*/
bool HasModules() {
return !mAudioNotificationSet.IsEmpty();
}
private:
bool mIsRegistered; // only used to make sure that Unregister is called before destroying a Valid instance.
LONG mRefCt;
IMMDeviceEnumerator* mDeviceEnum;
// Set of plugin modules that have registered to be notified when the audio device
// changes.
PluginModuleSet mAudioNotificationSet;
}; // class AudioNotification
// callback that gets notified of audio device events, or NULL
AudioNotification* sAudioNotification = nullptr;
nsresult
RegisterForAudioDeviceChanges(PluginModuleParent* aModuleParent, bool aShouldRegister)
{
// Hold the AudioNotification singleton iff there are PluginModuleParents
// that are subscribed to it.
if (aShouldRegister) {
if (!sAudioNotification) {
// We are registering the first module. Create the singleton.
sAudioNotification = new AudioNotification();
if (!sAudioNotification->IsRegistered()) {
PLUGIN_LOG_DEBUG(("Registered for plugin audio device notification failed."));
sAudioNotification->Release();
sAudioNotification = nullptr;
return NS_ERROR_FAILURE;
}
PLUGIN_LOG_DEBUG(("Registered for plugin audio device notification."));
}
sAudioNotification->AddModule(aModuleParent);
}
else if (!aShouldRegister && sAudioNotification) {
sAudioNotification->RemoveModule(aModuleParent);
if (!sAudioNotification->HasModules()) {
// We have removed the last module from the notification mechanism
// so we can destroy the singleton.
PLUGIN_LOG_DEBUG(("Unregistering for plugin audio device notification."));
sAudioNotification->Unregister();
sAudioNotification->Release();
sAudioNotification = nullptr;
}
}
return NS_OK;
}
} // namespace PluginUtilsWin
} // namespace plugins
} // namespace mozilla

View File

@ -0,0 +1,23 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
// vim:set ts=2 sts=2 sw=2 et cin:
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef dom_plugins_PluginUtilsWin_h
#define dom_plugins_PluginUtilsWin_h 1
#include "npapi.h"
namespace mozilla {
namespace plugins {
namespace PluginUtilsWin {
nsresult RegisterForAudioDeviceChanges(PluginModuleParent* aModuleParent,
bool aShouldRegister);
} // namespace PluginUtilsWin
} // namespace plugins
} // namespace mozilla
#endif //dom_plugins_PluginUtilsWin_h

View File

@ -102,6 +102,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
UNIFIED_SOURCES += [
'D3D11SurfaceHolder.cpp',
'PluginUtilsWin.cpp'
]
IPDL_SOURCES += [