diff options
Diffstat (limited to 'widget/windows/WindowsSMTCProvider.cpp')
-rw-r--r-- | widget/windows/WindowsSMTCProvider.cpp | 723 |
1 files changed, 723 insertions, 0 deletions
diff --git a/widget/windows/WindowsSMTCProvider.cpp b/widget/windows/WindowsSMTCProvider.cpp new file mode 100644 index 0000000000..91eea77918 --- /dev/null +++ b/widget/windows/WindowsSMTCProvider.cpp @@ -0,0 +1,723 @@ +/* -*- 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/. */ + +/* mingw currently doesn't support windows.media.h, so we disable + * the whole related class until this is fixed. + * @TODO: Maybe contact MinGW Team for inclusion?*/ +#ifndef __MINGW32__ + +# include "WindowsSMTCProvider.h" + +# include <windows.h> +# include <windows.media.h> +# include <winsdkver.h> +# include <wrl.h> + +# include "nsMimeTypes.h" +# include "mozilla/Assertions.h" +# include "mozilla/Logging.h" +# include "mozilla/Maybe.h" +# include "mozilla/WidgetUtils.h" +# include "mozilla/WindowsVersion.h" +# include "mozilla/ScopeExit.h" +# include "mozilla/dom/MediaControlUtils.h" +# include "mozilla/media/MediaUtils.h" +# include "nsThreadUtils.h" + +# pragma comment(lib, "runtimeobject.lib") + +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Media; +using namespace ABI::Windows::Storage::Streams; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace mozilla; + +# ifndef RuntimeClass_Windows_Media_SystemMediaTransportControls +# define RuntimeClass_Windows_Media_SystemMediaTransportControls \ + L"Windows.Media.SystemMediaTransportControls" +# endif + +# ifndef RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference +# define RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference \ + L"Windows.Storage.Streams.RandomAccessStreamReference" +# endif + +# ifndef ISystemMediaTransportControlsInterop +EXTERN_C const IID IID_ISystemMediaTransportControlsInterop; +MIDL_INTERFACE("ddb0472d-c911-4a1f-86d9-dc3d71a95f5a") +ISystemMediaTransportControlsInterop : public IInspectable { + public: + virtual HRESULT STDMETHODCALLTYPE GetForWindow( + /* [in] */ __RPC__in HWND appWindow, + /* [in] */ __RPC__in REFIID riid, + /* [iid_is][retval][out] */ + __RPC__deref_out_opt void** mediaTransportControl) = 0; +}; +# endif /* __ISystemMediaTransportControlsInterop_INTERFACE_DEFINED__ */ + +extern mozilla::LazyLogModule gMediaControlLog; + +# undef LOG +# define LOG(msg, ...) \ + MOZ_LOG(gMediaControlLog, LogLevel::Debug, \ + ("WindowSMTCProvider=%p, " msg, this, ##__VA_ARGS__)) + +static inline Maybe<mozilla::dom::MediaControlKey> TranslateKeycode( + SystemMediaTransportControlsButton keycode) { + switch (keycode) { + case SystemMediaTransportControlsButton_Play: + return Some(mozilla::dom::MediaControlKey::Play); + case SystemMediaTransportControlsButton_Pause: + return Some(mozilla::dom::MediaControlKey::Pause); + case SystemMediaTransportControlsButton_Next: + return Some(mozilla::dom::MediaControlKey::Nexttrack); + case SystemMediaTransportControlsButton_Previous: + return Some(mozilla::dom::MediaControlKey::Previoustrack); + case SystemMediaTransportControlsButton_Stop: + return Some(mozilla::dom::MediaControlKey::Stop); + case SystemMediaTransportControlsButton_FastForward: + return Some(mozilla::dom::MediaControlKey::Seekforward); + case SystemMediaTransportControlsButton_Rewind: + return Some(mozilla::dom::MediaControlKey::Seekbackward); + default: + return Nothing(); // Not supported Button + } +} + +static IAsyncInfo* GetIAsyncInfo(IAsyncOperation<unsigned int>* aAsyncOp) { + MOZ_ASSERT(aAsyncOp); + IAsyncInfo* asyncInfo; + HRESULT hr = aAsyncOp->QueryInterface(IID_IAsyncInfo, + reinterpret_cast<void**>(&asyncInfo)); + // The assertion always works since IAsyncOperation implements IAsyncInfo + MOZ_ASSERT(SUCCEEDED(hr)); + Unused << hr; + MOZ_ASSERT(asyncInfo); + return asyncInfo; +} + +WindowsSMTCProvider::WindowsSMTCProvider() { + LOG("Creating an empty and invisible window"); + + // In order to create a SMTC-Provider, we need a hWnd, which shall be created + // dynamically from an invisible window. This leads to the following + // boilerplate code. + WNDCLASS wnd{}; + wnd.lpszClassName = L"Firefox-MediaKeys"; + wnd.hInstance = nullptr; + wnd.lpfnWndProc = DefWindowProc; + GetLastError(); // Clear the error + RegisterClass(&wnd); + MOZ_ASSERT(!GetLastError()); + + mWindow = CreateWindowExW(0, L"Firefox-MediaKeys", L"Firefox Media Keys", 0, + CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, nullptr, + nullptr, nullptr, nullptr); + MOZ_ASSERT(mWindow); + MOZ_ASSERT(!GetLastError()); +} + +WindowsSMTCProvider::~WindowsSMTCProvider() { + // Dispose the window + MOZ_ASSERT(mWindow); + if (!DestroyWindow(mWindow)) { + LOG("Failed to destroy the hidden window. Error Code: %lu", GetLastError()); + } + if (!UnregisterClass(L"Firefox-MediaKeys", nullptr)) { + // Note that this is logged when the class wasn't even registered. + LOG("Failed to unregister the class. Error Code: %lu", GetLastError()); + } +} + +bool WindowsSMTCProvider::IsOpened() const { return mInitialized; } + +bool WindowsSMTCProvider::Open() { + LOG("Opening Source"); + MOZ_ASSERT(!mInitialized); + + if (!IsWin8Point1OrLater()) { + LOG("Windows 8.1 or later is required for Media Key Support"); + return false; + } + + if (!InitDisplayAndControls()) { + LOG("Failed to initialize the SMTC and its display"); + return false; + } + + if (!UpdateButtons()) { + LOG("Failed to initialize the buttons"); + return false; + } + + if (!RegisterEvents()) { + LOG("Failed to register SMTC key-event listener"); + return false; + } + + if (!EnableControl(true)) { + LOG("Failed to enable SMTC control"); + return false; + } + + mInitialized = true; + SetPlaybackState(mozilla::dom::MediaSessionPlaybackState::None); + return mInitialized; +} + +void WindowsSMTCProvider::Close() { + MediaControlKeySource::Close(); + // Prevent calling Set methods when init failed + if (mInitialized) { + SetPlaybackState(mozilla::dom::MediaSessionPlaybackState::None); + UnregisterEvents(); + ClearMetadata(); + // We have observed an Windows issue, if we modify `mControls` , (such as + // setting metadata, disable buttons) before disabling control, and those + // operations are not done sequentially within a same main thread task, + // then it would cause a problem where the SMTC wasn't clean up completely + // and show the executable name. + EnableControl(false); + mInitialized = false; + } +} + +void WindowsSMTCProvider::SetPlaybackState( + mozilla::dom::MediaSessionPlaybackState aState) { + MOZ_ASSERT(mInitialized); + MediaControlKeySource::SetPlaybackState(aState); + + HRESULT hr; + + // Note: we can't return the status of put_PlaybackStatus, but we can at least + // assert it. + switch (aState) { + case mozilla::dom::MediaSessionPlaybackState::Paused: + hr = mControls->put_PlaybackStatus( + ABI::Windows::Media::MediaPlaybackStatus_Paused); + break; + case mozilla::dom::MediaSessionPlaybackState::Playing: + hr = mControls->put_PlaybackStatus( + ABI::Windows::Media::MediaPlaybackStatus_Playing); + break; + case mozilla::dom::MediaSessionPlaybackState::None: + hr = mControls->put_PlaybackStatus( + ABI::Windows::Media::MediaPlaybackStatus_Stopped); + break; + // MediaPlaybackStatus still supports Closed and Changing, which we don't + // use (yet) + default: + MOZ_ASSERT_UNREACHABLE( + "Enum Inconsitency between PlaybackState and WindowsSMTCProvider"); + break; + } + + MOZ_ASSERT(SUCCEEDED(hr)); + Unused << hr; +} + +void WindowsSMTCProvider::SetMediaMetadata( + const mozilla::dom::MediaMetadataBase& aMetadata) { + MOZ_ASSERT(mInitialized); + SetMusicMetadata(aMetadata.mArtist, aMetadata.mTitle); + LoadThumbnail(aMetadata.mArtwork); +} + +void WindowsSMTCProvider::ClearMetadata() { + MOZ_ASSERT(mDisplay); + if (FAILED(mDisplay->ClearAll())) { + LOG("Failed to clear SMTC display"); + } + mImageFetchRequest.DisconnectIfExists(); + CancelPendingStoreAsyncOperation(); + mThumbnailUrl.Truncate(); + mProcessingUrl.Truncate(); + mNextImageIndex = 0; + mSupportedKeys = 0; +} + +void WindowsSMTCProvider::SetSupportedMediaKeys( + const MediaKeysArray& aSupportedKeys) { + MOZ_ASSERT(mInitialized); + + uint32_t supportedKeys = 0; + for (const mozilla::dom::MediaControlKey& key : aSupportedKeys) { + supportedKeys |= GetMediaKeyMask(key); + } + + if (supportedKeys == mSupportedKeys) { + LOG("Supported keys stay the same"); + return; + } + + LOG("Update supported keys"); + mSupportedKeys = supportedKeys; + UpdateButtons(); +} + +void WindowsSMTCProvider::UnregisterEvents() { + if (mControls && mButtonPressedToken.value != 0) { + mControls->remove_ButtonPressed(mButtonPressedToken); + } +} + +bool WindowsSMTCProvider::RegisterEvents() { + MOZ_ASSERT(mControls); + auto self = RefPtr<WindowsSMTCProvider>(this); + auto callbackbtnPressed = Callback< + ITypedEventHandler<SystemMediaTransportControls*, + SystemMediaTransportControlsButtonPressedEventArgs*>>( + [this, self](ISystemMediaTransportControls*, + ISystemMediaTransportControlsButtonPressedEventArgs* pArgs) + -> HRESULT { + MOZ_ASSERT(pArgs); + SystemMediaTransportControlsButton btn; + + if (FAILED(pArgs->get_Button(&btn))) { + LOG("SystemMediaTransportControls: ButtonPressedEvent - Could " + "not get Button."); + return S_OK; // Propagating the error probably wouldn't help. + } + + Maybe<mozilla::dom::MediaControlKey> keyCode = TranslateKeycode(btn); + if (keyCode.isSome() && IsOpened()) { + OnButtonPressed(keyCode.value()); + } + return S_OK; + }); + + if (FAILED(mControls->add_ButtonPressed(callbackbtnPressed.Get(), + &mButtonPressedToken))) { + LOG("SystemMediaTransportControls: Failed at " + "registerEvents().add_ButtonPressed()"); + return false; + } + + return true; +} + +void WindowsSMTCProvider::OnButtonPressed( + mozilla::dom::MediaControlKey aKey) const { + if (!IsKeySupported(aKey)) { + LOG("key: %s is not supported", ToMediaControlKeyStr(aKey)); + return; + } + + for (auto& listener : mListeners) { + listener->OnActionPerformed(mozilla::dom::MediaControlAction(aKey)); + } +} + +bool WindowsSMTCProvider::EnableControl(bool aEnabled) const { + MOZ_ASSERT(mControls); + return SUCCEEDED(mControls->put_IsEnabled(aEnabled)); +} + +bool WindowsSMTCProvider::UpdateButtons() const { + static const mozilla::dom::MediaControlKey kKeys[] = { + mozilla::dom::MediaControlKey::Play, mozilla::dom::MediaControlKey::Pause, + mozilla::dom::MediaControlKey::Previoustrack, + mozilla::dom::MediaControlKey::Nexttrack, + mozilla::dom::MediaControlKey::Stop}; + + bool success = true; + for (const mozilla::dom::MediaControlKey& key : kKeys) { + if (!EnableKey(key, IsKeySupported(key))) { + success = false; + LOG("Failed to set %s=%s", ToMediaControlKeyStr(key), + IsKeySupported(key) ? "true" : "false"); + } + } + + return success; +} + +bool WindowsSMTCProvider::IsKeySupported( + mozilla::dom::MediaControlKey aKey) const { + return mSupportedKeys & GetMediaKeyMask(aKey); +} + +bool WindowsSMTCProvider::EnableKey(mozilla::dom::MediaControlKey aKey, + bool aEnable) const { + MOZ_ASSERT(mControls); + switch (aKey) { + case mozilla::dom::MediaControlKey::Play: + return SUCCEEDED(mControls->put_IsPlayEnabled(aEnable)); + case mozilla::dom::MediaControlKey::Pause: + return SUCCEEDED(mControls->put_IsPauseEnabled(aEnable)); + case mozilla::dom::MediaControlKey::Previoustrack: + return SUCCEEDED(mControls->put_IsPreviousEnabled(aEnable)); + case mozilla::dom::MediaControlKey::Nexttrack: + return SUCCEEDED(mControls->put_IsNextEnabled(aEnable)); + case mozilla::dom::MediaControlKey::Stop: + return SUCCEEDED(mControls->put_IsStopEnabled(aEnable)); + default: + LOG("No button for %s", ToMediaControlKeyStr(aKey)); + return false; + } +} + +bool WindowsSMTCProvider::InitDisplayAndControls() { + // As Open() might be called multiple times, "cache" the results of the COM + // API + if (mControls && mDisplay) { + return true; + } + ComPtr<ISystemMediaTransportControlsInterop> interop; + HRESULT hr = GetActivationFactory( + HStringReference(RuntimeClass_Windows_Media_SystemMediaTransportControls) + .Get(), + interop.GetAddressOf()); + if (FAILED(hr)) { + LOG("SystemMediaTransportControls: Failed at instantiating the " + "Interop object"); + return false; + } + MOZ_ASSERT(interop); + + if (!mControls && FAILED(interop->GetForWindow( + mWindow, IID_PPV_ARGS(mControls.GetAddressOf())))) { + LOG("SystemMediaTransportControls: Failed at GetForWindow()"); + return false; + } + MOZ_ASSERT(mControls); + + if (!mDisplay && + FAILED(mControls->get_DisplayUpdater(mDisplay.GetAddressOf()))) { + LOG("SystemMediaTransportControls: Failed at get_DisplayUpdater()"); + } + + MOZ_ASSERT(mDisplay); + return true; +} + +bool WindowsSMTCProvider::SetMusicMetadata(const nsString& aArtist, + const nsString& aTitle) { + MOZ_ASSERT(mDisplay); + ComPtr<IMusicDisplayProperties> musicProps; + + HRESULT hr = mDisplay->put_Type(MediaPlaybackType::MediaPlaybackType_Music); + MOZ_ASSERT(SUCCEEDED(hr)); + Unused << hr; + hr = mDisplay->get_MusicProperties(musicProps.GetAddressOf()); + if (FAILED(hr)) { + LOG("Failed to get music properties"); + return false; + } + + hr = musicProps->put_Artist(HStringReference(aArtist.get()).Get()); + if (FAILED(hr)) { + LOG("Failed to set the music's artist"); + return false; + } + + hr = musicProps->put_Title(HStringReference(aTitle.get()).Get()); + if (FAILED(hr)) { + LOG("Failed to set the music's title"); + return false; + } + + hr = mDisplay->Update(); + if (FAILED(hr)) { + LOG("Failed to refresh the display"); + return false; + } + + return true; +} + +void WindowsSMTCProvider::LoadThumbnail( + const nsTArray<mozilla::dom::MediaImage>& aArtwork) { + MOZ_ASSERT(NS_IsMainThread()); + + // TODO: Sort the images by the preferred size or format. + mArtwork = aArtwork; + mNextImageIndex = 0; + + // Abort the loading if + // 1) thumbnail is being updated, and one in processing is in the artwork + // 2) thumbnail is not being updated, and one in use is in the artwork + if (!mProcessingUrl.IsEmpty()) { + LOG("Load thumbnail while image: %s is being processed", + NS_ConvertUTF16toUTF8(mProcessingUrl).get()); + if (mozilla::dom::IsImageIn(mArtwork, mProcessingUrl)) { + LOG("No need to load thumbnail. The one being processed is in the " + "artwork"); + return; + } + } else if (!mThumbnailUrl.IsEmpty()) { + if (mozilla::dom::IsImageIn(mArtwork, mThumbnailUrl)) { + LOG("No need to load thumbnail. The one in use is in the artwork"); + return; + } + } + + // If there is a pending image store operation, that image must be different + // from the new image will be loaded below, so the pending one should be + // cancelled. + CancelPendingStoreAsyncOperation(); + // Remove the current thumbnail on the interface + ClearThumbnail(); + // Then load the new thumbnail asynchronously + LoadImageAtIndex(mNextImageIndex++); +} + +void WindowsSMTCProvider::LoadImageAtIndex(const size_t aIndex) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aIndex >= mArtwork.Length()) { + LOG("Stop loading thumbnail. No more available images"); + mImageFetchRequest.DisconnectIfExists(); + mProcessingUrl.Truncate(); + return; + } + + const mozilla::dom::MediaImage& image = mArtwork[aIndex]; + + // TODO: No need to fetch the default image and do image processing since the + // the default image is local file and it's trustworthy. For the default + // image, we can use `CreateFromFile` to create the IRandomAccessStream. We + // should probably cache it since it could be used very often (Bug 1643102) + + if (!mozilla::dom::IsValidImageUrl(image.mSrc)) { + LOG("Skip the image with invalid URL. Try next image"); + mImageFetchRequest.DisconnectIfExists(); + LoadImageAtIndex(mNextImageIndex++); + return; + } + + mImageFetchRequest.DisconnectIfExists(); + mProcessingUrl = image.mSrc; + + mImageFetcher = mozilla::MakeUnique<mozilla::dom::FetchImageHelper>(image); + RefPtr<WindowsSMTCProvider> self = this; + mImageFetcher->FetchImage() + ->Then( + AbstractThread::MainThread(), __func__, + [this, self](const nsCOMPtr<imgIContainer>& aImage) { + LOG("The image is fetched successfully"); + mImageFetchRequest.Complete(); + + // Although IMAGE_JPEG or IMAGE_BMP are valid types as well, but a + // png image with transparent background will be converted into a + // jpeg/bmp file with a colored background. IMAGE_PNG format seems + // to be the best choice for now. + uint32_t size = 0; + char* src = nullptr; + // Only used to hold the image data + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = mozilla::dom::GetEncodedImageBuffer( + aImage, nsLiteralCString(IMAGE_PNG), + getter_AddRefs(inputStream), &size, &src); + if (NS_FAILED(rv) || !inputStream || size == 0 || !src) { + LOG("Failed to get the image buffer info. Try next image"); + LoadImageAtIndex(mNextImageIndex++); + return; + } + + LoadImage(src, size); + }, + [this, self](bool) { + LOG("Failed to fetch image. Try next image"); + mImageFetchRequest.Complete(); + LoadImageAtIndex(mNextImageIndex++); + }) + ->Track(mImageFetchRequest); +} + +void WindowsSMTCProvider::LoadImage(const char* aImageData, + uint32_t aDataSize) { + MOZ_ASSERT(NS_IsMainThread()); + + // 1. Use mImageDataWriter to write the binary data of image into mImageStream + // 2. Refer the image by mImageStreamReference and then set it to the SMTC + // In case of the race condition between they are being destroyed and the + // async operation for image loading, mImageDataWriter, mImageStream, and + // mImageStreamReference are member variables + + HRESULT hr = ActivateInstance( + HStringReference( + RuntimeClass_Windows_Storage_Streams_InMemoryRandomAccessStream) + .Get(), + mImageStream.GetAddressOf()); + if (FAILED(hr)) { + LOG("Failed to make mImageStream refer to an instance of " + "InMemoryRandomAccessStream"); + return; + } + + ComPtr<IOutputStream> outputStream; + hr = mImageStream.As(&outputStream); + if (FAILED(hr)) { + LOG("Failed when query IOutputStream interface from mImageStream"); + return; + } + + ComPtr<IDataWriterFactory> dataWriterFactory; + hr = GetActivationFactory( + HStringReference(RuntimeClass_Windows_Storage_Streams_DataWriter).Get(), + dataWriterFactory.GetAddressOf()); + if (FAILED(hr)) { + LOG("Failed to get an activation factory for IDataWriterFactory"); + return; + } + + hr = dataWriterFactory->CreateDataWriter(outputStream.Get(), + mImageDataWriter.GetAddressOf()); + if (FAILED(hr)) { + LOG("Failed to create mImageDataWriter that writes data to mImageStream"); + return; + } + + hr = mImageDataWriter->WriteBytes( + aDataSize, reinterpret_cast<BYTE*>(const_cast<char*>(aImageData))); + if (FAILED(hr)) { + LOG("Failed to write data to mImageStream"); + return; + } + + hr = mImageDataWriter->StoreAsync(&mStoreAsyncOperation); + if (FAILED(hr)) { + LOG("Failed to create a DataWriterStoreOperation for mStoreAsyncOperation"); + return; + } + + // Upon the image is stored in mImageStream, set the image to the SMTC + // interface + auto onStoreCompleted = Callback< + IAsyncOperationCompletedHandler<unsigned int>>( + [this, self = RefPtr<WindowsSMTCProvider>(this), + aImageUrl = nsString(mProcessingUrl)]( + IAsyncOperation<unsigned int>* aAsyncOp, AsyncStatus aStatus) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aStatus != AsyncStatus::Completed) { + LOG("Asynchronous operation is not completed"); + return E_ABORT; + } + + HRESULT hr = S_OK; + IAsyncInfo* asyncInfo = GetIAsyncInfo(aAsyncOp); + asyncInfo->get_ErrorCode(&hr); + if (FAILED(hr)) { + LOG("Failed to get termination status of the asynchronous operation"); + return hr; + } + + if (!UpdateThumbnail(aImageUrl)) { + LOG("Failed to update thumbnail"); + } + + // If an error occurs above: + // - If aImageUrl is not mProcessingUrl. It's fine. + // - If aImageUrl is mProcessingUrl, then mProcessingUrl won't be reset. + // Therefore the thumbnail will remain empty until a new image whose + // url is different from mProcessingUrl is loaded. + + return S_OK; + }); + + hr = mStoreAsyncOperation->put_Completed(onStoreCompleted.Get()); + if (FAILED(hr)) { + LOG("Failed to set callback on completeing the asynchronous operation"); + } +} + +bool WindowsSMTCProvider::SetThumbnail(const nsAString& aUrl) { + MOZ_ASSERT(mDisplay); + MOZ_ASSERT(mImageStream); + MOZ_ASSERT(!aUrl.IsEmpty()); + + ComPtr<IRandomAccessStreamReferenceStatics> streamRefFactory; + + HRESULT hr = GetActivationFactory( + HStringReference( + RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference) + .Get(), + streamRefFactory.GetAddressOf()); + auto cleanup = + MakeScopeExit([this, self = RefPtr<WindowsSMTCProvider>(this)] { + LOG("Clean mThumbnailUrl"); + mThumbnailUrl.Truncate(); + }); + + if (FAILED(hr)) { + LOG("Failed to get an activation factory for " + "IRandomAccessStreamReferenceStatics type"); + return false; + } + + hr = streamRefFactory->CreateFromStream(mImageStream.Get(), + mImageStreamReference.GetAddressOf()); + if (FAILED(hr)) { + LOG("Failed to create mImageStreamReference from mImageStream"); + return false; + } + + hr = mDisplay->put_Thumbnail(mImageStreamReference.Get()); + if (FAILED(hr)) { + LOG("Failed to update thumbnail"); + return false; + } + + hr = mDisplay->Update(); + if (FAILED(hr)) { + LOG("Failed to refresh display"); + return false; + } + + // No need to clean mThumbnailUrl since thumbnail is set successfully + cleanup.release(); + mThumbnailUrl = aUrl; + + return true; +} + +void WindowsSMTCProvider::ClearThumbnail() { + MOZ_ASSERT(mDisplay); + HRESULT hr = mDisplay->put_Thumbnail(nullptr); + MOZ_ASSERT(SUCCEEDED(hr)); + hr = mDisplay->Update(); + MOZ_ASSERT(SUCCEEDED(hr)); + Unused << hr; + mThumbnailUrl.Truncate(); +} + +bool WindowsSMTCProvider::UpdateThumbnail(const nsAString& aUrl) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsOpened()) { + LOG("Abort the thumbnail update: SMTC is closed"); + return false; + } + + if (aUrl != mProcessingUrl) { + LOG("Abort the thumbnail update: The image from %s is out of date", + NS_ConvertUTF16toUTF8(aUrl).get()); + return false; + } + + mProcessingUrl.Truncate(); + + if (!SetThumbnail(aUrl)) { + LOG("Failed to update thumbnail"); + return false; + } + + MOZ_ASSERT(mThumbnailUrl == aUrl); + LOG("The thumbnail is updated to the image from: %s", + NS_ConvertUTF16toUTF8(mThumbnailUrl).get()); + return true; +} + +void WindowsSMTCProvider::CancelPendingStoreAsyncOperation() const { + if (mStoreAsyncOperation) { + IAsyncInfo* asyncInfo = GetIAsyncInfo(mStoreAsyncOperation.Get()); + asyncInfo->Cancel(); + } +} + +#endif // __MINGW32__ |