1 /* vim: se cin sw=2 ts=2 et : */
2 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 *
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/. */
7
8 #include "TaskbarPreview.h"
9 #include <nsITaskbarPreviewController.h>
10 #include <windows.h>
11
12 #include <nsError.h>
13 #include <nsCOMPtr.h>
14 #include <nsIWidget.h>
15 #include <nsIBaseWindow.h>
16 #include <nsIObserverService.h>
17 #include <nsServiceManagerUtils.h>
18
19 #include "nsUXThemeData.h"
20 #include "nsWindow.h"
21 #include "nsAppShell.h"
22 #include "TaskbarPreviewButton.h"
23 #include "WinUtils.h"
24
25 #include "mozilla/dom/HTMLCanvasElement.h"
26 #include "mozilla/gfx/2D.h"
27 #include "mozilla/gfx/DataSurfaceHelpers.h"
28 #include "mozilla/Telemetry.h"
29
30 // Defined in dwmapi in a header that needs a higher numbered _WINNT #define
31 #ifndef DWM_SIT_DISPLAYFRAME
32 #define DWM_SIT_DISPLAYFRAME 0x1
33 #endif
34
35 namespace mozilla {
36 namespace widget {
37
38 ///////////////////////////////////////////////////////////////////////////////
39 // TaskbarPreview
40
TaskbarPreview(ITaskbarList4 * aTaskbar,nsITaskbarPreviewController * aController,HWND aHWND,nsIDocShell * aShell)41 TaskbarPreview::TaskbarPreview(ITaskbarList4 *aTaskbar,
42 nsITaskbarPreviewController *aController,
43 HWND aHWND, nsIDocShell *aShell)
44 : mTaskbar(aTaskbar),
45 mController(aController),
46 mWnd(aHWND),
47 mVisible(false),
48 mDocShell(do_GetWeakReference(aShell)) {
49 // TaskbarPreview may outlive the WinTaskbar that created it
50 ::CoInitialize(nullptr);
51
52 WindowHook &hook = GetWindowHook();
53 hook.AddMonitor(WM_DESTROY, MainWindowHook, this);
54 }
55
~TaskbarPreview()56 TaskbarPreview::~TaskbarPreview() {
57 // Avoid dangling pointer
58 if (sActivePreview == this) sActivePreview = nullptr;
59
60 // Our subclass should have invoked DetachFromNSWindow already.
61 NS_ASSERTION(
62 !mWnd,
63 "TaskbarPreview::DetachFromNSWindow was not called before destruction");
64
65 // Make sure to release before potentially uninitializing COM
66 mTaskbar = nullptr;
67
68 ::CoUninitialize();
69 }
70
71 NS_IMETHODIMP
SetController(nsITaskbarPreviewController * aController)72 TaskbarPreview::SetController(nsITaskbarPreviewController *aController) {
73 NS_ENSURE_ARG(aController);
74
75 mController = aController;
76 return NS_OK;
77 }
78
79 NS_IMETHODIMP
GetController(nsITaskbarPreviewController ** aController)80 TaskbarPreview::GetController(nsITaskbarPreviewController **aController) {
81 NS_ADDREF(*aController = mController);
82 return NS_OK;
83 }
84
85 NS_IMETHODIMP
GetTooltip(nsAString & aTooltip)86 TaskbarPreview::GetTooltip(nsAString &aTooltip) {
87 aTooltip = mTooltip;
88 return NS_OK;
89 }
90
91 NS_IMETHODIMP
SetTooltip(const nsAString & aTooltip)92 TaskbarPreview::SetTooltip(const nsAString &aTooltip) {
93 mTooltip = aTooltip;
94 return CanMakeTaskbarCalls() ? UpdateTooltip() : NS_OK;
95 }
96
97 NS_IMETHODIMP
SetVisible(bool visible)98 TaskbarPreview::SetVisible(bool visible) {
99 if (mVisible == visible) return NS_OK;
100 mVisible = visible;
101
102 // If the nsWindow has already been destroyed but the caller is still trying
103 // to use it then just pretend that everything succeeded. The caller doesn't
104 // actually have a way to detect this since it's the same case as when we
105 // CanMakeTaskbarCalls returns false.
106 if (!mWnd) return NS_OK;
107
108 return visible ? Enable() : Disable();
109 }
110
111 NS_IMETHODIMP
GetVisible(bool * visible)112 TaskbarPreview::GetVisible(bool *visible) {
113 *visible = mVisible;
114 return NS_OK;
115 }
116
117 NS_IMETHODIMP
SetActive(bool active)118 TaskbarPreview::SetActive(bool active) {
119 if (active)
120 sActivePreview = this;
121 else if (sActivePreview == this)
122 sActivePreview = nullptr;
123
124 return CanMakeTaskbarCalls() ? ShowActive(active) : NS_OK;
125 }
126
127 NS_IMETHODIMP
GetActive(bool * active)128 TaskbarPreview::GetActive(bool *active) {
129 *active = sActivePreview == this;
130 return NS_OK;
131 }
132
133 NS_IMETHODIMP
Invalidate()134 TaskbarPreview::Invalidate() {
135 if (!mVisible) return NS_OK;
136
137 // DWM Composition is required for previews
138 if (!nsUXThemeData::CheckForCompositor()) return NS_OK;
139
140 HWND previewWindow = PreviewWindow();
141 return FAILED(DwmInvalidateIconicBitmaps(previewWindow)) ? NS_ERROR_FAILURE
142 : NS_OK;
143 }
144
UpdateTaskbarProperties()145 nsresult TaskbarPreview::UpdateTaskbarProperties() {
146 nsresult rv = UpdateTooltip();
147
148 // If we are the active preview and our window is the active window, restore
149 // our active state - otherwise some other non-preview window is now active
150 // and should be displayed as so.
151 if (sActivePreview == this) {
152 if (mWnd == ::GetActiveWindow()) {
153 nsresult rvActive = ShowActive(true);
154 if (NS_FAILED(rvActive)) rv = rvActive;
155 } else {
156 sActivePreview = nullptr;
157 }
158 }
159 return rv;
160 }
161
Enable()162 nsresult TaskbarPreview::Enable() {
163 nsresult rv = NS_OK;
164 if (CanMakeTaskbarCalls()) {
165 rv = UpdateTaskbarProperties();
166 } else {
167 WindowHook &hook = GetWindowHook();
168 hook.AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
169 MainWindowHook, this);
170 }
171 return rv;
172 }
173
Disable()174 nsresult TaskbarPreview::Disable() {
175 if (!IsWindowAvailable()) {
176 // Window is already destroyed
177 return NS_OK;
178 }
179
180 WindowHook &hook = GetWindowHook();
181 (void)hook.RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
182 MainWindowHook, this);
183
184 return NS_OK;
185 }
186
IsWindowAvailable() const187 bool TaskbarPreview::IsWindowAvailable() const {
188 if (mWnd) {
189 nsWindow *win = WinUtils::GetNSWindowPtr(mWnd);
190 if (win && !win->Destroyed()) {
191 return true;
192 }
193 }
194 return false;
195 }
196
DetachFromNSWindow()197 void TaskbarPreview::DetachFromNSWindow() {
198 WindowHook &hook = GetWindowHook();
199 hook.RemoveMonitor(WM_DESTROY, MainWindowHook, this);
200 mWnd = nullptr;
201 }
202
203 LRESULT
WndProc(UINT nMsg,WPARAM wParam,LPARAM lParam)204 TaskbarPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) {
205 switch (nMsg) {
206 case WM_DWMSENDICONICTHUMBNAIL: {
207 uint32_t width = HIWORD(lParam);
208 uint32_t height = LOWORD(lParam);
209 float aspectRatio = width / float(height);
210
211 nsresult rv;
212 float preferredAspectRatio;
213 rv = mController->GetThumbnailAspectRatio(&preferredAspectRatio);
214 if (NS_FAILED(rv)) break;
215
216 uint32_t thumbnailWidth = width;
217 uint32_t thumbnailHeight = height;
218
219 if (aspectRatio > preferredAspectRatio) {
220 thumbnailWidth = uint32_t(thumbnailHeight * preferredAspectRatio);
221 } else {
222 thumbnailHeight = uint32_t(thumbnailWidth / preferredAspectRatio);
223 }
224
225 DrawBitmap(thumbnailWidth, thumbnailHeight, false);
226 } break;
227 case WM_DWMSENDICONICLIVEPREVIEWBITMAP: {
228 uint32_t width, height;
229 nsresult rv;
230 rv = mController->GetWidth(&width);
231 if (NS_FAILED(rv)) break;
232 rv = mController->GetHeight(&height);
233 if (NS_FAILED(rv)) break;
234
235 double scale = nsIWidget::DefaultScaleOverride();
236 if (scale <= 0.0) {
237 scale = WinUtils::LogToPhysFactor(PreviewWindow());
238 }
239 DrawBitmap(NSToIntRound(scale * width), NSToIntRound(scale * height),
240 true);
241 } break;
242 }
243 return ::DefWindowProcW(PreviewWindow(), nMsg, wParam, lParam);
244 }
245
CanMakeTaskbarCalls()246 bool TaskbarPreview::CanMakeTaskbarCalls() {
247 // If the nsWindow has already been destroyed and we know it but our caller
248 // clearly doesn't so we can't make any calls.
249 if (!mWnd) return false;
250 // Certain functions like SetTabOrder seem to require a visible window. During
251 // window close, the window seems to be hidden before being destroyed.
252 if (!::IsWindowVisible(mWnd)) return false;
253 if (mVisible) {
254 nsWindow *window = WinUtils::GetNSWindowPtr(mWnd);
255 NS_ASSERTION(window, "Could not get nsWindow from HWND");
256 return window->HasTaskbarIconBeenCreated();
257 }
258 return false;
259 }
260
GetWindowHook()261 WindowHook &TaskbarPreview::GetWindowHook() {
262 nsWindow *window = WinUtils::GetNSWindowPtr(mWnd);
263 NS_ASSERTION(window, "Cannot use taskbar previews in an embedded context!");
264
265 return window->GetWindowHook();
266 }
267
EnableCustomDrawing(HWND aHWND,bool aEnable)268 void TaskbarPreview::EnableCustomDrawing(HWND aHWND, bool aEnable) {
269 BOOL enabled = aEnable;
270 DwmSetWindowAttribute(aHWND, DWMWA_FORCE_ICONIC_REPRESENTATION, &enabled,
271 sizeof(enabled));
272
273 DwmSetWindowAttribute(aHWND, DWMWA_HAS_ICONIC_BITMAP, &enabled,
274 sizeof(enabled));
275 }
276
UpdateTooltip()277 nsresult TaskbarPreview::UpdateTooltip() {
278 NS_ASSERTION(CanMakeTaskbarCalls() && mVisible,
279 "UpdateTooltip called on invisible tab preview");
280
281 if (FAILED(mTaskbar->SetThumbnailTooltip(PreviewWindow(), mTooltip.get())))
282 return NS_ERROR_FAILURE;
283 return NS_OK;
284 }
285
DrawBitmap(uint32_t width,uint32_t height,bool isPreview)286 void TaskbarPreview::DrawBitmap(uint32_t width, uint32_t height,
287 bool isPreview) {
288 nsresult rv;
289 nsCOMPtr<nsITaskbarPreviewCallback> callback =
290 do_CreateInstance("@mozilla.org/widget/taskbar-preview-callback;1", &rv);
291 if (NS_FAILED(rv)) {
292 return;
293 }
294
295 ((TaskbarPreviewCallback *)callback.get())->SetPreview(this);
296
297 if (isPreview) {
298 ((TaskbarPreviewCallback *)callback.get())->SetIsPreview();
299 mController->RequestPreview(callback);
300 } else {
301 mController->RequestThumbnail(callback, width, height);
302 }
303 }
304
305 ///////////////////////////////////////////////////////////////////////////////
306 // TaskbarPreviewCallback
307
NS_IMPL_ISUPPORTS(TaskbarPreviewCallback,nsITaskbarPreviewCallback)308 NS_IMPL_ISUPPORTS(TaskbarPreviewCallback, nsITaskbarPreviewCallback)
309
310 /* void done (in nsISupports aCanvas, in boolean aDrawBorder); */
311 NS_IMETHODIMP
312 TaskbarPreviewCallback::Done(nsISupports *aCanvas, bool aDrawBorder) {
313 // We create and destroy TaskbarTabPreviews from front end code in response
314 // to TabOpen and TabClose events. Each TaskbarTabPreview creates and owns a
315 // proxy HWND which it hands to Windows as a tab identifier. When a tab
316 // closes, TaskbarTabPreview Disable() method is called by front end, which
317 // destroys the proxy window and clears mProxyWindow which is the HWND
318 // returned from PreviewWindow(). So, since this is async, we should check to
319 // be sure the tab is still alive before doing all this gfx work and making
320 // dwm calls. To accomplish this we check the result of PreviewWindow().
321 if (!aCanvas || !mPreview || !mPreview->PreviewWindow() ||
322 !mPreview->IsWindowAvailable()) {
323 return NS_ERROR_FAILURE;
324 }
325
326 nsCOMPtr<nsIContent> content(do_QueryInterface(aCanvas));
327 auto canvas = dom::HTMLCanvasElement::FromContentOrNull(content);
328 if (!canvas) {
329 return NS_ERROR_FAILURE;
330 }
331
332 RefPtr<gfx::SourceSurface> source = canvas->GetSurfaceSnapshot();
333 if (!source) {
334 return NS_ERROR_FAILURE;
335 }
336 RefPtr<gfxWindowsSurface> target = new gfxWindowsSurface(
337 source->GetSize(), gfx::SurfaceFormat::A8R8G8B8_UINT32);
338 if (!target) {
339 return NS_ERROR_FAILURE;
340 }
341
342 RefPtr<gfx::DataSourceSurface> srcSurface = source->GetDataSurface();
343 RefPtr<gfxImageSurface> imageSurface = target->GetAsImageSurface();
344 if (!srcSurface || !imageSurface) {
345 return NS_ERROR_FAILURE;
346 }
347
348 gfx::DataSourceSurface::MappedSurface sourceMap;
349 srcSurface->Map(gfx::DataSourceSurface::READ, &sourceMap);
350 mozilla::gfx::CopySurfaceDataToPackedArray(
351 sourceMap.mData, imageSurface->Data(), srcSurface->GetSize(),
352 sourceMap.mStride, BytesPerPixel(srcSurface->GetFormat()));
353 srcSurface->Unmap();
354
355 HDC hDC = target->GetDC();
356 HBITMAP hBitmap = (HBITMAP)GetCurrentObject(hDC, OBJ_BITMAP);
357
358 DWORD flags = aDrawBorder ? DWM_SIT_DISPLAYFRAME : 0;
359 POINT pptClient = {0, 0};
360 HRESULT hr;
361 if (!mIsThumbnail) {
362 hr = DwmSetIconicLivePreviewBitmap(mPreview->PreviewWindow(), hBitmap,
363 &pptClient, flags);
364 } else {
365 hr = DwmSetIconicThumbnail(mPreview->PreviewWindow(), hBitmap, flags);
366 }
367 MOZ_ASSERT(SUCCEEDED(hr));
368 return NS_OK;
369 }
370
371 /* static */
MainWindowHook(void * aContext,HWND hWnd,UINT nMsg,WPARAM wParam,LPARAM lParam,LRESULT * aResult)372 bool TaskbarPreview::MainWindowHook(void *aContext, HWND hWnd, UINT nMsg,
373 WPARAM wParam, LPARAM lParam,
374 LRESULT *aResult) {
375 NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage() ||
376 nMsg == WM_DESTROY,
377 "Window hook proc called with wrong message");
378 NS_ASSERTION(aContext, "Null context in MainWindowHook");
379 if (!aContext) return false;
380 TaskbarPreview *preview = reinterpret_cast<TaskbarPreview *>(aContext);
381 if (nMsg == WM_DESTROY) {
382 // nsWindow is being destroyed
383 // We can't really do anything at this point including removing hooks
384 return false;
385 } else {
386 nsWindow *window = WinUtils::GetNSWindowPtr(preview->mWnd);
387 if (window) {
388 window->SetHasTaskbarIconBeenCreated();
389
390 if (preview->mVisible) preview->UpdateTaskbarProperties();
391 }
392 }
393 return false;
394 }
395
396 TaskbarPreview *TaskbarPreview::sActivePreview = nullptr;
397
398 } // namespace widget
399 } // namespace mozilla
400