1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/windows/TaskbarPreview.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,428 @@ 1.4 +/* vim: se cin sw=2 ts=2 et : */ 1.5 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.6 + * 1.7 + * This Source Code Form is subject to the terms of the Mozilla Public 1.8 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.9 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.10 + 1.11 +#include "TaskbarPreview.h" 1.12 +#include <nsITaskbarPreviewController.h> 1.13 +#include <windows.h> 1.14 + 1.15 +#include <nsError.h> 1.16 +#include <nsCOMPtr.h> 1.17 +#include <nsIWidget.h> 1.18 +#include <nsIBaseWindow.h> 1.19 +#include <nsIObserverService.h> 1.20 +#include <nsServiceManagerUtils.h> 1.21 + 1.22 +#include "nsUXThemeData.h" 1.23 +#include "nsWindow.h" 1.24 +#include "nsAppShell.h" 1.25 +#include "TaskbarPreviewButton.h" 1.26 +#include "WinUtils.h" 1.27 +#include "gfxWindowsPlatform.h" 1.28 + 1.29 +#include <nsIBaseWindow.h> 1.30 +#include <nsICanvasRenderingContextInternal.h> 1.31 +#include "mozilla/dom/CanvasRenderingContext2D.h" 1.32 +#include <imgIContainer.h> 1.33 +#include <nsIDocShell.h> 1.34 + 1.35 +#include "mozilla/Telemetry.h" 1.36 + 1.37 +// Defined in dwmapi in a header that needs a higher numbered _WINNT #define 1.38 +#define DWM_SIT_DISPLAYFRAME 0x1 1.39 + 1.40 +namespace mozilla { 1.41 +namespace widget { 1.42 + 1.43 +namespace { 1.44 + 1.45 +// Shared by all TaskbarPreviews to avoid the expensive creation process. 1.46 +// Manually refcounted (see gInstCount) by the ctor and dtor of TaskbarPreview. 1.47 +// This is done because static constructors aren't allowed for perf reasons. 1.48 +dom::CanvasRenderingContext2D* gCtx = nullptr; 1.49 +// Used in tracking the number of previews. Used in freeing 1.50 +// the static 2d rendering context on shutdown. 1.51 +uint32_t gInstCount = 0; 1.52 + 1.53 +/* Helper method to lazily create a canvas rendering context and associate a given 1.54 + * surface with it. 1.55 + * 1.56 + * @param shell The docShell used by the canvas context for text settings and other 1.57 + * misc things. 1.58 + * @param surface The gfxSurface backing the context 1.59 + * @param width The width of the given surface 1.60 + * @param height The height of the given surface 1.61 + */ 1.62 +nsresult 1.63 +GetRenderingContext(nsIDocShell *shell, gfxASurface *surface, 1.64 + uint32_t width, uint32_t height) { 1.65 + if (!gCtx) { 1.66 + // create the canvas rendering context 1.67 + Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1); 1.68 + gCtx = new mozilla::dom::CanvasRenderingContext2D(); 1.69 + NS_ADDREF(gCtx); 1.70 + } 1.71 + 1.72 + // Set the surface we'll use to render. 1.73 + return gCtx->InitializeWithSurface(shell, surface, width, height); 1.74 +} 1.75 + 1.76 +/* Helper method for freeing surface resources associated with the rendering context. 1.77 + */ 1.78 +void 1.79 +ResetRenderingContext() { 1.80 + if (!gCtx) 1.81 + return; 1.82 + 1.83 + if (NS_FAILED(gCtx->Reset())) { 1.84 + NS_RELEASE(gCtx); 1.85 + gCtx = nullptr; 1.86 + } 1.87 +} 1.88 + 1.89 +} 1.90 + 1.91 +TaskbarPreview::TaskbarPreview(ITaskbarList4 *aTaskbar, nsITaskbarPreviewController *aController, HWND aHWND, nsIDocShell *aShell) 1.92 + : mTaskbar(aTaskbar), 1.93 + mController(aController), 1.94 + mWnd(aHWND), 1.95 + mVisible(false), 1.96 + mDocShell(do_GetWeakReference(aShell)) 1.97 +{ 1.98 + // TaskbarPreview may outlive the WinTaskbar that created it 1.99 + ::CoInitialize(nullptr); 1.100 + 1.101 + gInstCount++; 1.102 + 1.103 + WindowHook &hook = GetWindowHook(); 1.104 + hook.AddMonitor(WM_DESTROY, MainWindowHook, this); 1.105 +} 1.106 + 1.107 +TaskbarPreview::~TaskbarPreview() { 1.108 + // Avoid dangling pointer 1.109 + if (sActivePreview == this) 1.110 + sActivePreview = nullptr; 1.111 + 1.112 + // Our subclass should have invoked DetachFromNSWindow already. 1.113 + NS_ASSERTION(!mWnd, "TaskbarPreview::DetachFromNSWindow was not called before destruction"); 1.114 + 1.115 + // Make sure to release before potentially uninitializing COM 1.116 + mTaskbar = nullptr; 1.117 + 1.118 + if (--gInstCount == 0) 1.119 + NS_IF_RELEASE(gCtx); 1.120 + 1.121 + ::CoUninitialize(); 1.122 +} 1.123 + 1.124 +NS_IMETHODIMP 1.125 +TaskbarPreview::SetController(nsITaskbarPreviewController *aController) { 1.126 + NS_ENSURE_ARG(aController); 1.127 + 1.128 + mController = aController; 1.129 + return NS_OK; 1.130 +} 1.131 + 1.132 +NS_IMETHODIMP 1.133 +TaskbarPreview::GetController(nsITaskbarPreviewController **aController) { 1.134 + NS_ADDREF(*aController = mController); 1.135 + return NS_OK; 1.136 +} 1.137 + 1.138 +NS_IMETHODIMP 1.139 +TaskbarPreview::GetTooltip(nsAString &aTooltip) { 1.140 + aTooltip = mTooltip; 1.141 + return NS_OK; 1.142 +} 1.143 + 1.144 +NS_IMETHODIMP 1.145 +TaskbarPreview::SetTooltip(const nsAString &aTooltip) { 1.146 + mTooltip = aTooltip; 1.147 + return CanMakeTaskbarCalls() ? UpdateTooltip() : NS_OK; 1.148 +} 1.149 + 1.150 +NS_IMETHODIMP 1.151 +TaskbarPreview::SetVisible(bool visible) { 1.152 + if (mVisible == visible) return NS_OK; 1.153 + mVisible = visible; 1.154 + 1.155 + // If the nsWindow has already been destroyed but the caller is still trying 1.156 + // to use it then just pretend that everything succeeded. The caller doesn't 1.157 + // actually have a way to detect this since it's the same case as when we 1.158 + // CanMakeTaskbarCalls returns false. 1.159 + if (!mWnd) 1.160 + return NS_OK; 1.161 + 1.162 + return visible ? Enable() : Disable(); 1.163 +} 1.164 + 1.165 +NS_IMETHODIMP 1.166 +TaskbarPreview::GetVisible(bool *visible) { 1.167 + *visible = mVisible; 1.168 + return NS_OK; 1.169 +} 1.170 + 1.171 +NS_IMETHODIMP 1.172 +TaskbarPreview::SetActive(bool active) { 1.173 + if (active) 1.174 + sActivePreview = this; 1.175 + else if (sActivePreview == this) 1.176 + sActivePreview = nullptr; 1.177 + 1.178 + return CanMakeTaskbarCalls() ? ShowActive(active) : NS_OK; 1.179 +} 1.180 + 1.181 +NS_IMETHODIMP 1.182 +TaskbarPreview::GetActive(bool *active) { 1.183 + *active = sActivePreview == this; 1.184 + return NS_OK; 1.185 +} 1.186 + 1.187 +NS_IMETHODIMP 1.188 +TaskbarPreview::Invalidate() { 1.189 + if (!mVisible) 1.190 + return NS_ERROR_FAILURE; 1.191 + 1.192 + // DWM Composition is required for previews 1.193 + if (!nsUXThemeData::CheckForCompositor()) 1.194 + return NS_OK; 1.195 + 1.196 + HWND previewWindow = PreviewWindow(); 1.197 + return FAILED(WinUtils::dwmInvalidateIconicBitmapsPtr(previewWindow)) 1.198 + ? NS_ERROR_FAILURE 1.199 + : NS_OK; 1.200 +} 1.201 + 1.202 +nsresult 1.203 +TaskbarPreview::UpdateTaskbarProperties() { 1.204 + nsresult rv = UpdateTooltip(); 1.205 + 1.206 + // If we are the active preview and our window is the active window, restore 1.207 + // our active state - otherwise some other non-preview window is now active 1.208 + // and should be displayed as so. 1.209 + if (sActivePreview == this) { 1.210 + if (mWnd == ::GetActiveWindow()) { 1.211 + nsresult rvActive = ShowActive(true); 1.212 + if (NS_FAILED(rvActive)) 1.213 + rv = rvActive; 1.214 + } else { 1.215 + sActivePreview = nullptr; 1.216 + } 1.217 + } 1.218 + return rv; 1.219 +} 1.220 + 1.221 +nsresult 1.222 +TaskbarPreview::Enable() { 1.223 + nsresult rv = NS_OK; 1.224 + if (CanMakeTaskbarCalls()) { 1.225 + rv = UpdateTaskbarProperties(); 1.226 + } else { 1.227 + WindowHook &hook = GetWindowHook(); 1.228 + hook.AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), MainWindowHook, this); 1.229 + } 1.230 + return rv; 1.231 +} 1.232 + 1.233 +nsresult 1.234 +TaskbarPreview::Disable() { 1.235 + WindowHook &hook = GetWindowHook(); 1.236 + (void) hook.RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), MainWindowHook, this); 1.237 + 1.238 + return NS_OK; 1.239 +} 1.240 + 1.241 +bool 1.242 +TaskbarPreview::IsWindowAvailable() const { 1.243 + if (mWnd) { 1.244 + nsWindow* win = WinUtils::GetNSWindowPtr(mWnd); 1.245 + if(win && !win->Destroyed()) { 1.246 + return true; 1.247 + } 1.248 + } 1.249 + return false; 1.250 +} 1.251 + 1.252 +void 1.253 +TaskbarPreview::DetachFromNSWindow() { 1.254 + WindowHook &hook = GetWindowHook(); 1.255 + hook.RemoveMonitor(WM_DESTROY, MainWindowHook, this); 1.256 + mWnd = nullptr; 1.257 +} 1.258 + 1.259 +LRESULT 1.260 +TaskbarPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) { 1.261 + switch (nMsg) { 1.262 + case WM_DWMSENDICONICTHUMBNAIL: 1.263 + { 1.264 + uint32_t width = HIWORD(lParam); 1.265 + uint32_t height = LOWORD(lParam); 1.266 + float aspectRatio = width/float(height); 1.267 + 1.268 + nsresult rv; 1.269 + float preferredAspectRatio; 1.270 + rv = mController->GetThumbnailAspectRatio(&preferredAspectRatio); 1.271 + if (NS_FAILED(rv)) 1.272 + break; 1.273 + 1.274 + uint32_t thumbnailWidth = width; 1.275 + uint32_t thumbnailHeight = height; 1.276 + 1.277 + if (aspectRatio > preferredAspectRatio) { 1.278 + thumbnailWidth = uint32_t(thumbnailHeight * preferredAspectRatio); 1.279 + } else { 1.280 + thumbnailHeight = uint32_t(thumbnailWidth / preferredAspectRatio); 1.281 + } 1.282 + 1.283 + DrawBitmap(thumbnailWidth, thumbnailHeight, false); 1.284 + } 1.285 + break; 1.286 + case WM_DWMSENDICONICLIVEPREVIEWBITMAP: 1.287 + { 1.288 + uint32_t width, height; 1.289 + nsresult rv; 1.290 + rv = mController->GetWidth(&width); 1.291 + if (NS_FAILED(rv)) 1.292 + break; 1.293 + rv = mController->GetHeight(&height); 1.294 + if (NS_FAILED(rv)) 1.295 + break; 1.296 + 1.297 + double scale = nsIWidget::DefaultScaleOverride(); 1.298 + if (scale <= 0.0) 1.299 + scale = gfxWindowsPlatform::GetPlatform()->GetDPIScale(); 1.300 + 1.301 + DrawBitmap(NSToIntRound(scale * width), NSToIntRound(scale * height), true); 1.302 + } 1.303 + break; 1.304 + } 1.305 + return ::DefWindowProcW(PreviewWindow(), nMsg, wParam, lParam); 1.306 +} 1.307 + 1.308 +bool 1.309 +TaskbarPreview::CanMakeTaskbarCalls() { 1.310 + // If the nsWindow has already been destroyed and we know it but our caller 1.311 + // clearly doesn't so we can't make any calls. 1.312 + if (!mWnd) 1.313 + return false; 1.314 + // Certain functions like SetTabOrder seem to require a visible window. During 1.315 + // window close, the window seems to be hidden before being destroyed. 1.316 + if (!::IsWindowVisible(mWnd)) 1.317 + return false; 1.318 + if (mVisible) { 1.319 + nsWindow *window = WinUtils::GetNSWindowPtr(mWnd); 1.320 + NS_ASSERTION(window, "Could not get nsWindow from HWND"); 1.321 + return window->HasTaskbarIconBeenCreated(); 1.322 + } 1.323 + return false; 1.324 +} 1.325 + 1.326 +WindowHook& 1.327 +TaskbarPreview::GetWindowHook() { 1.328 + nsWindow *window = WinUtils::GetNSWindowPtr(mWnd); 1.329 + NS_ASSERTION(window, "Cannot use taskbar previews in an embedded context!"); 1.330 + 1.331 + return window->GetWindowHook(); 1.332 +} 1.333 + 1.334 +void 1.335 +TaskbarPreview::EnableCustomDrawing(HWND aHWND, bool aEnable) { 1.336 + BOOL enabled = aEnable; 1.337 + WinUtils::dwmSetWindowAttributePtr( 1.338 + aHWND, 1.339 + DWMWA_FORCE_ICONIC_REPRESENTATION, 1.340 + &enabled, 1.341 + sizeof(enabled)); 1.342 + 1.343 + WinUtils::dwmSetWindowAttributePtr( 1.344 + aHWND, 1.345 + DWMWA_HAS_ICONIC_BITMAP, 1.346 + &enabled, 1.347 + sizeof(enabled)); 1.348 +} 1.349 + 1.350 + 1.351 +nsresult 1.352 +TaskbarPreview::UpdateTooltip() { 1.353 + NS_ASSERTION(CanMakeTaskbarCalls() && mVisible, "UpdateTooltip called on invisible tab preview"); 1.354 + 1.355 + if (FAILED(mTaskbar->SetThumbnailTooltip(PreviewWindow(), mTooltip.get()))) 1.356 + return NS_ERROR_FAILURE; 1.357 + return NS_OK; 1.358 +} 1.359 + 1.360 +void 1.361 +TaskbarPreview::DrawBitmap(uint32_t width, uint32_t height, bool isPreview) { 1.362 + nsresult rv; 1.363 + nsRefPtr<gfxWindowsSurface> surface = new gfxWindowsSurface(gfxIntSize(width, height), gfxImageFormat::ARGB32); 1.364 + 1.365 + nsCOMPtr<nsIDocShell> shell = do_QueryReferent(mDocShell); 1.366 + 1.367 + if (!shell) 1.368 + return; 1.369 + 1.370 + rv = GetRenderingContext(shell, surface, width, height); 1.371 + if (NS_FAILED(rv)) 1.372 + return; 1.373 + 1.374 + bool drawFrame = false; 1.375 + if (isPreview) 1.376 + rv = mController->DrawPreview(gCtx, &drawFrame); 1.377 + else 1.378 + rv = mController->DrawThumbnail(gCtx, width, height, &drawFrame); 1.379 + 1.380 + if (NS_FAILED(rv)) 1.381 + return; 1.382 + 1.383 + HDC hDC = surface->GetDC(); 1.384 + HBITMAP hBitmap = (HBITMAP)GetCurrentObject(hDC, OBJ_BITMAP); 1.385 + 1.386 + DWORD flags = drawFrame ? DWM_SIT_DISPLAYFRAME : 0; 1.387 + POINT pptClient = { 0, 0 }; 1.388 + if (isPreview) 1.389 + WinUtils::dwmSetIconicLivePreviewBitmapPtr(PreviewWindow(), hBitmap, &pptClient, flags); 1.390 + else 1.391 + WinUtils::dwmSetIconicThumbnailPtr(PreviewWindow(), hBitmap, flags); 1.392 + 1.393 + ResetRenderingContext(); 1.394 +} 1.395 + 1.396 +/* static */ 1.397 +bool 1.398 +TaskbarPreview::MainWindowHook(void *aContext, 1.399 + HWND hWnd, UINT nMsg, 1.400 + WPARAM wParam, LPARAM lParam, 1.401 + LRESULT *aResult) 1.402 +{ 1.403 + NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage() || 1.404 + nMsg == WM_DESTROY, 1.405 + "Window hook proc called with wrong message"); 1.406 + NS_ASSERTION(aContext, "Null context in MainWindowHook"); 1.407 + if (!aContext) 1.408 + return false; 1.409 + TaskbarPreview *preview = reinterpret_cast<TaskbarPreview*>(aContext); 1.410 + if (nMsg == WM_DESTROY) { 1.411 + // nsWindow is being destroyed 1.412 + // We can't really do anything at this point including removing hooks 1.413 + preview->mWnd = nullptr; 1.414 + } else { 1.415 + nsWindow *window = WinUtils::GetNSWindowPtr(preview->mWnd); 1.416 + if (window) { 1.417 + window->SetHasTaskbarIconBeenCreated(); 1.418 + 1.419 + if (preview->mVisible) 1.420 + preview->UpdateTaskbarProperties(); 1.421 + } 1.422 + } 1.423 + return false; 1.424 +} 1.425 + 1.426 +TaskbarPreview * 1.427 +TaskbarPreview::sActivePreview = nullptr; 1.428 + 1.429 +} // namespace widget 1.430 +} // namespace mozilla 1.431 +