|
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 #include "gfxWindowsPlatform.h" |
|
25 |
|
26 #include <nsIBaseWindow.h> |
|
27 #include <nsICanvasRenderingContextInternal.h> |
|
28 #include "mozilla/dom/CanvasRenderingContext2D.h" |
|
29 #include <imgIContainer.h> |
|
30 #include <nsIDocShell.h> |
|
31 |
|
32 #include "mozilla/Telemetry.h" |
|
33 |
|
34 // Defined in dwmapi in a header that needs a higher numbered _WINNT #define |
|
35 #define DWM_SIT_DISPLAYFRAME 0x1 |
|
36 |
|
37 namespace mozilla { |
|
38 namespace widget { |
|
39 |
|
40 namespace { |
|
41 |
|
42 // Shared by all TaskbarPreviews to avoid the expensive creation process. |
|
43 // Manually refcounted (see gInstCount) by the ctor and dtor of TaskbarPreview. |
|
44 // This is done because static constructors aren't allowed for perf reasons. |
|
45 dom::CanvasRenderingContext2D* gCtx = nullptr; |
|
46 // Used in tracking the number of previews. Used in freeing |
|
47 // the static 2d rendering context on shutdown. |
|
48 uint32_t gInstCount = 0; |
|
49 |
|
50 /* Helper method to lazily create a canvas rendering context and associate a given |
|
51 * surface with it. |
|
52 * |
|
53 * @param shell The docShell used by the canvas context for text settings and other |
|
54 * misc things. |
|
55 * @param surface The gfxSurface backing the context |
|
56 * @param width The width of the given surface |
|
57 * @param height The height of the given surface |
|
58 */ |
|
59 nsresult |
|
60 GetRenderingContext(nsIDocShell *shell, gfxASurface *surface, |
|
61 uint32_t width, uint32_t height) { |
|
62 if (!gCtx) { |
|
63 // create the canvas rendering context |
|
64 Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1); |
|
65 gCtx = new mozilla::dom::CanvasRenderingContext2D(); |
|
66 NS_ADDREF(gCtx); |
|
67 } |
|
68 |
|
69 // Set the surface we'll use to render. |
|
70 return gCtx->InitializeWithSurface(shell, surface, width, height); |
|
71 } |
|
72 |
|
73 /* Helper method for freeing surface resources associated with the rendering context. |
|
74 */ |
|
75 void |
|
76 ResetRenderingContext() { |
|
77 if (!gCtx) |
|
78 return; |
|
79 |
|
80 if (NS_FAILED(gCtx->Reset())) { |
|
81 NS_RELEASE(gCtx); |
|
82 gCtx = nullptr; |
|
83 } |
|
84 } |
|
85 |
|
86 } |
|
87 |
|
88 TaskbarPreview::TaskbarPreview(ITaskbarList4 *aTaskbar, nsITaskbarPreviewController *aController, HWND aHWND, nsIDocShell *aShell) |
|
89 : mTaskbar(aTaskbar), |
|
90 mController(aController), |
|
91 mWnd(aHWND), |
|
92 mVisible(false), |
|
93 mDocShell(do_GetWeakReference(aShell)) |
|
94 { |
|
95 // TaskbarPreview may outlive the WinTaskbar that created it |
|
96 ::CoInitialize(nullptr); |
|
97 |
|
98 gInstCount++; |
|
99 |
|
100 WindowHook &hook = GetWindowHook(); |
|
101 hook.AddMonitor(WM_DESTROY, MainWindowHook, this); |
|
102 } |
|
103 |
|
104 TaskbarPreview::~TaskbarPreview() { |
|
105 // Avoid dangling pointer |
|
106 if (sActivePreview == this) |
|
107 sActivePreview = nullptr; |
|
108 |
|
109 // Our subclass should have invoked DetachFromNSWindow already. |
|
110 NS_ASSERTION(!mWnd, "TaskbarPreview::DetachFromNSWindow was not called before destruction"); |
|
111 |
|
112 // Make sure to release before potentially uninitializing COM |
|
113 mTaskbar = nullptr; |
|
114 |
|
115 if (--gInstCount == 0) |
|
116 NS_IF_RELEASE(gCtx); |
|
117 |
|
118 ::CoUninitialize(); |
|
119 } |
|
120 |
|
121 NS_IMETHODIMP |
|
122 TaskbarPreview::SetController(nsITaskbarPreviewController *aController) { |
|
123 NS_ENSURE_ARG(aController); |
|
124 |
|
125 mController = aController; |
|
126 return NS_OK; |
|
127 } |
|
128 |
|
129 NS_IMETHODIMP |
|
130 TaskbarPreview::GetController(nsITaskbarPreviewController **aController) { |
|
131 NS_ADDREF(*aController = mController); |
|
132 return NS_OK; |
|
133 } |
|
134 |
|
135 NS_IMETHODIMP |
|
136 TaskbarPreview::GetTooltip(nsAString &aTooltip) { |
|
137 aTooltip = mTooltip; |
|
138 return NS_OK; |
|
139 } |
|
140 |
|
141 NS_IMETHODIMP |
|
142 TaskbarPreview::SetTooltip(const nsAString &aTooltip) { |
|
143 mTooltip = aTooltip; |
|
144 return CanMakeTaskbarCalls() ? UpdateTooltip() : NS_OK; |
|
145 } |
|
146 |
|
147 NS_IMETHODIMP |
|
148 TaskbarPreview::SetVisible(bool visible) { |
|
149 if (mVisible == visible) return NS_OK; |
|
150 mVisible = visible; |
|
151 |
|
152 // If the nsWindow has already been destroyed but the caller is still trying |
|
153 // to use it then just pretend that everything succeeded. The caller doesn't |
|
154 // actually have a way to detect this since it's the same case as when we |
|
155 // CanMakeTaskbarCalls returns false. |
|
156 if (!mWnd) |
|
157 return NS_OK; |
|
158 |
|
159 return visible ? Enable() : Disable(); |
|
160 } |
|
161 |
|
162 NS_IMETHODIMP |
|
163 TaskbarPreview::GetVisible(bool *visible) { |
|
164 *visible = mVisible; |
|
165 return NS_OK; |
|
166 } |
|
167 |
|
168 NS_IMETHODIMP |
|
169 TaskbarPreview::SetActive(bool active) { |
|
170 if (active) |
|
171 sActivePreview = this; |
|
172 else if (sActivePreview == this) |
|
173 sActivePreview = nullptr; |
|
174 |
|
175 return CanMakeTaskbarCalls() ? ShowActive(active) : NS_OK; |
|
176 } |
|
177 |
|
178 NS_IMETHODIMP |
|
179 TaskbarPreview::GetActive(bool *active) { |
|
180 *active = sActivePreview == this; |
|
181 return NS_OK; |
|
182 } |
|
183 |
|
184 NS_IMETHODIMP |
|
185 TaskbarPreview::Invalidate() { |
|
186 if (!mVisible) |
|
187 return NS_ERROR_FAILURE; |
|
188 |
|
189 // DWM Composition is required for previews |
|
190 if (!nsUXThemeData::CheckForCompositor()) |
|
191 return NS_OK; |
|
192 |
|
193 HWND previewWindow = PreviewWindow(); |
|
194 return FAILED(WinUtils::dwmInvalidateIconicBitmapsPtr(previewWindow)) |
|
195 ? NS_ERROR_FAILURE |
|
196 : NS_OK; |
|
197 } |
|
198 |
|
199 nsresult |
|
200 TaskbarPreview::UpdateTaskbarProperties() { |
|
201 nsresult rv = UpdateTooltip(); |
|
202 |
|
203 // If we are the active preview and our window is the active window, restore |
|
204 // our active state - otherwise some other non-preview window is now active |
|
205 // and should be displayed as so. |
|
206 if (sActivePreview == this) { |
|
207 if (mWnd == ::GetActiveWindow()) { |
|
208 nsresult rvActive = ShowActive(true); |
|
209 if (NS_FAILED(rvActive)) |
|
210 rv = rvActive; |
|
211 } else { |
|
212 sActivePreview = nullptr; |
|
213 } |
|
214 } |
|
215 return rv; |
|
216 } |
|
217 |
|
218 nsresult |
|
219 TaskbarPreview::Enable() { |
|
220 nsresult rv = NS_OK; |
|
221 if (CanMakeTaskbarCalls()) { |
|
222 rv = UpdateTaskbarProperties(); |
|
223 } else { |
|
224 WindowHook &hook = GetWindowHook(); |
|
225 hook.AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), MainWindowHook, this); |
|
226 } |
|
227 return rv; |
|
228 } |
|
229 |
|
230 nsresult |
|
231 TaskbarPreview::Disable() { |
|
232 WindowHook &hook = GetWindowHook(); |
|
233 (void) hook.RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), MainWindowHook, this); |
|
234 |
|
235 return NS_OK; |
|
236 } |
|
237 |
|
238 bool |
|
239 TaskbarPreview::IsWindowAvailable() const { |
|
240 if (mWnd) { |
|
241 nsWindow* win = WinUtils::GetNSWindowPtr(mWnd); |
|
242 if(win && !win->Destroyed()) { |
|
243 return true; |
|
244 } |
|
245 } |
|
246 return false; |
|
247 } |
|
248 |
|
249 void |
|
250 TaskbarPreview::DetachFromNSWindow() { |
|
251 WindowHook &hook = GetWindowHook(); |
|
252 hook.RemoveMonitor(WM_DESTROY, MainWindowHook, this); |
|
253 mWnd = nullptr; |
|
254 } |
|
255 |
|
256 LRESULT |
|
257 TaskbarPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) { |
|
258 switch (nMsg) { |
|
259 case WM_DWMSENDICONICTHUMBNAIL: |
|
260 { |
|
261 uint32_t width = HIWORD(lParam); |
|
262 uint32_t height = LOWORD(lParam); |
|
263 float aspectRatio = width/float(height); |
|
264 |
|
265 nsresult rv; |
|
266 float preferredAspectRatio; |
|
267 rv = mController->GetThumbnailAspectRatio(&preferredAspectRatio); |
|
268 if (NS_FAILED(rv)) |
|
269 break; |
|
270 |
|
271 uint32_t thumbnailWidth = width; |
|
272 uint32_t thumbnailHeight = height; |
|
273 |
|
274 if (aspectRatio > preferredAspectRatio) { |
|
275 thumbnailWidth = uint32_t(thumbnailHeight * preferredAspectRatio); |
|
276 } else { |
|
277 thumbnailHeight = uint32_t(thumbnailWidth / preferredAspectRatio); |
|
278 } |
|
279 |
|
280 DrawBitmap(thumbnailWidth, thumbnailHeight, false); |
|
281 } |
|
282 break; |
|
283 case WM_DWMSENDICONICLIVEPREVIEWBITMAP: |
|
284 { |
|
285 uint32_t width, height; |
|
286 nsresult rv; |
|
287 rv = mController->GetWidth(&width); |
|
288 if (NS_FAILED(rv)) |
|
289 break; |
|
290 rv = mController->GetHeight(&height); |
|
291 if (NS_FAILED(rv)) |
|
292 break; |
|
293 |
|
294 double scale = nsIWidget::DefaultScaleOverride(); |
|
295 if (scale <= 0.0) |
|
296 scale = gfxWindowsPlatform::GetPlatform()->GetDPIScale(); |
|
297 |
|
298 DrawBitmap(NSToIntRound(scale * width), NSToIntRound(scale * height), true); |
|
299 } |
|
300 break; |
|
301 } |
|
302 return ::DefWindowProcW(PreviewWindow(), nMsg, wParam, lParam); |
|
303 } |
|
304 |
|
305 bool |
|
306 TaskbarPreview::CanMakeTaskbarCalls() { |
|
307 // If the nsWindow has already been destroyed and we know it but our caller |
|
308 // clearly doesn't so we can't make any calls. |
|
309 if (!mWnd) |
|
310 return false; |
|
311 // Certain functions like SetTabOrder seem to require a visible window. During |
|
312 // window close, the window seems to be hidden before being destroyed. |
|
313 if (!::IsWindowVisible(mWnd)) |
|
314 return false; |
|
315 if (mVisible) { |
|
316 nsWindow *window = WinUtils::GetNSWindowPtr(mWnd); |
|
317 NS_ASSERTION(window, "Could not get nsWindow from HWND"); |
|
318 return window->HasTaskbarIconBeenCreated(); |
|
319 } |
|
320 return false; |
|
321 } |
|
322 |
|
323 WindowHook& |
|
324 TaskbarPreview::GetWindowHook() { |
|
325 nsWindow *window = WinUtils::GetNSWindowPtr(mWnd); |
|
326 NS_ASSERTION(window, "Cannot use taskbar previews in an embedded context!"); |
|
327 |
|
328 return window->GetWindowHook(); |
|
329 } |
|
330 |
|
331 void |
|
332 TaskbarPreview::EnableCustomDrawing(HWND aHWND, bool aEnable) { |
|
333 BOOL enabled = aEnable; |
|
334 WinUtils::dwmSetWindowAttributePtr( |
|
335 aHWND, |
|
336 DWMWA_FORCE_ICONIC_REPRESENTATION, |
|
337 &enabled, |
|
338 sizeof(enabled)); |
|
339 |
|
340 WinUtils::dwmSetWindowAttributePtr( |
|
341 aHWND, |
|
342 DWMWA_HAS_ICONIC_BITMAP, |
|
343 &enabled, |
|
344 sizeof(enabled)); |
|
345 } |
|
346 |
|
347 |
|
348 nsresult |
|
349 TaskbarPreview::UpdateTooltip() { |
|
350 NS_ASSERTION(CanMakeTaskbarCalls() && mVisible, "UpdateTooltip called on invisible tab preview"); |
|
351 |
|
352 if (FAILED(mTaskbar->SetThumbnailTooltip(PreviewWindow(), mTooltip.get()))) |
|
353 return NS_ERROR_FAILURE; |
|
354 return NS_OK; |
|
355 } |
|
356 |
|
357 void |
|
358 TaskbarPreview::DrawBitmap(uint32_t width, uint32_t height, bool isPreview) { |
|
359 nsresult rv; |
|
360 nsRefPtr<gfxWindowsSurface> surface = new gfxWindowsSurface(gfxIntSize(width, height), gfxImageFormat::ARGB32); |
|
361 |
|
362 nsCOMPtr<nsIDocShell> shell = do_QueryReferent(mDocShell); |
|
363 |
|
364 if (!shell) |
|
365 return; |
|
366 |
|
367 rv = GetRenderingContext(shell, surface, width, height); |
|
368 if (NS_FAILED(rv)) |
|
369 return; |
|
370 |
|
371 bool drawFrame = false; |
|
372 if (isPreview) |
|
373 rv = mController->DrawPreview(gCtx, &drawFrame); |
|
374 else |
|
375 rv = mController->DrawThumbnail(gCtx, width, height, &drawFrame); |
|
376 |
|
377 if (NS_FAILED(rv)) |
|
378 return; |
|
379 |
|
380 HDC hDC = surface->GetDC(); |
|
381 HBITMAP hBitmap = (HBITMAP)GetCurrentObject(hDC, OBJ_BITMAP); |
|
382 |
|
383 DWORD flags = drawFrame ? DWM_SIT_DISPLAYFRAME : 0; |
|
384 POINT pptClient = { 0, 0 }; |
|
385 if (isPreview) |
|
386 WinUtils::dwmSetIconicLivePreviewBitmapPtr(PreviewWindow(), hBitmap, &pptClient, flags); |
|
387 else |
|
388 WinUtils::dwmSetIconicThumbnailPtr(PreviewWindow(), hBitmap, flags); |
|
389 |
|
390 ResetRenderingContext(); |
|
391 } |
|
392 |
|
393 /* static */ |
|
394 bool |
|
395 TaskbarPreview::MainWindowHook(void *aContext, |
|
396 HWND hWnd, UINT nMsg, |
|
397 WPARAM wParam, LPARAM lParam, |
|
398 LRESULT *aResult) |
|
399 { |
|
400 NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage() || |
|
401 nMsg == WM_DESTROY, |
|
402 "Window hook proc called with wrong message"); |
|
403 NS_ASSERTION(aContext, "Null context in MainWindowHook"); |
|
404 if (!aContext) |
|
405 return false; |
|
406 TaskbarPreview *preview = reinterpret_cast<TaskbarPreview*>(aContext); |
|
407 if (nMsg == WM_DESTROY) { |
|
408 // nsWindow is being destroyed |
|
409 // We can't really do anything at this point including removing hooks |
|
410 preview->mWnd = nullptr; |
|
411 } else { |
|
412 nsWindow *window = WinUtils::GetNSWindowPtr(preview->mWnd); |
|
413 if (window) { |
|
414 window->SetHasTaskbarIconBeenCreated(); |
|
415 |
|
416 if (preview->mVisible) |
|
417 preview->UpdateTaskbarProperties(); |
|
418 } |
|
419 } |
|
420 return false; |
|
421 } |
|
422 |
|
423 TaskbarPreview * |
|
424 TaskbarPreview::sActivePreview = nullptr; |
|
425 |
|
426 } // namespace widget |
|
427 } // namespace mozilla |
|
428 |