1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 /* mingw currently doesn't support windows.media.h, so we disable
7  * the whole related class until this is fixed.
8  * @TODO: Maybe contact MinGW Team for inclusion?*/
9 #ifndef __MINGW32__
10 
11 #  include "WindowsSMTCProvider.h"
12 
13 #  include <windows.h>
14 #  include <windows.media.h>
15 #  include <winsdkver.h>
16 #  include <wrl.h>
17 
18 #  include "nsMimeTypes.h"
19 #  include "mozilla/Assertions.h"
20 #  include "mozilla/Logging.h"
21 #  include "mozilla/Maybe.h"
22 #  include "mozilla/WidgetUtils.h"
23 #  include "mozilla/WindowsVersion.h"
24 #  include "mozilla/ScopeExit.h"
25 #  include "mozilla/dom/MediaControlUtils.h"
26 #  include "mozilla/media/MediaUtils.h"
27 #  include "nsThreadUtils.h"
28 
29 #  pragma comment(lib, "runtimeobject.lib")
30 
31 using namespace ABI::Windows::Foundation;
32 using namespace ABI::Windows::Media;
33 using namespace ABI::Windows::Storage::Streams;
34 using namespace Microsoft::WRL;
35 using namespace Microsoft::WRL::Wrappers;
36 using namespace mozilla;
37 
38 #  ifndef RuntimeClass_Windows_Media_SystemMediaTransportControls
39 #    define RuntimeClass_Windows_Media_SystemMediaTransportControls \
40       L"Windows.Media.SystemMediaTransportControls"
41 #  endif
42 
43 #  ifndef RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference
44 #    define RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference \
45       L"Windows.Storage.Streams.RandomAccessStreamReference"
46 #  endif
47 
48 #  ifndef ISystemMediaTransportControlsInterop
49 EXTERN_C const IID IID_ISystemMediaTransportControlsInterop;
50 MIDL_INTERFACE("ddb0472d-c911-4a1f-86d9-dc3d71a95f5a")
51 ISystemMediaTransportControlsInterop : public IInspectable {
52  public:
53   virtual HRESULT STDMETHODCALLTYPE GetForWindow(
54       /* [in] */ __RPC__in HWND appWindow,
55       /* [in] */ __RPC__in REFIID riid,
56       /* [iid_is][retval][out] */
57       __RPC__deref_out_opt void** mediaTransportControl) = 0;
58 };
59 #  endif /* __ISystemMediaTransportControlsInterop_INTERFACE_DEFINED__ */
60 
61 extern mozilla::LazyLogModule gMediaControlLog;
62 
63 #  undef LOG
64 #  define LOG(msg, ...)                        \
65     MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
66             ("WindowSMTCProvider=%p, " msg, this, ##__VA_ARGS__))
67 
68 static inline Maybe<mozilla::dom::MediaControlKey> TranslateKeycode(
69     SystemMediaTransportControlsButton keycode) {
70   switch (keycode) {
71     case SystemMediaTransportControlsButton_Play:
72       return Some(mozilla::dom::MediaControlKey::Play);
73     case SystemMediaTransportControlsButton_Pause:
74       return Some(mozilla::dom::MediaControlKey::Pause);
75     case SystemMediaTransportControlsButton_Next:
76       return Some(mozilla::dom::MediaControlKey::Nexttrack);
77     case SystemMediaTransportControlsButton_Previous:
78       return Some(mozilla::dom::MediaControlKey::Previoustrack);
79     case SystemMediaTransportControlsButton_Stop:
80       return Some(mozilla::dom::MediaControlKey::Stop);
81     case SystemMediaTransportControlsButton_FastForward:
82       return Some(mozilla::dom::MediaControlKey::Seekforward);
83     case SystemMediaTransportControlsButton_Rewind:
84       return Some(mozilla::dom::MediaControlKey::Seekbackward);
85     default:
86       return Nothing();  // Not supported Button
87   }
88 }
89 
90 static IAsyncInfo* GetIAsyncInfo(IAsyncOperation<unsigned int>* aAsyncOp) {
91   MOZ_ASSERT(aAsyncOp);
92   IAsyncInfo* asyncInfo;
93   HRESULT hr = aAsyncOp->QueryInterface(IID_IAsyncInfo,
94                                         reinterpret_cast<void**>(&asyncInfo));
95   // The assertion always works since IAsyncOperation implements IAsyncInfo
96   MOZ_ASSERT(SUCCEEDED(hr));
97   Unused << hr;
98   MOZ_ASSERT(asyncInfo);
99   return asyncInfo;
100 }
101 
102 WindowsSMTCProvider::WindowsSMTCProvider() {
103   LOG("Creating an empty and invisible window");
104 
105   // In order to create a SMTC-Provider, we need a hWnd, which shall be created
106   // dynamically from an invisible window. This leads to the following
107   // boilerplate code.
108   WNDCLASS wnd{};
109   wnd.lpszClassName = L"Firefox-MediaKeys";
110   wnd.hInstance = nullptr;
111   wnd.lpfnWndProc = DefWindowProc;
112   GetLastError();  // Clear the error
113   RegisterClass(&wnd);
114   MOZ_ASSERT(!GetLastError());
115 
116   mWindow = CreateWindowExW(0, L"Firefox-MediaKeys", L"Firefox Media Keys", 0,
117                             CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, nullptr,
118                             nullptr, nullptr, nullptr);
119   MOZ_ASSERT(mWindow);
120   MOZ_ASSERT(!GetLastError());
121 }
122 
123 WindowsSMTCProvider::~WindowsSMTCProvider() {
124   // Dispose the window
125   MOZ_ASSERT(mWindow);
126   if (!DestroyWindow(mWindow)) {
127     LOG("Failed to destroy the hidden window. Error Code: %d", GetLastError());
128   }
129   if (!UnregisterClass(L"Firefox-MediaKeys", nullptr)) {
130     // Note that this is logged when the class wasn't even registered.
131     LOG("Failed to unregister the class. Error Code: %d", GetLastError());
132   }
133 }
134 
135 bool WindowsSMTCProvider::IsOpened() const { return mInitialized; }
136 
137 bool WindowsSMTCProvider::Open() {
138   LOG("Opening Source");
139   MOZ_ASSERT(!mInitialized);
140 
141   if (!IsWin8Point1OrLater()) {
142     LOG("Windows 8.1 or later is required for Media Key Support");
143     return false;
144   }
145 
146   if (!InitDisplayAndControls()) {
147     LOG("Failed to initialize the SMTC and its display");
148     return false;
149   }
150 
151   if (!UpdateButtons()) {
152     LOG("Failed to initialize the buttons");
153     return false;
154   }
155 
156   if (!RegisterEvents()) {
157     LOG("Failed to register SMTC key-event listener");
158     return false;
159   }
160 
161   if (!EnableControl(true)) {
162     LOG("Failed to enable SMTC control");
163     return false;
164   }
165 
166   mInitialized = true;
167   SetPlaybackState(mozilla::dom::MediaSessionPlaybackState::None);
168   return mInitialized;
169 }
170 
171 void WindowsSMTCProvider::Close() {
172   MediaControlKeySource::Close();
173   // Prevent calling Set methods when init failed
174   if (mInitialized) {
175     SetPlaybackState(mozilla::dom::MediaSessionPlaybackState::None);
176     UnregisterEvents();
177     ClearMetadata();
178     // We have observed an Windows issue, if we modify `mControls` , (such as
179     // setting metadata, disable buttons) before disabling control, and those
180     // operations are not done sequentially within a same main thread task,
181     // then it would cause a problem where the SMTC wasn't clean up completely
182     // and show the executable name.
183     EnableControl(false);
184     mInitialized = false;
185   }
186 }
187 
188 void WindowsSMTCProvider::SetPlaybackState(
189     mozilla::dom::MediaSessionPlaybackState aState) {
190   MOZ_ASSERT(mInitialized);
191   MediaControlKeySource::SetPlaybackState(aState);
192 
193   HRESULT hr;
194 
195   // Note: we can't return the status of put_PlaybackStatus, but we can at least
196   // assert it.
197   switch (aState) {
198     case mozilla::dom::MediaSessionPlaybackState::Paused:
199       hr = mControls->put_PlaybackStatus(
200           ABI::Windows::Media::MediaPlaybackStatus_Paused);
201       break;
202     case mozilla::dom::MediaSessionPlaybackState::Playing:
203       hr = mControls->put_PlaybackStatus(
204           ABI::Windows::Media::MediaPlaybackStatus_Playing);
205       break;
206     case mozilla::dom::MediaSessionPlaybackState::None:
207       hr = mControls->put_PlaybackStatus(
208           ABI::Windows::Media::MediaPlaybackStatus_Stopped);
209       break;
210       // MediaPlaybackStatus still supports Closed and Changing, which we don't
211       // use (yet)
212     default:
213       MOZ_ASSERT_UNREACHABLE(
214           "Enum Inconsitency between PlaybackState and WindowsSMTCProvider");
215       break;
216   }
217 
218   MOZ_ASSERT(SUCCEEDED(hr));
219   Unused << hr;
220 }
221 
222 void WindowsSMTCProvider::SetMediaMetadata(
223     const mozilla::dom::MediaMetadataBase& aMetadata) {
224   MOZ_ASSERT(mInitialized);
225   SetMusicMetadata(aMetadata.mArtist.get(), aMetadata.mTitle.get(),
226                    aMetadata.mAlbum.get());
227   LoadThumbnail(aMetadata.mArtwork);
228 }
229 
230 void WindowsSMTCProvider::ClearMetadata() {
231   MOZ_ASSERT(mDisplay);
232   if (FAILED(mDisplay->ClearAll())) {
233     LOG("Failed to clear SMTC display");
234   }
235   mImageFetchRequest.DisconnectIfExists();
236   CancelPendingStoreAsyncOperation();
237   mThumbnailUrl.Truncate();
238   mProcessingUrl.Truncate();
239   mNextImageIndex = 0;
240   mSupportedKeys = 0;
241 }
242 
243 void WindowsSMTCProvider::SetSupportedMediaKeys(
244     const MediaKeysArray& aSupportedKeys) {
245   MOZ_ASSERT(mInitialized);
246 
247   uint32_t supportedKeys = 0;
248   for (const mozilla::dom::MediaControlKey& key : aSupportedKeys) {
249     supportedKeys |= GetMediaKeyMask(key);
250   }
251 
252   if (supportedKeys == mSupportedKeys) {
253     LOG("Supported keys stay the same");
254     return;
255   }
256 
257   LOG("Update supported keys");
258   mSupportedKeys = supportedKeys;
259   UpdateButtons();
260 }
261 
262 void WindowsSMTCProvider::UnregisterEvents() {
263   if (mControls && mButtonPressedToken.value != 0) {
264     mControls->remove_ButtonPressed(mButtonPressedToken);
265   }
266 }
267 
268 bool WindowsSMTCProvider::RegisterEvents() {
269   MOZ_ASSERT(mControls);
270   auto self = RefPtr<WindowsSMTCProvider>(this);
271   auto callbackbtnPressed = Callback<
272       ITypedEventHandler<SystemMediaTransportControls*,
273                          SystemMediaTransportControlsButtonPressedEventArgs*>>(
274       [this, self](ISystemMediaTransportControls*,
275                    ISystemMediaTransportControlsButtonPressedEventArgs* pArgs)
276           -> HRESULT {
277         MOZ_ASSERT(pArgs);
278         SystemMediaTransportControlsButton btn;
279 
280         if (FAILED(pArgs->get_Button(&btn))) {
281           LOG("SystemMediaTransportControls: ButtonPressedEvent - Could "
282               "not get Button.");
283           return S_OK;  // Propagating the error probably wouldn't help.
284         }
285 
286         Maybe<mozilla::dom::MediaControlKey> keyCode = TranslateKeycode(btn);
287         if (keyCode.isSome() && IsOpened()) {
288           OnButtonPressed(keyCode.value());
289         }
290         return S_OK;
291       });
292 
293   if (FAILED(mControls->add_ButtonPressed(callbackbtnPressed.Get(),
294                                           &mButtonPressedToken))) {
295     LOG("SystemMediaTransportControls: Failed at "
296         "registerEvents().add_ButtonPressed()");
297     return false;
298   }
299 
300   return true;
301 }
302 
303 void WindowsSMTCProvider::OnButtonPressed(
304     mozilla::dom::MediaControlKey aKey) const {
305   if (!IsKeySupported(aKey)) {
306     LOG("key: %s is not supported", ToMediaControlKeyStr(aKey));
307     return;
308   }
309 
310   for (auto& listener : mListeners) {
311     listener->OnActionPerformed(mozilla::dom::MediaControlAction(aKey));
312   }
313 }
314 
315 bool WindowsSMTCProvider::EnableControl(bool aEnabled) const {
316   MOZ_ASSERT(mControls);
317   return SUCCEEDED(mControls->put_IsEnabled(aEnabled));
318 }
319 
320 bool WindowsSMTCProvider::UpdateButtons() const {
321   static const mozilla::dom::MediaControlKey kKeys[] = {
322       mozilla::dom::MediaControlKey::Play, mozilla::dom::MediaControlKey::Pause,
323       mozilla::dom::MediaControlKey::Previoustrack,
324       mozilla::dom::MediaControlKey::Nexttrack,
325       mozilla::dom::MediaControlKey::Stop};
326 
327   bool success = true;
328   for (const mozilla::dom::MediaControlKey& key : kKeys) {
329     if (!EnableKey(key, IsKeySupported(key))) {
330       success = false;
331       LOG("Failed to set %s=%s", ToMediaControlKeyStr(key),
332           IsKeySupported(key) ? "true" : "false");
333     }
334   }
335 
336   return success;
337 }
338 
339 bool WindowsSMTCProvider::IsKeySupported(
340     mozilla::dom::MediaControlKey aKey) const {
341   return mSupportedKeys & GetMediaKeyMask(aKey);
342 }
343 
344 bool WindowsSMTCProvider::EnableKey(mozilla::dom::MediaControlKey aKey,
345                                     bool aEnable) const {
346   MOZ_ASSERT(mControls);
347   switch (aKey) {
348     case mozilla::dom::MediaControlKey::Play:
349       return SUCCEEDED(mControls->put_IsPlayEnabled(aEnable));
350     case mozilla::dom::MediaControlKey::Pause:
351       return SUCCEEDED(mControls->put_IsPauseEnabled(aEnable));
352     case mozilla::dom::MediaControlKey::Previoustrack:
353       return SUCCEEDED(mControls->put_IsPreviousEnabled(aEnable));
354     case mozilla::dom::MediaControlKey::Nexttrack:
355       return SUCCEEDED(mControls->put_IsNextEnabled(aEnable));
356     case mozilla::dom::MediaControlKey::Stop:
357       return SUCCEEDED(mControls->put_IsStopEnabled(aEnable));
358     default:
359       LOG("No button for %s", ToMediaControlKeyStr(aKey));
360       return false;
361   }
362 }
363 
364 bool WindowsSMTCProvider::InitDisplayAndControls() {
365   // As Open() might be called multiple times, "cache" the results of the COM
366   // API
367   if (mControls && mDisplay) {
368     return true;
369   }
370   ComPtr<ISystemMediaTransportControlsInterop> interop;
371   HRESULT hr = GetActivationFactory(
372       HStringReference(RuntimeClass_Windows_Media_SystemMediaTransportControls)
373           .Get(),
374       interop.GetAddressOf());
375   if (FAILED(hr)) {
376     LOG("SystemMediaTransportControls: Failed at instantiating the "
377         "Interop object");
378     return false;
379   }
380   MOZ_ASSERT(interop);
381 
382   if (!mControls && FAILED(interop->GetForWindow(
383                         mWindow, IID_PPV_ARGS(mControls.GetAddressOf())))) {
384     LOG("SystemMediaTransportControls: Failed at GetForWindow()");
385     return false;
386   }
387   MOZ_ASSERT(mControls);
388 
389   if (!mDisplay &&
390       FAILED(mControls->get_DisplayUpdater(mDisplay.GetAddressOf()))) {
391     LOG("SystemMediaTransportControls: Failed at get_DisplayUpdater()");
392   }
393 
394   MOZ_ASSERT(mDisplay);
395   return true;
396 }
397 
398 bool WindowsSMTCProvider::SetMusicMetadata(const wchar_t* aArtist,
399                                            const wchar_t* aTitle,
400                                            const wchar_t* aAlbumArtist) {
401   MOZ_ASSERT(mDisplay);
402   MOZ_ASSERT(aArtist);
403   MOZ_ASSERT(aTitle);
404   MOZ_ASSERT(aAlbumArtist);
405   ComPtr<IMusicDisplayProperties> musicProps;
406 
407   HRESULT hr = mDisplay->put_Type(MediaPlaybackType::MediaPlaybackType_Music);
408   MOZ_ASSERT(SUCCEEDED(hr));
409   Unused << hr;
410   hr = mDisplay->get_MusicProperties(musicProps.GetAddressOf());
411   if (FAILED(hr)) {
412     LOG("Failed to get music properties");
413     return false;
414   }
415 
416   hr = musicProps->put_Artist(HStringReference(aArtist).Get());
417   if (FAILED(hr)) {
418     LOG("Failed to set the music's artist");
419     return false;
420   }
421 
422   hr = musicProps->put_Title(HStringReference(aTitle).Get());
423   if (FAILED(hr)) {
424     LOG("Failed to set the music's title");
425     return false;
426   }
427 
428   hr = musicProps->put_AlbumArtist(HStringReference(aAlbumArtist).Get());
429   if (FAILED(hr)) {
430     LOG("Failed to set the music's album");
431     return false;
432   }
433 
434   hr = mDisplay->Update();
435   if (FAILED(hr)) {
436     LOG("Failed to refresh the display");
437     return false;
438   }
439 
440   return true;
441 }
442 
443 void WindowsSMTCProvider::LoadThumbnail(
444     const nsTArray<mozilla::dom::MediaImage>& aArtwork) {
445   MOZ_ASSERT(NS_IsMainThread());
446 
447   // TODO: Sort the images by the preferred size or format.
448   mArtwork = aArtwork;
449   mNextImageIndex = 0;
450 
451   // Abort the loading if
452   // 1) thumbnail is being updated, and one in processing is in the artwork
453   // 2) thumbnail is not being updated, and one in use is in the artwork
454   if (!mProcessingUrl.IsEmpty()) {
455     LOG("Load thumbnail while image: %s is being processed",
456         NS_ConvertUTF16toUTF8(mProcessingUrl).get());
457     if (mozilla::dom::IsImageIn(mArtwork, mProcessingUrl)) {
458       LOG("No need to load thumbnail. The one being processed is in the "
459           "artwork");
460       return;
461     }
462   } else if (!mThumbnailUrl.IsEmpty()) {
463     if (mozilla::dom::IsImageIn(mArtwork, mThumbnailUrl)) {
464       LOG("No need to load thumbnail. The one in use is in the artwork");
465       return;
466     }
467   }
468 
469   // If there is a pending image store operation, that image must be different
470   // from the new image will be loaded below, so the pending one should be
471   // cancelled.
472   CancelPendingStoreAsyncOperation();
473   // Remove the current thumbnail on the interface
474   ClearThumbnail();
475   // Then load the new thumbnail asynchronously
476   LoadImageAtIndex(mNextImageIndex++);
477 }
478 
479 void WindowsSMTCProvider::LoadImageAtIndex(const size_t aIndex) {
480   MOZ_ASSERT(NS_IsMainThread());
481 
482   if (aIndex >= mArtwork.Length()) {
483     LOG("Stop loading thumbnail. No more available images");
484     mImageFetchRequest.DisconnectIfExists();
485     mProcessingUrl.Truncate();
486     return;
487   }
488 
489   const mozilla::dom::MediaImage& image = mArtwork[aIndex];
490 
491   // TODO: No need to fetch the default image and do image processing since the
492   // the default image is local file and it's trustworthy. For the default
493   // image, we can use `CreateFromFile` to create the IRandomAccessStream. We
494   // should probably cache it since it could be used very often (Bug 1643102)
495 
496   if (!mozilla::dom::IsValidImageUrl(image.mSrc)) {
497     LOG("Skip the image with invalid URL. Try next image");
498     mImageFetchRequest.DisconnectIfExists();
499     LoadImageAtIndex(mNextImageIndex++);
500     return;
501   }
502 
503   mImageFetchRequest.DisconnectIfExists();
504   mProcessingUrl = image.mSrc;
505 
506   mImageFetcher = mozilla::MakeUnique<mozilla::dom::FetchImageHelper>(image);
507   RefPtr<WindowsSMTCProvider> self = this;
508   mImageFetcher->FetchImage()
509       ->Then(
510           AbstractThread::MainThread(), __func__,
511           [this, self](const nsCOMPtr<imgIContainer>& aImage) {
512             LOG("The image is fetched successfully");
513             mImageFetchRequest.Complete();
514 
515             // Although IMAGE_JPEG or IMAGE_BMP are valid types as well, but a
516             // png image with transparent background will be converted into a
517             // jpeg/bmp file with a colored background. IMAGE_PNG format seems
518             // to be the best choice for now.
519             uint32_t size = 0;
520             char* src = nullptr;
521             // Only used to hold the image data
522             nsCOMPtr<nsIInputStream> inputStream;
523             nsresult rv = mozilla::dom::GetEncodedImageBuffer(
524                 aImage, nsLiteralCString(IMAGE_PNG),
525                 getter_AddRefs(inputStream), &size, &src);
526             if (NS_FAILED(rv) || !inputStream || size == 0 || !src) {
527               LOG("Failed to get the image buffer info. Try next image");
528               LoadImageAtIndex(mNextImageIndex++);
529               return;
530             }
531 
532             LoadImage(src, size);
533           },
534           [this, self](bool) {
535             LOG("Failed to fetch image. Try next image");
536             mImageFetchRequest.Complete();
537             LoadImageAtIndex(mNextImageIndex++);
538           })
539       ->Track(mImageFetchRequest);
540 }
541 
542 void WindowsSMTCProvider::LoadImage(const char* aImageData,
543                                     uint32_t aDataSize) {
544   MOZ_ASSERT(NS_IsMainThread());
545 
546   // 1. Use mImageDataWriter to write the binary data of image into mImageStream
547   // 2. Refer the image by mImageStreamReference and then set it to the SMTC
548   // In case of the race condition between they are being destroyed and the
549   // async operation for image loading, mImageDataWriter, mImageStream, and
550   // mImageStreamReference are member variables
551 
552   HRESULT hr = ActivateInstance(
553       HStringReference(
554           RuntimeClass_Windows_Storage_Streams_InMemoryRandomAccessStream)
555           .Get(),
556       mImageStream.GetAddressOf());
557   if (FAILED(hr)) {
558     LOG("Failed to make mImageStream refer to an instance of "
559         "InMemoryRandomAccessStream");
560     return;
561   }
562 
563   ComPtr<IOutputStream> outputStream;
564   hr = mImageStream.As(&outputStream);
565   if (FAILED(hr)) {
566     LOG("Failed when query IOutputStream interface from mImageStream");
567     return;
568   }
569 
570   ComPtr<IDataWriterFactory> dataWriterFactory;
571   hr = GetActivationFactory(
572       HStringReference(RuntimeClass_Windows_Storage_Streams_DataWriter).Get(),
573       dataWriterFactory.GetAddressOf());
574   if (FAILED(hr)) {
575     LOG("Failed to get an activation factory for IDataWriterFactory");
576     return;
577   }
578 
579   hr = dataWriterFactory->CreateDataWriter(outputStream.Get(),
580                                            mImageDataWriter.GetAddressOf());
581   if (FAILED(hr)) {
582     LOG("Failed to create mImageDataWriter that writes data to mImageStream");
583     return;
584   }
585 
586   hr = mImageDataWriter->WriteBytes(
587       aDataSize, reinterpret_cast<BYTE*>(const_cast<char*>(aImageData)));
588   if (FAILED(hr)) {
589     LOG("Failed to write data to mImageStream");
590     return;
591   }
592 
593   hr = mImageDataWriter->StoreAsync(&mStoreAsyncOperation);
594   if (FAILED(hr)) {
595     LOG("Failed to create a DataWriterStoreOperation for mStoreAsyncOperation");
596     return;
597   }
598 
599   // Upon the image is stored in mImageStream, set the image to the SMTC
600   // interface
601   auto onStoreCompleted = Callback<
602       IAsyncOperationCompletedHandler<unsigned int>>(
603       [this, self = RefPtr<WindowsSMTCProvider>(this),
604        aImageUrl = nsString(mProcessingUrl)](
605           IAsyncOperation<unsigned int>* aAsyncOp, AsyncStatus aStatus) {
606         MOZ_ASSERT(NS_IsMainThread());
607 
608         if (aStatus != AsyncStatus::Completed) {
609           LOG("Asynchronous operation is not completed");
610           return E_ABORT;
611         }
612 
613         HRESULT hr = S_OK;
614         IAsyncInfo* asyncInfo = GetIAsyncInfo(aAsyncOp);
615         asyncInfo->get_ErrorCode(&hr);
616         if (FAILED(hr)) {
617           LOG("Failed to get termination status of the asynchronous operation");
618           return hr;
619         }
620 
621         if (!UpdateThumbnail(aImageUrl)) {
622           LOG("Failed to update thumbnail");
623         }
624 
625         // If an error occurs above:
626         // - If aImageUrl is not mProcessingUrl. It's fine.
627         // - If aImageUrl is mProcessingUrl, then mProcessingUrl won't be reset.
628         //   Therefore the thumbnail will remain empty until a new image whose
629         //   url is different from mProcessingUrl is loaded.
630 
631         return S_OK;
632       });
633 
634   hr = mStoreAsyncOperation->put_Completed(onStoreCompleted.Get());
635   if (FAILED(hr)) {
636     LOG("Failed to set callback on completeing the asynchronous operation");
637   }
638 }
639 
640 bool WindowsSMTCProvider::SetThumbnail(const nsAString& aUrl) {
641   MOZ_ASSERT(mDisplay);
642   MOZ_ASSERT(mImageStream);
643   MOZ_ASSERT(!aUrl.IsEmpty());
644 
645   ComPtr<IRandomAccessStreamReferenceStatics> streamRefFactory;
646 
647   HRESULT hr = GetActivationFactory(
648       HStringReference(
649           RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference)
650           .Get(),
651       streamRefFactory.GetAddressOf());
652   auto cleanup =
653       MakeScopeExit([this, self = RefPtr<WindowsSMTCProvider>(this)] {
654         LOG("Clean mThumbnailUrl");
655         mThumbnailUrl.Truncate();
656       });
657 
658   if (FAILED(hr)) {
659     LOG("Failed to get an activation factory for "
660         "IRandomAccessStreamReferenceStatics type");
661     return false;
662   }
663 
664   hr = streamRefFactory->CreateFromStream(mImageStream.Get(),
665                                           mImageStreamReference.GetAddressOf());
666   if (FAILED(hr)) {
667     LOG("Failed to create mImageStreamReference from mImageStream");
668     return false;
669   }
670 
671   hr = mDisplay->put_Thumbnail(mImageStreamReference.Get());
672   if (FAILED(hr)) {
673     LOG("Failed to update thumbnail");
674     return false;
675   }
676 
677   hr = mDisplay->Update();
678   if (FAILED(hr)) {
679     LOG("Failed to refresh display");
680     return false;
681   }
682 
683   // No need to clean mThumbnailUrl since thumbnail is set successfully
684   cleanup.release();
685   mThumbnailUrl = aUrl;
686 
687   return true;
688 }
689 
690 void WindowsSMTCProvider::ClearThumbnail() {
691   MOZ_ASSERT(mDisplay);
692   HRESULT hr = mDisplay->put_Thumbnail(nullptr);
693   MOZ_ASSERT(SUCCEEDED(hr));
694   hr = mDisplay->Update();
695   MOZ_ASSERT(SUCCEEDED(hr));
696   Unused << hr;
697   mThumbnailUrl.Truncate();
698 }
699 
700 bool WindowsSMTCProvider::UpdateThumbnail(const nsAString& aUrl) {
701   MOZ_ASSERT(NS_IsMainThread());
702 
703   if (!IsOpened()) {
704     LOG("Abort the thumbnail update: SMTC is closed");
705     return false;
706   }
707 
708   if (aUrl != mProcessingUrl) {
709     LOG("Abort the thumbnail update: The image from %s is out of date",
710         NS_ConvertUTF16toUTF8(aUrl).get());
711     return false;
712   }
713 
714   mProcessingUrl.Truncate();
715 
716   if (!SetThumbnail(aUrl)) {
717     LOG("Failed to update thumbnail");
718     return false;
719   }
720 
721   MOZ_ASSERT(mThumbnailUrl == aUrl);
722   LOG("The thumbnail is updated to the image from: %s",
723       NS_ConvertUTF16toUTF8(mThumbnailUrl).get());
724   return true;
725 }
726 
727 void WindowsSMTCProvider::CancelPendingStoreAsyncOperation() const {
728   if (mStoreAsyncOperation) {
729     IAsyncInfo* asyncInfo = GetIAsyncInfo(mStoreAsyncOperation.Get());
730     asyncInfo->Cancel();
731   }
732 }
733 
734 #endif  // __MINGW32__
735