1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
12 // mmsystem.h is needed to build with WIN32_LEAN_AND_MEAN
15 #include "HeadlessSound.h"
18 #include "nsNetUtil.h"
19 #include "nsIChannel.h"
20 #include "nsContentUtils.h"
22 #include "nsIObserverService.h"
24 #include "mozilla/Logging.h"
27 #include "nsNativeCharsetUtils.h"
28 #include "nsThreadUtils.h"
29 #include "mozilla/ClearOnShutdown.h"
30 #include "gfxPlatform.h"
32 using mozilla::LogLevel
;
35 static mozilla::LazyLogModule
gWin32SoundLog("nsSound");
38 // Hackaround for bug 1644240
39 // When we call PlaySound for the first time in the process, winmm.dll creates
40 // a new thread and starts a message loop in winmm!mciwindow. After that,
41 // every call of PlaySound communicates with that thread via Window messages.
42 // It seems that Warsaw application hooks USER32!GetMessageA, and there is
43 // a timing window where they free their trampoline region without reverting
44 // the hook on USER32!GetMessageA, resulting in crash when winmm!mciwindow
45 // receives a message because it tries to jump to a freed buffer.
46 // Based on the crash reports, it happened on all versions of Windows x64, and
47 // the possible condition was wslbdhm64.dll was loaded but wslbscrwh64.dll was
48 // unloaded. Therefore we suppress playing a sound under such a condition.
49 static bool ShouldSuppressPlaySound() {
51 if (::GetModuleHandle(L
"wslbdhm64.dll") &&
52 !::GetModuleHandle(L
"wslbscrwh64.dll")) {
55 #endif // defined(_M_AMD64)
59 class nsSoundPlayer
: public mozilla::Runnable
{
61 explicit nsSoundPlayer(const nsAString
& aSoundName
)
62 : mozilla::Runnable("nsSoundPlayer"),
63 mSoundName(aSoundName
),
64 mSoundData(nullptr) {}
66 nsSoundPlayer(const uint8_t* aData
, size_t aSize
)
67 : mozilla::Runnable("nsSoundPlayer"), mSoundName(u
""_ns
) {
68 MOZ_ASSERT(aSize
> 0, "Size should not be zero");
69 MOZ_ASSERT(aData
, "Data shoud not be null");
71 // We will disptach nsSoundPlayer to playerthread, so keep a data copy
72 mSoundData
= new uint8_t[aSize
];
73 memcpy(mSoundData
, aData
, aSize
);
86 nsSoundPlayer::Run() {
87 if (ShouldSuppressPlaySound()) {
91 MOZ_ASSERT(!mSoundName
.IsEmpty() || mSoundData
,
92 "Sound name or sound data should be specified");
93 DWORD flags
= SND_NODEFAULT
| SND_ASYNC
;
97 ::PlaySoundW(reinterpret_cast<LPCWSTR
>(mSoundData
), nullptr, flags
);
100 ::PlaySoundW(mSoundName
.get(), nullptr, flags
);
105 nsSoundPlayer::~nsSoundPlayer() { delete[] mSoundData
; }
107 mozilla::StaticRefPtr
<nsISound
> nsSound::sInstance
;
110 already_AddRefed
<nsISound
> nsSound::GetInstance() {
112 if (gfxPlatform::IsHeadless()) {
113 sInstance
= new mozilla::widget::HeadlessSound();
115 RefPtr
<nsSound
> sound
= new nsSound();
116 nsresult rv
= sound
->CreatePlayerThread();
117 if (NS_WARN_IF(NS_FAILED(rv
))) {
120 sInstance
= sound
.forget();
122 ClearOnShutdown(&sInstance
);
125 RefPtr
<nsISound
> service
= sInstance
;
126 return service
.forget();
130 // Not available on Windows CE, and according to MSDN
131 // doesn't do anything on recent windows either.
135 NS_IMPL_ISUPPORTS(nsSound
, nsISound
, nsIStreamLoaderObserver
, nsIObserver
)
137 nsSound::nsSound() : mInited(false) {}
139 nsSound::~nsSound() {}
141 void nsSound::PurgeLastSound() {
142 // Halt any currently playing sound.
145 mPlayerThread
->Dispatch(
146 NS_NewRunnableFunction("nsSound::PurgeLastSound",
147 [player
= std::move(mSoundPlayer
)]() {
148 // Capture move mSoundPlayer to lambda then
149 // PlaySoundW(nullptr, nullptr, SND_PURGE)
150 // will be called before freeing the
152 if (ShouldSuppressPlaySound()) {
155 ::PlaySoundW(nullptr, nullptr, SND_PURGE
);
162 NS_IMETHODIMP
nsSound::Beep() {
168 NS_IMETHODIMP
nsSound::OnStreamComplete(nsIStreamLoader
* aLoader
,
169 nsISupports
* context
, nsresult aStatus
,
170 uint32_t dataLen
, const uint8_t* data
) {
171 MOZ_ASSERT(mPlayerThread
, "player thread should not be null ");
172 // print a load error on bad status
173 if (NS_FAILED(aStatus
)) {
176 nsCOMPtr
<nsIRequest
> request
;
177 nsCOMPtr
<nsIChannel
> channel
;
178 aLoader
->GetRequest(getter_AddRefs(request
));
179 if (request
) channel
= do_QueryInterface(request
);
181 nsCOMPtr
<nsIURI
> uri
;
182 channel
->GetURI(getter_AddRefs(uri
));
184 nsAutoCString uriSpec
;
185 uri
->GetSpec(uriSpec
);
186 MOZ_LOG(gWin32SoundLog
, LogLevel::Info
,
187 ("Failed to load %s\n", uriSpec
.get()));
197 if (data
&& dataLen
> 0) {
198 MOZ_ASSERT(!mSoundPlayer
, "mSoundPlayer should be null");
199 mSoundPlayer
= new nsSoundPlayer(data
, dataLen
);
200 MOZ_ASSERT(mSoundPlayer
, "Could not create player");
202 nsresult rv
= mPlayerThread
->Dispatch(mSoundPlayer
, NS_DISPATCH_NORMAL
);
203 if (NS_WARN_IF(FAILED(rv
))) {
211 NS_IMETHODIMP
nsSound::Play(nsIURL
* aURL
) {
217 MOZ_LOG(gWin32SoundLog
, LogLevel::Info
, ("%s\n", url
));
220 nsCOMPtr
<nsIStreamLoader
> loader
;
221 rv
= NS_NewStreamLoader(
222 getter_AddRefs(loader
), aURL
,
224 nsContentUtils::GetSystemPrincipal(),
225 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
226 nsIContentPolicy::TYPE_OTHER
);
230 nsresult
nsSound::CreatePlayerThread() {
234 if (NS_WARN_IF(NS_FAILED(NS_NewNamedThread("PlayEventSound",
235 getter_AddRefs(mPlayerThread
))))) {
236 return NS_ERROR_FAILURE
;
239 // Add an observer for shutdown event to release the thread at that time
240 nsCOMPtr
<nsIObserverService
> observerService
=
241 mozilla::services::GetObserverService();
242 if (!observerService
) {
243 return NS_ERROR_FAILURE
;
246 observerService
->AddObserver(this, "xpcom-shutdown-threads", false);
251 nsSound::Observe(nsISupports
* aSubject
, const char* aTopic
,
252 const char16_t
* aData
) {
253 if (!strcmp(aTopic
, "xpcom-shutdown-threads")) {
257 mPlayerThread
->Shutdown();
258 mPlayerThread
= nullptr;
265 NS_IMETHODIMP
nsSound::Init() {
270 MOZ_ASSERT(mPlayerThread
, "player thread should not be null ");
271 // This call halts a sound if it was still playing.
272 // We have to use the sound library for something to make sure
273 // it is initialized.
274 // If we wait until the first sound is played, there will
275 // be a time lag as the library gets loaded.
276 // This should be done in player thread otherwise it will block main thread
277 // at the first time loading sound library.
278 mPlayerThread
->Dispatch(
279 NS_NewRunnableFunction("nsSound::Init",
281 if (ShouldSuppressPlaySound()) {
284 ::PlaySoundW(nullptr, nullptr, SND_PURGE
);
293 NS_IMETHODIMP
nsSound::PlayEventSound(uint32_t aEventId
) {
294 MOZ_ASSERT(mPlayerThread
, "player thread should not be null ");
297 const wchar_t* sound
= nullptr;
299 case EVENT_NEW_MAIL_RECEIVED
:
302 case EVENT_ALERT_DIALOG_OPEN
:
303 sound
= L
"SystemExclamation";
305 case EVENT_CONFIRM_DIALOG_OPEN
:
306 sound
= L
"SystemQuestion";
308 case EVENT_MENU_EXECUTE
:
309 sound
= L
"MenuCommand";
311 case EVENT_MENU_POPUP
:
312 sound
= L
"MenuPopup";
314 case EVENT_EDITOR_MAX_LEN
:
318 // Win32 plays no sounds at NS_SYSSOUND_PROMPT_DIALOG and
319 // NS_SYSSOUND_SELECT_DIALOG.
322 NS_ASSERTION(sound
, "sound is null");
323 MOZ_ASSERT(!mSoundPlayer
, "mSoundPlayer should be null");
324 mSoundPlayer
= new nsSoundPlayer(nsDependentString(sound
));
325 MOZ_ASSERT(mSoundPlayer
, "Could not create player");
326 nsresult rv
= mPlayerThread
->Dispatch(mSoundPlayer
, NS_DISPATCH_NORMAL
);
327 if (NS_WARN_IF(NS_FAILED(rv
))) {