Bug 1829659 - Remove `import.py` because it's not used anymore r=glandium,bwc
[gecko.git] / widget / windows / TaskbarPreview.cpp
bloba5d2aeb54016bb9c91115e9e233987a268f076c2
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 // DWM Composition is required for previews
144 if (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) return NS_OK;
146 HWND previewWindow = PreviewWindow();
147 return FAILED(DwmInvalidateIconicBitmaps(previewWindow)) ? NS_ERROR_FAILURE
148 : NS_OK;
151 nsresult TaskbarPreview::UpdateTaskbarProperties() {
152 nsresult rv = UpdateTooltip();
154 // If we are the active preview and our window is the active window, restore
155 // our active state - otherwise some other non-preview window is now active
156 // and should be displayed as so.
157 if (sActivePreview == this) {
158 if (mWnd == ::GetActiveWindow()) {
159 nsresult rvActive = ShowActive(true);
160 if (NS_FAILED(rvActive)) rv = rvActive;
161 } else {
162 sActivePreview = nullptr;
165 return rv;
168 nsresult TaskbarPreview::Enable() {
169 nsresult rv = NS_OK;
170 if (CanMakeTaskbarCalls()) {
171 rv = UpdateTaskbarProperties();
172 } else if (IsWindowAvailable()) {
173 WindowHook* hook = GetWindowHook();
174 MOZ_ASSERT(hook,
175 "IsWindowAvailable() should have eliminated the null case.");
176 hook->AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
177 MainWindowHook, this);
179 return rv;
182 nsresult TaskbarPreview::Disable() {
183 if (!IsWindowAvailable()) {
184 // Window is already destroyed
185 return NS_OK;
188 WindowHook* hook = GetWindowHook();
189 MOZ_ASSERT(hook, "IsWindowAvailable() should have eliminated the null case.");
190 (void)hook->RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
191 MainWindowHook, this);
193 return NS_OK;
196 bool TaskbarPreview::IsWindowAvailable() const {
197 if (mWnd) {
198 nsWindow* win = WinUtils::GetNSWindowPtr(mWnd);
199 if (win && !win->Destroyed()) {
200 return true;
203 return false;
206 void TaskbarPreview::DetachFromNSWindow() {
207 if (WindowHook* hook = GetWindowHook()) {
208 hook->RemoveMonitor(WM_DESTROY, MainWindowHook, this);
210 mWnd = nullptr;
213 LRESULT
214 TaskbarPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) {
215 switch (nMsg) {
216 case WM_DWMSENDICONICTHUMBNAIL: {
217 uint32_t width = HIWORD(lParam);
218 uint32_t height = LOWORD(lParam);
219 float aspectRatio = width / float(height);
221 nsresult rv;
222 float preferredAspectRatio;
223 rv = mController->GetThumbnailAspectRatio(&preferredAspectRatio);
224 if (NS_FAILED(rv)) break;
226 uint32_t thumbnailWidth = width;
227 uint32_t thumbnailHeight = height;
229 if (aspectRatio > preferredAspectRatio) {
230 thumbnailWidth = uint32_t(thumbnailHeight * preferredAspectRatio);
231 } else {
232 thumbnailHeight = uint32_t(thumbnailWidth / preferredAspectRatio);
235 DrawBitmap(thumbnailWidth, thumbnailHeight, false);
236 } break;
237 case WM_DWMSENDICONICLIVEPREVIEWBITMAP: {
238 uint32_t width, height;
239 nsresult rv;
240 rv = mController->GetWidth(&width);
241 if (NS_FAILED(rv)) break;
242 rv = mController->GetHeight(&height);
243 if (NS_FAILED(rv)) break;
245 double scale = StaticPrefs::layout_css_devPixelsPerPx();
246 if (scale <= 0.0) {
247 scale = WinUtils::LogToPhysFactor(PreviewWindow());
249 DrawBitmap(NSToIntRound(scale * width), NSToIntRound(scale * height),
250 true);
251 } break;
253 return ::DefWindowProcW(PreviewWindow(), nMsg, wParam, lParam);
256 bool TaskbarPreview::CanMakeTaskbarCalls() {
257 // If the nsWindow has already been destroyed and we know it but our caller
258 // clearly doesn't so we can't make any calls.
259 if (!mWnd) return false;
260 // Certain functions like SetTabOrder seem to require a visible window. During
261 // window close, the window seems to be hidden before being destroyed.
262 if (!::IsWindowVisible(mWnd)) return false;
263 if (mVisible) {
264 nsWindow* window = WinUtils::GetNSWindowPtr(mWnd);
265 NS_ASSERTION(window, "Could not get nsWindow from HWND");
266 return window ? window->HasTaskbarIconBeenCreated() : false;
268 return false;
271 WindowHook* TaskbarPreview::GetWindowHook() {
272 nsWindow* window = WinUtils::GetNSWindowPtr(mWnd);
273 NS_ASSERTION(window, "Cannot use taskbar previews in an embedded context!");
275 return window ? &window->GetWindowHook() : nullptr;
278 void TaskbarPreview::EnableCustomDrawing(HWND aHWND, bool aEnable) {
279 BOOL enabled = aEnable;
280 DwmSetWindowAttribute(aHWND, DWMWA_FORCE_ICONIC_REPRESENTATION, &enabled,
281 sizeof(enabled));
283 DwmSetWindowAttribute(aHWND, DWMWA_HAS_ICONIC_BITMAP, &enabled,
284 sizeof(enabled));
287 nsresult TaskbarPreview::UpdateTooltip() {
288 NS_ASSERTION(CanMakeTaskbarCalls() && mVisible,
289 "UpdateTooltip called on invisible tab preview");
291 if (FAILED(mTaskbar->SetThumbnailTooltip(PreviewWindow(), mTooltip.get())))
292 return NS_ERROR_FAILURE;
293 return NS_OK;
296 void TaskbarPreview::DrawBitmap(uint32_t width, uint32_t height,
297 bool isPreview) {
298 nsresult rv;
299 nsCOMPtr<nsITaskbarPreviewCallback> callback =
300 do_CreateInstance("@mozilla.org/widget/taskbar-preview-callback;1", &rv);
301 if (NS_FAILED(rv)) {
302 return;
305 ((TaskbarPreviewCallback*)callback.get())->SetPreview(this);
307 if (isPreview) {
308 ((TaskbarPreviewCallback*)callback.get())->SetIsPreview();
309 mController->RequestPreview(callback);
310 } else {
311 mController->RequestThumbnail(callback, width, height);
315 ///////////////////////////////////////////////////////////////////////////////
316 // TaskbarPreviewCallback
318 NS_IMPL_ISUPPORTS(TaskbarPreviewCallback, nsITaskbarPreviewCallback)
320 /* void done (in nsISupports aCanvas, in boolean aDrawBorder); */
321 NS_IMETHODIMP
322 TaskbarPreviewCallback::Done(nsISupports* aCanvas, bool aDrawBorder) {
323 // We create and destroy TaskbarTabPreviews from front end code in response
324 // to TabOpen and TabClose events. Each TaskbarTabPreview creates and owns a
325 // proxy HWND which it hands to Windows as a tab identifier. When a tab
326 // closes, TaskbarTabPreview Disable() method is called by front end, which
327 // destroys the proxy window and clears mProxyWindow which is the HWND
328 // returned from PreviewWindow(). So, since this is async, we should check to
329 // be sure the tab is still alive before doing all this gfx work and making
330 // dwm calls. To accomplish this we check the result of PreviewWindow().
331 if (!aCanvas || !mPreview || !mPreview->PreviewWindow() ||
332 !mPreview->IsWindowAvailable()) {
333 return NS_ERROR_FAILURE;
336 nsCOMPtr<nsIContent> content(do_QueryInterface(aCanvas));
337 auto canvas = dom::HTMLCanvasElement::FromNodeOrNull(content);
338 if (!canvas) {
339 return NS_ERROR_FAILURE;
342 RefPtr<gfx::SourceSurface> source = canvas->GetSurfaceSnapshot();
343 if (!source) {
344 return NS_ERROR_FAILURE;
346 RefPtr<gfxWindowsSurface> target = new gfxWindowsSurface(
347 source->GetSize(), gfx::SurfaceFormat::A8R8G8B8_UINT32);
348 if (target->CairoStatus() != CAIRO_STATUS_SUCCESS) {
349 return NS_ERROR_FAILURE;
352 using DataSrcSurf = gfx::DataSourceSurface;
353 RefPtr<DataSrcSurf> srcSurface = source->GetDataSurface();
354 RefPtr<gfxImageSurface> imageSurface = target->GetAsImageSurface();
355 if (!srcSurface || !imageSurface) {
356 return NS_ERROR_FAILURE;
359 if (DataSrcSurf::ScopedMap const sourceMap(srcSurface, DataSrcSurf::READ);
360 sourceMap.IsMapped()) {
361 mozilla::gfx::CopySurfaceDataToPackedArray(
362 sourceMap.GetData(), imageSurface->Data(), srcSurface->GetSize(),
363 sourceMap.GetStride(), BytesPerPixel(srcSurface->GetFormat()));
364 } else if (source->GetSize().IsEmpty()) {
365 // A zero-size source-surface probably shouldn't happen, but is harmless
366 // here. Fall through.
367 } else {
368 return NS_ERROR_FAILURE;
371 HDC hDC = target->GetDC();
372 HBITMAP hBitmap = (HBITMAP)GetCurrentObject(hDC, OBJ_BITMAP);
374 DWORD flags = aDrawBorder ? DWM_SIT_DISPLAYFRAME : 0;
375 HRESULT hr;
376 if (!mIsThumbnail) {
377 POINT pptClient = {0, 0};
378 hr = DwmSetIconicLivePreviewBitmap(mPreview->PreviewWindow(), hBitmap,
379 &pptClient, flags);
380 } else {
381 hr = DwmSetIconicThumbnail(mPreview->PreviewWindow(), hBitmap, flags);
383 MOZ_ASSERT(SUCCEEDED(hr));
384 mozilla::Unused << hr;
385 return NS_OK;
388 /* static */
389 bool TaskbarPreview::MainWindowHook(void* aContext, HWND hWnd, UINT nMsg,
390 WPARAM wParam, LPARAM lParam,
391 LRESULT* aResult) {
392 NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage() ||
393 nMsg == WM_DESTROY,
394 "Window hook proc called with wrong message");
395 NS_ASSERTION(aContext, "Null context in MainWindowHook");
396 if (!aContext) return false;
397 TaskbarPreview* preview = reinterpret_cast<TaskbarPreview*>(aContext);
398 if (nMsg == WM_DESTROY) {
399 // nsWindow is being destroyed
400 // We can't really do anything at this point including removing hooks
401 return false;
402 } else {
403 nsWindow* window = WinUtils::GetNSWindowPtr(preview->mWnd);
404 if (window) {
405 window->SetHasTaskbarIconBeenCreated();
407 if (preview->mVisible) preview->UpdateTaskbarProperties();
410 return false;
413 TaskbarPreview* TaskbarPreview::sActivePreview = nullptr;
415 } // namespace widget
416 } // namespace mozilla