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