summaryrefslogtreecommitdiffstats
path: root/widget/windows/TaskbarPreview.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/windows/TaskbarPreview.cpp')
-rw-r--r--widget/windows/TaskbarPreview.cpp416
1 files changed, 416 insertions, 0 deletions
diff --git a/widget/windows/TaskbarPreview.cpp b/widget/windows/TaskbarPreview.cpp
new file mode 100644
index 0000000000..a5d2aeb540
--- /dev/null
+++ b/widget/windows/TaskbarPreview.cpp
@@ -0,0 +1,416 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- 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/. */
+
+#include "TaskbarPreview.h"
+#include <nsITaskbarPreviewController.h>
+#include <windows.h>
+
+#include <nsError.h>
+#include <nsCOMPtr.h>
+#include <nsIWidget.h>
+#include <nsServiceManagerUtils.h>
+
+#include "nsUXThemeData.h"
+#include "nsWindow.h"
+#include "nsAppShell.h"
+#include "TaskbarPreviewButton.h"
+#include "WinUtils.h"
+
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/Telemetry.h"
+
+// Defined in dwmapi in a header that needs a higher numbered _WINNT #define
+#ifndef DWM_SIT_DISPLAYFRAME
+# define DWM_SIT_DISPLAYFRAME 0x1
+#endif
+
+namespace mozilla {
+namespace widget {
+
+///////////////////////////////////////////////////////////////////////////////
+// TaskbarPreview
+
+TaskbarPreview::TaskbarPreview(ITaskbarList4* aTaskbar,
+ nsITaskbarPreviewController* aController,
+ HWND aHWND, nsIDocShell* aShell)
+ : mTaskbar(aTaskbar),
+ mController(aController),
+ mWnd(aHWND),
+ mVisible(false),
+ mDocShell(do_GetWeakReference(aShell)) {}
+
+TaskbarPreview::~TaskbarPreview() {
+ // Avoid dangling pointer
+ if (sActivePreview == this) sActivePreview = nullptr;
+
+ // Our subclass should have invoked DetachFromNSWindow already.
+ NS_ASSERTION(
+ !mWnd,
+ "TaskbarPreview::DetachFromNSWindow was not called before destruction");
+
+ // Make sure to release before potentially uninitializing COM
+ mTaskbar = nullptr;
+
+ ::CoUninitialize();
+}
+
+nsresult TaskbarPreview::Init() {
+ // TaskbarPreview may outlive the WinTaskbar that created it
+ if (FAILED(::CoInitialize(nullptr))) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ WindowHook* hook = GetWindowHook();
+ if (!hook) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return hook->AddMonitor(WM_DESTROY, MainWindowHook, this);
+}
+
+NS_IMETHODIMP
+TaskbarPreview::SetController(nsITaskbarPreviewController* aController) {
+ NS_ENSURE_ARG(aController);
+
+ mController = aController;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::GetController(nsITaskbarPreviewController** aController) {
+ NS_ADDREF(*aController = mController);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::GetTooltip(nsAString& aTooltip) {
+ aTooltip = mTooltip;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::SetTooltip(const nsAString& aTooltip) {
+ mTooltip = aTooltip;
+ return CanMakeTaskbarCalls() ? UpdateTooltip() : NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::SetVisible(bool visible) {
+ if (mVisible == visible) return NS_OK;
+ mVisible = visible;
+
+ // If the nsWindow has already been destroyed but the caller is still trying
+ // to use it then just pretend that everything succeeded. The caller doesn't
+ // actually have a way to detect this since it's the same case as when we
+ // CanMakeTaskbarCalls returns false.
+ if (!IsWindowAvailable()) return NS_OK;
+
+ return visible ? Enable() : Disable();
+}
+
+NS_IMETHODIMP
+TaskbarPreview::GetVisible(bool* visible) {
+ *visible = mVisible;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::SetActive(bool active) {
+ if (active)
+ sActivePreview = this;
+ else if (sActivePreview == this)
+ sActivePreview = nullptr;
+
+ return CanMakeTaskbarCalls() ? ShowActive(active) : NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::GetActive(bool* active) {
+ *active = sActivePreview == this;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::Invalidate() {
+ if (!mVisible) return NS_OK;
+
+ // DWM Composition is required for previews
+ if (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) return NS_OK;
+
+ HWND previewWindow = PreviewWindow();
+ return FAILED(DwmInvalidateIconicBitmaps(previewWindow)) ? NS_ERROR_FAILURE
+ : NS_OK;
+}
+
+nsresult TaskbarPreview::UpdateTaskbarProperties() {
+ nsresult rv = UpdateTooltip();
+
+ // If we are the active preview and our window is the active window, restore
+ // our active state - otherwise some other non-preview window is now active
+ // and should be displayed as so.
+ if (sActivePreview == this) {
+ if (mWnd == ::GetActiveWindow()) {
+ nsresult rvActive = ShowActive(true);
+ if (NS_FAILED(rvActive)) rv = rvActive;
+ } else {
+ sActivePreview = nullptr;
+ }
+ }
+ return rv;
+}
+
+nsresult TaskbarPreview::Enable() {
+ nsresult rv = NS_OK;
+ if (CanMakeTaskbarCalls()) {
+ rv = UpdateTaskbarProperties();
+ } else if (IsWindowAvailable()) {
+ WindowHook* hook = GetWindowHook();
+ MOZ_ASSERT(hook,
+ "IsWindowAvailable() should have eliminated the null case.");
+ hook->AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
+ MainWindowHook, this);
+ }
+ return rv;
+}
+
+nsresult TaskbarPreview::Disable() {
+ if (!IsWindowAvailable()) {
+ // Window is already destroyed
+ return NS_OK;
+ }
+
+ WindowHook* hook = GetWindowHook();
+ MOZ_ASSERT(hook, "IsWindowAvailable() should have eliminated the null case.");
+ (void)hook->RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
+ MainWindowHook, this);
+
+ return NS_OK;
+}
+
+bool TaskbarPreview::IsWindowAvailable() const {
+ if (mWnd) {
+ nsWindow* win = WinUtils::GetNSWindowPtr(mWnd);
+ if (win && !win->Destroyed()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void TaskbarPreview::DetachFromNSWindow() {
+ if (WindowHook* hook = GetWindowHook()) {
+ hook->RemoveMonitor(WM_DESTROY, MainWindowHook, this);
+ }
+ mWnd = nullptr;
+}
+
+LRESULT
+TaskbarPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) {
+ switch (nMsg) {
+ case WM_DWMSENDICONICTHUMBNAIL: {
+ uint32_t width = HIWORD(lParam);
+ uint32_t height = LOWORD(lParam);
+ float aspectRatio = width / float(height);
+
+ nsresult rv;
+ float preferredAspectRatio;
+ rv = mController->GetThumbnailAspectRatio(&preferredAspectRatio);
+ if (NS_FAILED(rv)) break;
+
+ uint32_t thumbnailWidth = width;
+ uint32_t thumbnailHeight = height;
+
+ if (aspectRatio > preferredAspectRatio) {
+ thumbnailWidth = uint32_t(thumbnailHeight * preferredAspectRatio);
+ } else {
+ thumbnailHeight = uint32_t(thumbnailWidth / preferredAspectRatio);
+ }
+
+ DrawBitmap(thumbnailWidth, thumbnailHeight, false);
+ } break;
+ case WM_DWMSENDICONICLIVEPREVIEWBITMAP: {
+ uint32_t width, height;
+ nsresult rv;
+ rv = mController->GetWidth(&width);
+ if (NS_FAILED(rv)) break;
+ rv = mController->GetHeight(&height);
+ if (NS_FAILED(rv)) break;
+
+ double scale = StaticPrefs::layout_css_devPixelsPerPx();
+ if (scale <= 0.0) {
+ scale = WinUtils::LogToPhysFactor(PreviewWindow());
+ }
+ DrawBitmap(NSToIntRound(scale * width), NSToIntRound(scale * height),
+ true);
+ } break;
+ }
+ return ::DefWindowProcW(PreviewWindow(), nMsg, wParam, lParam);
+}
+
+bool TaskbarPreview::CanMakeTaskbarCalls() {
+ // If the nsWindow has already been destroyed and we know it but our caller
+ // clearly doesn't so we can't make any calls.
+ if (!mWnd) return false;
+ // Certain functions like SetTabOrder seem to require a visible window. During
+ // window close, the window seems to be hidden before being destroyed.
+ if (!::IsWindowVisible(mWnd)) return false;
+ if (mVisible) {
+ nsWindow* window = WinUtils::GetNSWindowPtr(mWnd);
+ NS_ASSERTION(window, "Could not get nsWindow from HWND");
+ return window ? window->HasTaskbarIconBeenCreated() : false;
+ }
+ return false;
+}
+
+WindowHook* TaskbarPreview::GetWindowHook() {
+ nsWindow* window = WinUtils::GetNSWindowPtr(mWnd);
+ NS_ASSERTION(window, "Cannot use taskbar previews in an embedded context!");
+
+ return window ? &window->GetWindowHook() : nullptr;
+}
+
+void TaskbarPreview::EnableCustomDrawing(HWND aHWND, bool aEnable) {
+ BOOL enabled = aEnable;
+ DwmSetWindowAttribute(aHWND, DWMWA_FORCE_ICONIC_REPRESENTATION, &enabled,
+ sizeof(enabled));
+
+ DwmSetWindowAttribute(aHWND, DWMWA_HAS_ICONIC_BITMAP, &enabled,
+ sizeof(enabled));
+}
+
+nsresult TaskbarPreview::UpdateTooltip() {
+ NS_ASSERTION(CanMakeTaskbarCalls() && mVisible,
+ "UpdateTooltip called on invisible tab preview");
+
+ if (FAILED(mTaskbar->SetThumbnailTooltip(PreviewWindow(), mTooltip.get())))
+ return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+void TaskbarPreview::DrawBitmap(uint32_t width, uint32_t height,
+ bool isPreview) {
+ nsresult rv;
+ nsCOMPtr<nsITaskbarPreviewCallback> callback =
+ do_CreateInstance("@mozilla.org/widget/taskbar-preview-callback;1", &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ ((TaskbarPreviewCallback*)callback.get())->SetPreview(this);
+
+ if (isPreview) {
+ ((TaskbarPreviewCallback*)callback.get())->SetIsPreview();
+ mController->RequestPreview(callback);
+ } else {
+ mController->RequestThumbnail(callback, width, height);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TaskbarPreviewCallback
+
+NS_IMPL_ISUPPORTS(TaskbarPreviewCallback, nsITaskbarPreviewCallback)
+
+/* void done (in nsISupports aCanvas, in boolean aDrawBorder); */
+NS_IMETHODIMP
+TaskbarPreviewCallback::Done(nsISupports* aCanvas, bool aDrawBorder) {
+ // We create and destroy TaskbarTabPreviews from front end code in response
+ // to TabOpen and TabClose events. Each TaskbarTabPreview creates and owns a
+ // proxy HWND which it hands to Windows as a tab identifier. When a tab
+ // closes, TaskbarTabPreview Disable() method is called by front end, which
+ // destroys the proxy window and clears mProxyWindow which is the HWND
+ // returned from PreviewWindow(). So, since this is async, we should check to
+ // be sure the tab is still alive before doing all this gfx work and making
+ // dwm calls. To accomplish this we check the result of PreviewWindow().
+ if (!aCanvas || !mPreview || !mPreview->PreviewWindow() ||
+ !mPreview->IsWindowAvailable()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aCanvas));
+ auto canvas = dom::HTMLCanvasElement::FromNodeOrNull(content);
+ if (!canvas) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<gfx::SourceSurface> source = canvas->GetSurfaceSnapshot();
+ if (!source) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<gfxWindowsSurface> target = new gfxWindowsSurface(
+ source->GetSize(), gfx::SurfaceFormat::A8R8G8B8_UINT32);
+ if (target->CairoStatus() != CAIRO_STATUS_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ using DataSrcSurf = gfx::DataSourceSurface;
+ RefPtr<DataSrcSurf> srcSurface = source->GetDataSurface();
+ RefPtr<gfxImageSurface> imageSurface = target->GetAsImageSurface();
+ if (!srcSurface || !imageSurface) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (DataSrcSurf::ScopedMap const sourceMap(srcSurface, DataSrcSurf::READ);
+ sourceMap.IsMapped()) {
+ mozilla::gfx::CopySurfaceDataToPackedArray(
+ sourceMap.GetData(), imageSurface->Data(), srcSurface->GetSize(),
+ sourceMap.GetStride(), BytesPerPixel(srcSurface->GetFormat()));
+ } else if (source->GetSize().IsEmpty()) {
+ // A zero-size source-surface probably shouldn't happen, but is harmless
+ // here. Fall through.
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ HDC hDC = target->GetDC();
+ HBITMAP hBitmap = (HBITMAP)GetCurrentObject(hDC, OBJ_BITMAP);
+
+ DWORD flags = aDrawBorder ? DWM_SIT_DISPLAYFRAME : 0;
+ HRESULT hr;
+ if (!mIsThumbnail) {
+ POINT pptClient = {0, 0};
+ hr = DwmSetIconicLivePreviewBitmap(mPreview->PreviewWindow(), hBitmap,
+ &pptClient, flags);
+ } else {
+ hr = DwmSetIconicThumbnail(mPreview->PreviewWindow(), hBitmap, flags);
+ }
+ MOZ_ASSERT(SUCCEEDED(hr));
+ mozilla::Unused << hr;
+ return NS_OK;
+}
+
+/* static */
+bool TaskbarPreview::MainWindowHook(void* aContext, HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam,
+ LRESULT* aResult) {
+ NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage() ||
+ nMsg == WM_DESTROY,
+ "Window hook proc called with wrong message");
+ NS_ASSERTION(aContext, "Null context in MainWindowHook");
+ if (!aContext) return false;
+ TaskbarPreview* preview = reinterpret_cast<TaskbarPreview*>(aContext);
+ if (nMsg == WM_DESTROY) {
+ // nsWindow is being destroyed
+ // We can't really do anything at this point including removing hooks
+ return false;
+ } else {
+ nsWindow* window = WinUtils::GetNSWindowPtr(preview->mWnd);
+ if (window) {
+ window->SetHasTaskbarIconBeenCreated();
+
+ if (preview->mVisible) preview->UpdateTaskbarProperties();
+ }
+ }
+ return false;
+}
+
+TaskbarPreview* TaskbarPreview::sActivePreview = nullptr;
+
+} // namespace widget
+} // namespace mozilla