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>
14 #include <nsIWidget.h>
15 #include <nsServiceManagerUtils.h>
17 #include "nsUXThemeData.h"
19 #include "nsAppShell.h"
20 #include "TaskbarPreviewButton.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
37 ///////////////////////////////////////////////////////////////////////////////
40 TaskbarPreview::TaskbarPreview(ITaskbarList4
* aTaskbar
,
41 nsITaskbarPreviewController
* aController
,
42 HWND aHWND
, nsIDocShell
* aShell
)
44 mController(aController
),
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.
56 "TaskbarPreview::DetachFromNSWindow was not called before destruction");
58 // Make sure to release before potentially uninitializing COM
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();
72 return NS_ERROR_NOT_AVAILABLE
;
74 return hook
->AddMonitor(WM_DESTROY
, MainWindowHook
, this);
78 TaskbarPreview::SetController(nsITaskbarPreviewController
* aController
) {
79 NS_ENSURE_ARG(aController
);
81 mController
= aController
;
86 TaskbarPreview::GetController(nsITaskbarPreviewController
** aController
) {
87 NS_ADDREF(*aController
= mController
);
92 TaskbarPreview::GetTooltip(nsAString
& aTooltip
) {
98 TaskbarPreview::SetTooltip(const nsAString
& aTooltip
) {
100 return CanMakeTaskbarCalls() ? UpdateTooltip() : NS_OK
;
104 TaskbarPreview::SetVisible(bool visible
) {
105 if (mVisible
== visible
) return NS_OK
;
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();
118 TaskbarPreview::GetVisible(bool* visible
) {
124 TaskbarPreview::SetActive(bool active
) {
126 sActivePreview
= this;
127 else if (sActivePreview
== this)
128 sActivePreview
= nullptr;
130 return CanMakeTaskbarCalls() ? ShowActive(active
) : NS_OK
;
134 TaskbarPreview::GetActive(bool* active
) {
135 *active
= sActivePreview
== this;
140 TaskbarPreview::Invalidate() {
141 if (!mVisible
) return NS_OK
;
143 HWND previewWindow
= PreviewWindow();
144 return FAILED(DwmInvalidateIconicBitmaps(previewWindow
)) ? NS_ERROR_FAILURE
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
;
159 sActivePreview
= nullptr;
165 nsresult
TaskbarPreview::Enable() {
167 if (CanMakeTaskbarCalls()) {
168 rv
= UpdateTaskbarProperties();
169 } else if (IsWindowAvailable()) {
170 WindowHook
* hook
= GetWindowHook();
172 "IsWindowAvailable() should have eliminated the null case.");
173 hook
->AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
174 MainWindowHook
, this);
179 nsresult
TaskbarPreview::Disable() {
180 if (!IsWindowAvailable()) {
181 // Window is already destroyed
185 WindowHook
* hook
= GetWindowHook();
186 MOZ_ASSERT(hook
, "IsWindowAvailable() should have eliminated the null case.");
187 (void)hook
->RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
188 MainWindowHook
, this);
193 bool TaskbarPreview::IsWindowAvailable() const {
195 nsWindow
* win
= WinUtils::GetNSWindowPtr(mWnd
);
196 if (win
&& !win
->Destroyed()) {
203 void TaskbarPreview::DetachFromNSWindow() {
204 if (WindowHook
* hook
= GetWindowHook()) {
205 hook
->RemoveMonitor(WM_DESTROY
, MainWindowHook
, this);
211 TaskbarPreview::WndProc(UINT nMsg
, WPARAM wParam
, LPARAM lParam
) {
213 case WM_DWMSENDICONICTHUMBNAIL
: {
214 uint32_t width
= HIWORD(lParam
);
215 uint32_t height
= LOWORD(lParam
);
216 float aspectRatio
= width
/ float(height
);
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
);
229 thumbnailHeight
= uint32_t(thumbnailWidth
/ preferredAspectRatio
);
232 DrawBitmap(thumbnailWidth
, thumbnailHeight
, false);
234 case WM_DWMSENDICONICLIVEPREVIEWBITMAP
: {
235 uint32_t width
, height
;
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();
244 scale
= WinUtils::LogToPhysFactor(PreviewWindow());
246 DrawBitmap(NSToIntRound(scale
* width
), NSToIntRound(scale
* height
),
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;
261 nsWindow
* window
= WinUtils::GetNSWindowPtr(mWnd
);
262 NS_ASSERTION(window
, "Could not get nsWindow from HWND");
263 return window
? window
->HasTaskbarIconBeenCreated() : 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
,
280 DwmSetWindowAttribute(aHWND
, DWMWA_HAS_ICONIC_BITMAP
, &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
;
293 void TaskbarPreview::DrawBitmap(uint32_t width
, uint32_t height
,
296 nsCOMPtr
<nsITaskbarPreviewCallback
> callback
=
297 do_CreateInstance("@mozilla.org/widget/taskbar-preview-callback;1", &rv
);
302 ((TaskbarPreviewCallback
*)callback
.get())->SetPreview(this);
305 ((TaskbarPreviewCallback
*)callback
.get())->SetIsPreview();
306 mController
->RequestPreview(callback
);
308 mController
->RequestThumbnail(callback
, width
, height
);
312 ///////////////////////////////////////////////////////////////////////////////
313 // TaskbarPreviewCallback
315 NS_IMPL_ISUPPORTS(TaskbarPreviewCallback
, nsITaskbarPreviewCallback
)
317 /* void done (in nsISupports aCanvas, in boolean aDrawBorder); */
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
);
336 return NS_ERROR_FAILURE
;
339 RefPtr
<gfx::SourceSurface
> source
= canvas
->GetSurfaceSnapshot();
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.
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;
374 POINT pptClient
= {0, 0};
375 hr
= DwmSetIconicLivePreviewBitmap(mPreview
->PreviewWindow(), hBitmap
,
378 hr
= DwmSetIconicThumbnail(mPreview
->PreviewWindow(), hBitmap
, flags
);
380 MOZ_ASSERT(SUCCEEDED(hr
));
381 mozilla::Unused
<< hr
;
386 bool TaskbarPreview::MainWindowHook(void* aContext
, HWND hWnd
, UINT nMsg
,
387 WPARAM wParam
, LPARAM lParam
,
389 NS_ASSERTION(nMsg
== nsAppShell::GetTaskbarButtonCreatedMessage() ||
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
400 nsWindow
* window
= WinUtils::GetNSWindowPtr(preview
->mWnd
);
402 window
->SetHasTaskbarIconBeenCreated();
404 if (preview
->mVisible
) preview
->UpdateTaskbarProperties();
410 TaskbarPreview
* TaskbarPreview::sActivePreview
= nullptr;
412 } // namespace widget
413 } // namespace mozilla