Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / windows / TaskbarPreview.cpp
blob2b958f84c1c3aec628a03c5c0f6230adfe39bbb0
1 /* vim: se cin sw=2 ts=2 et : */
2 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "TaskbarPreview.h"
9 #include <nsITaskbarPreviewController.h>
10 #include <windows.h>
12 #include <nsError.h>
13 #include <nsCOMPtr.h>
14 #include <nsIWidget.h>
15 #include <nsServiceManagerUtils.h>
17 #include "nsUXThemeData.h"
18 #include "nsWindow.h"
19 #include "nsAppShell.h"
20 #include "TaskbarPreviewButton.h"
21 #include "WinUtils.h"
23 #include "mozilla/dom/HTMLCanvasElement.h"
24 #include "mozilla/gfx/2D.h"
25 #include "mozilla/gfx/DataSurfaceHelpers.h"
26 #include "mozilla/StaticPrefs_layout.h"
27 #include "mozilla/Telemetry.h"
29 // Defined in dwmapi in a header that needs a higher numbered _WINNT #define
30 #ifndef DWM_SIT_DISPLAYFRAME
31 # define DWM_SIT_DISPLAYFRAME 0x1
32 #endif
34 namespace mozilla {
35 namespace widget {
37 ///////////////////////////////////////////////////////////////////////////////
38 // TaskbarPreview
40 TaskbarPreview::TaskbarPreview(ITaskbarList4* aTaskbar,
41 nsITaskbarPreviewController* aController,
42 HWND aHWND, nsIDocShell* aShell)
43 : mTaskbar(aTaskbar),
44 mController(aController),
45 mWnd(aHWND),
46 mVisible(false),
47 mDocShell(do_GetWeakReference(aShell)) {}
49 TaskbarPreview::~TaskbarPreview() {
50 // Avoid dangling pointer
51 if (sActivePreview == this) sActivePreview = nullptr;
53 // Our subclass should have invoked DetachFromNSWindow already.
54 NS_ASSERTION(
55 !mWnd,
56 "TaskbarPreview::DetachFromNSWindow was not called before destruction");
58 // Make sure to release before potentially uninitializing COM
59 mTaskbar = nullptr;
61 ::CoUninitialize();
64 nsresult TaskbarPreview::Init() {
65 // TaskbarPreview may outlive the WinTaskbar that created it
66 if (FAILED(::CoInitialize(nullptr))) {
67 return NS_ERROR_NOT_INITIALIZED;
70 WindowHook* hook = GetWindowHook();
71 if (!hook) {
72 return NS_ERROR_NOT_AVAILABLE;
74 return hook->AddMonitor(WM_DESTROY, MainWindowHook, this);
77 NS_IMETHODIMP
78 TaskbarPreview::SetController(nsITaskbarPreviewController* aController) {
79 NS_ENSURE_ARG(aController);
81 mController = aController;
82 return NS_OK;
85 NS_IMETHODIMP
86 TaskbarPreview::GetController(nsITaskbarPreviewController** aController) {
87 NS_ADDREF(*aController = mController);
88 return NS_OK;
91 NS_IMETHODIMP
92 TaskbarPreview::GetTooltip(nsAString& aTooltip) {
93 aTooltip = mTooltip;
94 return NS_OK;
97 NS_IMETHODIMP
98 TaskbarPreview::SetTooltip(const nsAString& aTooltip) {
99 mTooltip = aTooltip;
100 return CanMakeTaskbarCalls() ? UpdateTooltip() : NS_OK;
103 NS_IMETHODIMP
104 TaskbarPreview::SetVisible(bool visible) {
105 if (mVisible == visible) return NS_OK;
106 mVisible = visible;
108 // If the nsWindow has already been destroyed but the caller is still trying
109 // to use it then just pretend that everything succeeded. The caller doesn't
110 // actually have a way to detect this since it's the same case as when we
111 // CanMakeTaskbarCalls returns false.
112 if (!IsWindowAvailable()) return NS_OK;
114 return visible ? Enable() : Disable();
117 NS_IMETHODIMP
118 TaskbarPreview::GetVisible(bool* visible) {
119 *visible = mVisible;
120 return NS_OK;
123 NS_IMETHODIMP
124 TaskbarPreview::SetActive(bool active) {
125 if (active)
126 sActivePreview = this;
127 else if (sActivePreview == this)
128 sActivePreview = nullptr;
130 return CanMakeTaskbarCalls() ? ShowActive(active) : NS_OK;
133 NS_IMETHODIMP
134 TaskbarPreview::GetActive(bool* active) {
135 *active = sActivePreview == this;
136 return NS_OK;
139 NS_IMETHODIMP
140 TaskbarPreview::Invalidate() {
141 if (!mVisible) return NS_OK;
143 HWND previewWindow = PreviewWindow();
144 return FAILED(DwmInvalidateIconicBitmaps(previewWindow)) ? NS_ERROR_FAILURE
145 : NS_OK;
148 nsresult TaskbarPreview::UpdateTaskbarProperties() {
149 nsresult rv = UpdateTooltip();
151 // If we are the active preview and our window is the active window, restore
152 // our active state - otherwise some other non-preview window is now active
153 // and should be displayed as so.
154 if (sActivePreview == this) {
155 if (mWnd == ::GetActiveWindow()) {
156 nsresult rvActive = ShowActive(true);
157 if (NS_FAILED(rvActive)) rv = rvActive;
158 } else {
159 sActivePreview = nullptr;
162 return rv;
165 nsresult TaskbarPreview::Enable() {
166 nsresult rv = NS_OK;
167 if (CanMakeTaskbarCalls()) {
168 rv = UpdateTaskbarProperties();
169 } else if (IsWindowAvailable()) {
170 WindowHook* hook = GetWindowHook();
171 MOZ_ASSERT(hook,
172 "IsWindowAvailable() should have eliminated the null case.");
173 hook->AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
174 MainWindowHook, this);
176 return rv;
179 nsresult TaskbarPreview::Disable() {
180 if (!IsWindowAvailable()) {
181 // Window is already destroyed
182 return NS_OK;
185 WindowHook* hook = GetWindowHook();
186 MOZ_ASSERT(hook, "IsWindowAvailable() should have eliminated the null case.");
187 (void)hook->RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
188 MainWindowHook, this);
190 return NS_OK;
193 bool TaskbarPreview::IsWindowAvailable() const {
194 if (mWnd) {
195 nsWindow* win = WinUtils::GetNSWindowPtr(mWnd);
196 if (win && !win->Destroyed()) {
197 return true;
200 return false;
203 void TaskbarPreview::DetachFromNSWindow() {
204 if (WindowHook* hook = GetWindowHook()) {
205 hook->RemoveMonitor(WM_DESTROY, MainWindowHook, this);
207 mWnd = nullptr;
210 LRESULT
211 TaskbarPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) {
212 switch (nMsg) {
213 case WM_DWMSENDICONICTHUMBNAIL: {
214 uint32_t width = HIWORD(lParam);
215 uint32_t height = LOWORD(lParam);
216 float aspectRatio = width / float(height);
218 nsresult rv;
219 float preferredAspectRatio;
220 rv = mController->GetThumbnailAspectRatio(&preferredAspectRatio);
221 if (NS_FAILED(rv)) break;
223 uint32_t thumbnailWidth = width;
224 uint32_t thumbnailHeight = height;
226 if (aspectRatio > preferredAspectRatio) {
227 thumbnailWidth = uint32_t(thumbnailHeight * preferredAspectRatio);
228 } else {
229 thumbnailHeight = uint32_t(thumbnailWidth / preferredAspectRatio);
232 DrawBitmap(thumbnailWidth, thumbnailHeight, false);
233 } break;
234 case WM_DWMSENDICONICLIVEPREVIEWBITMAP: {
235 uint32_t width, height;
236 nsresult rv;
237 rv = mController->GetWidth(&width);
238 if (NS_FAILED(rv)) break;
239 rv = mController->GetHeight(&height);
240 if (NS_FAILED(rv)) break;
242 double scale = StaticPrefs::layout_css_devPixelsPerPx();
243 if (scale <= 0.0) {
244 scale = WinUtils::LogToPhysFactor(PreviewWindow());
246 DrawBitmap(NSToIntRound(scale * width), NSToIntRound(scale * height),
247 true);
248 } break;
250 return ::DefWindowProcW(PreviewWindow(), nMsg, wParam, lParam);
253 bool TaskbarPreview::CanMakeTaskbarCalls() {
254 // If the nsWindow has already been destroyed and we know it but our caller
255 // clearly doesn't so we can't make any calls.
256 if (!mWnd) return false;
257 // Certain functions like SetTabOrder seem to require a visible window. During
258 // window close, the window seems to be hidden before being destroyed.
259 if (!::IsWindowVisible(mWnd)) return false;
260 if (mVisible) {
261 nsWindow* window = WinUtils::GetNSWindowPtr(mWnd);
262 NS_ASSERTION(window, "Could not get nsWindow from HWND");
263 return window ? window->HasTaskbarIconBeenCreated() : false;
265 return false;
268 WindowHook* TaskbarPreview::GetWindowHook() {
269 nsWindow* window = WinUtils::GetNSWindowPtr(mWnd);
270 NS_ASSERTION(window, "Cannot use taskbar previews in an embedded context!");
272 return window ? &window->GetWindowHook() : nullptr;
275 void TaskbarPreview::EnableCustomDrawing(HWND aHWND, bool aEnable) {
276 BOOL enabled = aEnable;
277 DwmSetWindowAttribute(aHWND, DWMWA_FORCE_ICONIC_REPRESENTATION, &enabled,
278 sizeof(enabled));
280 DwmSetWindowAttribute(aHWND, DWMWA_HAS_ICONIC_BITMAP, &enabled,
281 sizeof(enabled));
284 nsresult TaskbarPreview::UpdateTooltip() {
285 NS_ASSERTION(CanMakeTaskbarCalls() && mVisible,
286 "UpdateTooltip called on invisible tab preview");
288 if (FAILED(mTaskbar->SetThumbnailTooltip(PreviewWindow(), mTooltip.get())))
289 return NS_ERROR_FAILURE;
290 return NS_OK;
293 void TaskbarPreview::DrawBitmap(uint32_t width, uint32_t height,
294 bool isPreview) {
295 nsresult rv;
296 nsCOMPtr<nsITaskbarPreviewCallback> callback =
297 do_CreateInstance("@mozilla.org/widget/taskbar-preview-callback;1", &rv);
298 if (NS_FAILED(rv)) {
299 return;
302 ((TaskbarPreviewCallback*)callback.get())->SetPreview(this);
304 if (isPreview) {
305 ((TaskbarPreviewCallback*)callback.get())->SetIsPreview();
306 mController->RequestPreview(callback);
307 } else {
308 mController->RequestThumbnail(callback, width, height);
312 ///////////////////////////////////////////////////////////////////////////////
313 // TaskbarPreviewCallback
315 NS_IMPL_ISUPPORTS(TaskbarPreviewCallback, nsITaskbarPreviewCallback)
317 /* void done (in nsISupports aCanvas, in boolean aDrawBorder); */
318 NS_IMETHODIMP
319 TaskbarPreviewCallback::Done(nsISupports* aCanvas, bool aDrawBorder) {
320 // We create and destroy TaskbarTabPreviews from front end code in response
321 // to TabOpen and TabClose events. Each TaskbarTabPreview creates and owns a
322 // proxy HWND which it hands to Windows as a tab identifier. When a tab
323 // closes, TaskbarTabPreview Disable() method is called by front end, which
324 // destroys the proxy window and clears mProxyWindow which is the HWND
325 // returned from PreviewWindow(). So, since this is async, we should check to
326 // be sure the tab is still alive before doing all this gfx work and making
327 // dwm calls. To accomplish this we check the result of PreviewWindow().
328 if (!aCanvas || !mPreview || !mPreview->PreviewWindow() ||
329 !mPreview->IsWindowAvailable()) {
330 return NS_ERROR_FAILURE;
333 nsCOMPtr<nsIContent> content(do_QueryInterface(aCanvas));
334 auto canvas = dom::HTMLCanvasElement::FromNodeOrNull(content);
335 if (!canvas) {
336 return NS_ERROR_FAILURE;
339 RefPtr<gfx::SourceSurface> source = canvas->GetSurfaceSnapshot();
340 if (!source) {
341 return NS_ERROR_FAILURE;
343 RefPtr<gfxWindowsSurface> target = new gfxWindowsSurface(
344 source->GetSize(), gfx::SurfaceFormat::A8R8G8B8_UINT32);
345 if (target->CairoStatus() != CAIRO_STATUS_SUCCESS) {
346 return NS_ERROR_FAILURE;
349 using DataSrcSurf = gfx::DataSourceSurface;
350 RefPtr<DataSrcSurf> srcSurface = source->GetDataSurface();
351 RefPtr<gfxImageSurface> imageSurface = target->GetAsImageSurface();
352 if (!srcSurface || !imageSurface) {
353 return NS_ERROR_FAILURE;
356 if (DataSrcSurf::ScopedMap const sourceMap(srcSurface, DataSrcSurf::READ);
357 sourceMap.IsMapped()) {
358 mozilla::gfx::CopySurfaceDataToPackedArray(
359 sourceMap.GetData(), imageSurface->Data(), srcSurface->GetSize(),
360 sourceMap.GetStride(), BytesPerPixel(srcSurface->GetFormat()));
361 } else if (source->GetSize().IsEmpty()) {
362 // A zero-size source-surface probably shouldn't happen, but is harmless
363 // here. Fall through.
364 } else {
365 return NS_ERROR_FAILURE;
368 HDC hDC = target->GetDC();
369 HBITMAP hBitmap = (HBITMAP)GetCurrentObject(hDC, OBJ_BITMAP);
371 DWORD flags = aDrawBorder ? DWM_SIT_DISPLAYFRAME : 0;
372 HRESULT hr;
373 if (!mIsThumbnail) {
374 POINT pptClient = {0, 0};
375 hr = DwmSetIconicLivePreviewBitmap(mPreview->PreviewWindow(), hBitmap,
376 &pptClient, flags);
377 } else {
378 hr = DwmSetIconicThumbnail(mPreview->PreviewWindow(), hBitmap, flags);
380 MOZ_ASSERT(SUCCEEDED(hr));
381 mozilla::Unused << hr;
382 return NS_OK;
385 /* static */
386 bool TaskbarPreview::MainWindowHook(void* aContext, HWND hWnd, UINT nMsg,
387 WPARAM wParam, LPARAM lParam,
388 LRESULT* aResult) {
389 NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage() ||
390 nMsg == WM_DESTROY,
391 "Window hook proc called with wrong message");
392 NS_ASSERTION(aContext, "Null context in MainWindowHook");
393 if (!aContext) return false;
394 TaskbarPreview* preview = reinterpret_cast<TaskbarPreview*>(aContext);
395 if (nMsg == WM_DESTROY) {
396 // nsWindow is being destroyed
397 // We can't really do anything at this point including removing hooks
398 return false;
399 } else {
400 nsWindow* window = WinUtils::GetNSWindowPtr(preview->mWnd);
401 if (window) {
402 window->SetHasTaskbarIconBeenCreated();
404 if (preview->mVisible) preview->UpdateTaskbarProperties();
407 return false;
410 TaskbarPreview* TaskbarPreview::sActivePreview = nullptr;
412 } // namespace widget
413 } // namespace mozilla