Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / windows / nsSound.cpp
blob1fecf09c3ae5924ec9d2efd0ac72de61be67349c
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/. */
7 #include "nscore.h"
8 #include <stdio.h>
9 #include "nsString.h"
10 #include <windows.h>
12 // mmsystem.h is needed to build with WIN32_LEAN_AND_MEAN
13 #include <mmsystem.h>
15 #include "HeadlessSound.h"
16 #include "nsSound.h"
17 #include "nsIURL.h"
18 #include "nsNetUtil.h"
19 #include "nsIChannel.h"
20 #include "nsContentUtils.h"
21 #include "nsCRT.h"
22 #include "nsIObserverService.h"
24 #include "mozilla/Logging.h"
25 #include "prtime.h"
27 #include "nsNativeCharsetUtils.h"
28 #include "nsThreadUtils.h"
29 #include "mozilla/ClearOnShutdown.h"
30 #include "gfxPlatform.h"
32 using mozilla::LogLevel;
34 #ifdef DEBUG
35 static mozilla::LazyLogModule gWin32SoundLog("nsSound");
36 #endif
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() {
50 #if defined(_M_AMD64)
51 if (::GetModuleHandle(L"wslbdhm64.dll") &&
52 !::GetModuleHandle(L"wslbscrwh64.dll")) {
53 return true;
55 #endif // defined(_M_AMD64)
56 return false;
59 class nsSoundPlayer : public mozilla::Runnable {
60 public:
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);
76 NS_DECL_NSIRUNNABLE
78 protected:
79 ~nsSoundPlayer();
81 nsString mSoundName;
82 uint8_t* mSoundData;
85 NS_IMETHODIMP
86 nsSoundPlayer::Run() {
87 if (ShouldSuppressPlaySound()) {
88 return NS_OK;
91 MOZ_ASSERT(!mSoundName.IsEmpty() || mSoundData,
92 "Sound name or sound data should be specified");
93 DWORD flags = SND_NODEFAULT | SND_ASYNC;
95 if (mSoundData) {
96 flags |= SND_MEMORY;
97 ::PlaySoundW(reinterpret_cast<LPCWSTR>(mSoundData), nullptr, flags);
98 } else {
99 flags |= SND_ALIAS;
100 ::PlaySoundW(mSoundName.get(), nullptr, flags);
102 return NS_OK;
105 nsSoundPlayer::~nsSoundPlayer() { delete[] mSoundData; }
107 mozilla::StaticRefPtr<nsISound> nsSound::sInstance;
109 /* static */
110 already_AddRefed<nsISound> nsSound::GetInstance() {
111 if (!sInstance) {
112 if (gfxPlatform::IsHeadless()) {
113 sInstance = new mozilla::widget::HeadlessSound();
114 } else {
115 RefPtr<nsSound> sound = new nsSound();
116 nsresult rv = sound->CreatePlayerThread();
117 if (NS_WARN_IF(NS_FAILED(rv))) {
118 return nullptr;
120 sInstance = sound.forget();
122 ClearOnShutdown(&sInstance);
125 RefPtr<nsISound> service = sInstance;
126 return service.forget();
129 #ifndef SND_PURGE
130 // Not available on Windows CE, and according to MSDN
131 // doesn't do anything on recent windows either.
132 # define SND_PURGE 0
133 #endif
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.
143 if (mSoundPlayer) {
144 if (mPlayerThread) {
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
151 // nsSoundPlayer.
152 if (ShouldSuppressPlaySound()) {
153 return;
155 ::PlaySoundW(nullptr, nullptr, SND_PURGE);
157 NS_DISPATCH_NORMAL);
162 NS_IMETHODIMP nsSound::Beep() {
163 ::MessageBeep(0);
165 return NS_OK;
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)) {
174 #ifdef DEBUG
175 if (aLoader) {
176 nsCOMPtr<nsIRequest> request;
177 nsCOMPtr<nsIChannel> channel;
178 aLoader->GetRequest(getter_AddRefs(request));
179 if (request) channel = do_QueryInterface(request);
180 if (channel) {
181 nsCOMPtr<nsIURI> uri;
182 channel->GetURI(getter_AddRefs(uri));
183 if (uri) {
184 nsAutoCString uriSpec;
185 uri->GetSpec(uriSpec);
186 MOZ_LOG(gWin32SoundLog, LogLevel::Info,
187 ("Failed to load %s\n", uriSpec.get()));
191 #endif
192 return aStatus;
195 PurgeLastSound();
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))) {
204 return rv;
208 return NS_OK;
211 NS_IMETHODIMP nsSound::Play(nsIURL* aURL) {
212 nsresult rv;
214 #ifdef DEBUG_SOUND
215 char* url;
216 aURL->GetSpec(&url);
217 MOZ_LOG(gWin32SoundLog, LogLevel::Info, ("%s\n", url));
218 #endif
220 nsCOMPtr<nsIStreamLoader> loader;
221 rv = NS_NewStreamLoader(
222 getter_AddRefs(loader), aURL,
223 this, // aObserver
224 nsContentUtils::GetSystemPrincipal(),
225 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
226 nsIContentPolicy::TYPE_OTHER);
227 return rv;
230 nsresult nsSound::CreatePlayerThread() {
231 if (mPlayerThread) {
232 return NS_OK;
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);
247 return NS_OK;
250 NS_IMETHODIMP
251 nsSound::Observe(nsISupports* aSubject, const char* aTopic,
252 const char16_t* aData) {
253 if (!strcmp(aTopic, "xpcom-shutdown-threads")) {
254 PurgeLastSound();
256 if (mPlayerThread) {
257 mPlayerThread->Shutdown();
258 mPlayerThread = nullptr;
262 return NS_OK;
265 NS_IMETHODIMP nsSound::Init() {
266 if (mInited) {
267 return NS_OK;
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",
280 []() {
281 if (ShouldSuppressPlaySound()) {
282 return;
284 ::PlaySoundW(nullptr, nullptr, SND_PURGE);
286 NS_DISPATCH_NORMAL);
288 mInited = true;
290 return NS_OK;
293 NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId) {
294 MOZ_ASSERT(mPlayerThread, "player thread should not be null ");
295 PurgeLastSound();
297 const wchar_t* sound = nullptr;
298 switch (aEventId) {
299 case EVENT_NEW_MAIL_RECEIVED:
300 sound = L"MailBeep";
301 break;
302 case EVENT_ALERT_DIALOG_OPEN:
303 sound = L"SystemExclamation";
304 break;
305 case EVENT_CONFIRM_DIALOG_OPEN:
306 sound = L"SystemQuestion";
307 break;
308 case EVENT_MENU_EXECUTE:
309 sound = L"MenuCommand";
310 break;
311 case EVENT_MENU_POPUP:
312 sound = L"MenuPopup";
313 break;
314 case EVENT_EDITOR_MAX_LEN:
315 sound = L".Default";
316 break;
317 default:
318 // Win32 plays no sounds at NS_SYSSOUND_PROMPT_DIALOG and
319 // NS_SYSSOUND_SELECT_DIALOG.
320 return NS_OK;
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))) {
328 return rv;
330 return NS_OK;