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/. */
13 // mmsystem.h is needed to build with WIN32_LEAN_AND_MEAN
16 #include "HeadlessSound.h"
19 #include "nsNetUtil.h"
20 #include "nsIChannel.h"
21 #include "nsContentUtils.h"
23 #include "nsIObserverService.h"
25 #include "mozilla/Logging.h"
28 #include "nsNativeCharsetUtils.h"
29 #include "nsThreadUtils.h"
30 #include "mozilla/ClearOnShutdown.h"
31 #include "gfxPlatform.h"
33 using mozilla::LogLevel
;
35 static mozilla::LazyLogModule
gWin32SoundLog("nsSound");
37 // Hackaround for bug 1644240
38 // When we call PlaySound for the first time in the process, winmm.dll creates
39 // a new thread and starts a message loop in winmm!mciwindow. After that,
40 // every call of PlaySound communicates with that thread via Window messages.
41 // It seems that Warsaw application hooks USER32!GetMessageA, and there is
42 // a timing window where they free their trampoline region without reverting
43 // the hook on USER32!GetMessageA, resulting in crash when winmm!mciwindow
44 // receives a message because it tries to jump to a freed buffer.
45 // Based on the crash reports, it happened on all versions of Windows x64, and
46 // the possible condition was wslbdhm64.dll was loaded but wslbscrwh64.dll was
47 // unloaded. Therefore we suppress playing a sound under such a condition.
48 static bool ShouldSuppressPlaySound() {
50 if (::GetModuleHandle(L
"wslbdhm64.dll") &&
51 !::GetModuleHandle(L
"wslbscrwh64.dll")) {
54 #endif // defined(_M_AMD64)
58 class nsSoundPlayer
: public mozilla::Runnable
{
60 explicit nsSoundPlayer(const nsAString
& aSoundName
)
61 : mozilla::Runnable("nsSoundPlayer"),
62 mSoundName(aSoundName
),
63 mSoundData(nullptr) {}
65 nsSoundPlayer(const uint8_t* aData
, size_t aSize
)
66 : mozilla::Runnable("nsSoundPlayer"), mSoundName(u
""_ns
) {
67 MOZ_ASSERT(aSize
> 0, "Size should not be zero");
68 MOZ_ASSERT(aData
, "Data shoud not be null");
70 // We will disptach nsSoundPlayer to playerthread, so keep a data copy
71 mSoundData
= new uint8_t[aSize
];
72 memcpy(mSoundData
, aData
, aSize
);
85 nsSoundPlayer::Run() {
86 if (ShouldSuppressPlaySound()) {
90 MOZ_ASSERT(!mSoundName
.IsEmpty() || mSoundData
,
91 "Sound name or sound data should be specified");
92 DWORD flags
= SND_NODEFAULT
| SND_ASYNC
;
96 ::PlaySoundW(reinterpret_cast<LPCWSTR
>(mSoundData
), nullptr, flags
);
99 ::PlaySoundW(mSoundName
.get(), nullptr, flags
);
104 nsSoundPlayer::~nsSoundPlayer() { delete[] mSoundData
; }
106 mozilla::StaticRefPtr
<nsISound
> nsSound::sInstance
;
109 already_AddRefed
<nsISound
> nsSound::GetInstance() {
111 if (gfxPlatform::IsHeadless()) {
112 sInstance
= new mozilla::widget::HeadlessSound();
114 RefPtr
<nsSound
> sound
= new nsSound();
115 nsresult rv
= sound
->CreatePlayerThread();
116 if (NS_WARN_IF(NS_FAILED(rv
))) {
119 sInstance
= sound
.forget();
121 ClearOnShutdown(&sInstance
);
124 RefPtr
<nsISound
> service
= sInstance
;
125 return service
.forget();
129 // Not available on Windows CE, and according to MSDN
130 // doesn't do anything on recent windows either.
134 NS_IMPL_ISUPPORTS(nsSound
, nsISound
, nsIStreamLoaderObserver
, nsIObserver
)
136 nsSound::nsSound() : mInited(false) {}
138 nsSound::~nsSound() {}
140 void nsSound::PurgeLastSound() {
141 // Halt any currently playing sound.
144 mPlayerThread
->Dispatch(
145 NS_NewRunnableFunction("nsSound::PurgeLastSound",
146 [player
= std::move(mSoundPlayer
)]() {
147 // Capture move mSoundPlayer to lambda then
148 // PlaySoundW(nullptr, nullptr, SND_PURGE)
149 // will be called before freeing the
151 if (ShouldSuppressPlaySound()) {
154 ::PlaySoundW(nullptr, nullptr, SND_PURGE
);
161 NS_IMETHODIMP
nsSound::Beep() {
167 NS_IMETHODIMP
nsSound::OnStreamComplete(nsIStreamLoader
* aLoader
,
168 nsISupports
* context
, nsresult aStatus
,
169 uint32_t dataLen
, const uint8_t* data
) {
170 MOZ_ASSERT(mPlayerThread
, "player thread should not be null ");
171 // print a load error on bad status
172 if (NS_FAILED(aStatus
)) {
175 nsCOMPtr
<nsIRequest
> request
;
176 nsCOMPtr
<nsIChannel
> channel
;
177 aLoader
->GetRequest(getter_AddRefs(request
));
178 if (request
) channel
= do_QueryInterface(request
);
180 nsCOMPtr
<nsIURI
> uri
;
181 channel
->GetURI(getter_AddRefs(uri
));
183 nsAutoCString uriSpec
;
184 uri
->GetSpec(uriSpec
);
185 MOZ_LOG(gWin32SoundLog
, LogLevel::Info
,
186 ("Failed to load %s\n", uriSpec
.get()));
196 if (data
&& dataLen
> 0) {
197 MOZ_ASSERT(!mSoundPlayer
, "mSoundPlayer should be null");
198 mSoundPlayer
= new nsSoundPlayer(data
, dataLen
);
199 MOZ_ASSERT(mSoundPlayer
, "Could not create player");
201 nsresult rv
= mPlayerThread
->Dispatch(mSoundPlayer
, NS_DISPATCH_NORMAL
);
202 if (NS_WARN_IF(FAILED(rv
))) {
210 NS_IMETHODIMP
nsSound::Play(nsIURL
* aURL
) {
216 MOZ_LOG(gWin32SoundLog
, LogLevel::Info
, ("%s\n", url
));
219 nsCOMPtr
<nsIStreamLoader
> loader
;
220 rv
= NS_NewStreamLoader(
221 getter_AddRefs(loader
), aURL
,
223 nsContentUtils::GetSystemPrincipal(),
224 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
225 nsIContentPolicy::TYPE_OTHER
);
229 nsresult
nsSound::CreatePlayerThread() {
233 if (NS_WARN_IF(NS_FAILED(NS_NewNamedThread("PlayEventSound",
234 getter_AddRefs(mPlayerThread
))))) {
235 return NS_ERROR_FAILURE
;
238 // Add an observer for shutdown event to release the thread at that time
239 nsCOMPtr
<nsIObserverService
> observerService
=
240 mozilla::services::GetObserverService();
241 if (!observerService
) {
242 return NS_ERROR_FAILURE
;
245 observerService
->AddObserver(this, "xpcom-shutdown-threads", false);
250 nsSound::Observe(nsISupports
* aSubject
, const char* aTopic
,
251 const char16_t
* aData
) {
252 if (!strcmp(aTopic
, "xpcom-shutdown-threads")) {
256 mPlayerThread
->Shutdown();
257 mPlayerThread
= nullptr;
264 NS_IMETHODIMP
nsSound::Init() {
269 MOZ_ASSERT(mPlayerThread
, "player thread should not be null ");
270 // This call halts a sound if it was still playing.
271 // We have to use the sound library for something to make sure
272 // it is initialized.
273 // If we wait until the first sound is played, there will
274 // be a time lag as the library gets loaded.
275 // This should be done in player thread otherwise it will block main thread
276 // at the first time loading sound library.
277 mPlayerThread
->Dispatch(
278 NS_NewRunnableFunction("nsSound::Init",
280 if (ShouldSuppressPlaySound()) {
283 ::PlaySoundW(nullptr, nullptr, SND_PURGE
);
292 NS_IMETHODIMP
nsSound::PlayEventSound(uint32_t aEventId
) {
293 MOZ_ASSERT(mPlayerThread
, "player thread should not be null ");
296 const wchar_t* sound
= nullptr;
298 case EVENT_NEW_MAIL_RECEIVED
:
301 case EVENT_ALERT_DIALOG_OPEN
:
302 sound
= L
"SystemExclamation";
304 case EVENT_CONFIRM_DIALOG_OPEN
:
305 sound
= L
"SystemQuestion";
307 case EVENT_MENU_EXECUTE
:
308 sound
= L
"MenuCommand";
310 case EVENT_MENU_POPUP
:
311 sound
= L
"MenuPopup";
313 case EVENT_EDITOR_MAX_LEN
:
317 // Win32 plays no sounds at NS_SYSSOUND_PROMPT_DIALOG and
318 // NS_SYSSOUND_SELECT_DIALOG.
321 NS_ASSERTION(sound
, "sound is null");
322 MOZ_ASSERT(!mSoundPlayer
, "mSoundPlayer should be null");
323 mSoundPlayer
= new nsSoundPlayer(nsDependentString(sound
));
324 MOZ_ASSERT(mSoundPlayer
, "Could not create player");
325 nsresult rv
= mPlayerThread
->Dispatch(mSoundPlayer
, NS_DISPATCH_NORMAL
);
326 if (NS_WARN_IF(NS_FAILED(rv
))) {