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.cpp431
1 files changed, 431 insertions, 0 deletions
diff --git a/widget/windows/TaskbarPreview.cpp b/widget/windows/TaskbarPreview.cpp
new file mode 100644
index 000000000..c897af021
--- /dev/null
+++ b/widget/windows/TaskbarPreview.cpp
@@ -0,0 +1,431 @@
+/* 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 <nsIBaseWindow.h>
+#include <nsIObserverService.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/Telemetry.h"
+
+// Defined in dwmapi in a header that needs a higher numbered _WINNT #define
+#define DWM_SIT_DISPLAYFRAME 0x1
+
+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 may outlive the WinTaskbar that created it
+ ::CoInitialize(nullptr);
+
+ WindowHook &hook = GetWindowHook();
+ hook.AddMonitor(WM_DESTROY, MainWindowHook, this);
+}
+
+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();
+}
+
+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 (!mWnd)
+ 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 (!nsUXThemeData::CheckForCompositor())
+ return NS_OK;
+
+ HWND previewWindow = PreviewWindow();
+ return FAILED(WinUtils::dwmInvalidateIconicBitmapsPtr(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 {
+ WindowHook &hook = GetWindowHook();
+ hook.AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), MainWindowHook, this);
+ }
+ return rv;
+}
+
+nsresult
+TaskbarPreview::Disable() {
+ if (!IsWindowAvailable()) {
+ // Window is already destroyed
+ return NS_OK;
+ }
+
+ WindowHook &hook = GetWindowHook();
+ (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() {
+ 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 = nsIWidget::DefaultScaleOverride();
+ 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->HasTaskbarIconBeenCreated();
+ }
+ return false;
+}
+
+WindowHook&
+TaskbarPreview::GetWindowHook() {
+ nsWindow *window = WinUtils::GetNSWindowPtr(mWnd);
+ NS_ASSERTION(window, "Cannot use taskbar previews in an embedded context!");
+
+ return window->GetWindowHook();
+}
+
+void
+TaskbarPreview::EnableCustomDrawing(HWND aHWND, bool aEnable) {
+ BOOL enabled = aEnable;
+ WinUtils::dwmSetWindowAttributePtr(
+ aHWND,
+ DWMWA_FORCE_ICONIC_REPRESENTATION,
+ &enabled,
+ sizeof(enabled));
+
+ WinUtils::dwmSetWindowAttributePtr(
+ 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<nsIDOMHTMLCanvasElement> domcanvas(do_QueryInterface(aCanvas));
+ dom::HTMLCanvasElement * canvas = ((dom::HTMLCanvasElement*)domcanvas.get());
+ 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) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<gfx::DataSourceSurface> srcSurface = source->GetDataSurface();
+ RefPtr<gfxImageSurface> imageSurface = target->GetAsImageSurface();
+ if (!srcSurface || !imageSurface) {
+ return NS_ERROR_FAILURE;
+ }
+
+ gfx::DataSourceSurface::MappedSurface sourceMap;
+ srcSurface->Map(gfx::DataSourceSurface::READ, &sourceMap);
+ mozilla::gfx::CopySurfaceDataToPackedArray(sourceMap.mData,
+ imageSurface->Data(),
+ srcSurface->GetSize(),
+ sourceMap.mStride,
+ BytesPerPixel(srcSurface->GetFormat()));
+ srcSurface->Unmap();
+
+ HDC hDC = target->GetDC();
+ HBITMAP hBitmap = (HBITMAP)GetCurrentObject(hDC, OBJ_BITMAP);
+
+ DWORD flags = aDrawBorder ? DWM_SIT_DISPLAYFRAME : 0;
+ POINT pptClient = { 0, 0 };
+ HRESULT hr;
+ if (!mIsThumbnail) {
+ hr = WinUtils::dwmSetIconicLivePreviewBitmapPtr(mPreview->PreviewWindow(),
+ hBitmap, &pptClient, flags);
+ } else {
+ hr = WinUtils::dwmSetIconicThumbnailPtr(mPreview->PreviewWindow(),
+ hBitmap, flags);
+ }
+ MOZ_ASSERT(SUCCEEDED(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
+