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 "TaskbarTabPreview.h"
9 #include "nsWindowGfx.h"
10 #include "nsUXThemeData.h"
11 #include "WinUtils.h"
12 #include <nsITaskbarPreviewController.h>
13 
14 #define TASKBARPREVIEW_HWNDID L"TaskbarTabPreviewHwnd"
15 
16 namespace mozilla {
17 namespace widget {
18 
19 NS_IMPL_ISUPPORTS(TaskbarTabPreview, nsITaskbarTabPreview)
20 
21 const wchar_t *const kWindowClass = L"MozillaTaskbarPreviewClass";
22 
TaskbarTabPreview(ITaskbarList4 * aTaskbar,nsITaskbarPreviewController * aController,HWND aHWND,nsIDocShell * aShell)23 TaskbarTabPreview::TaskbarTabPreview(ITaskbarList4 *aTaskbar,
24                                      nsITaskbarPreviewController *aController,
25                                      HWND aHWND, nsIDocShell *aShell)
26     : TaskbarPreview(aTaskbar, aController, aHWND, aShell),
27       mProxyWindow(nullptr),
28       mIcon(nullptr),
29       mRegistered(false) {
30   WindowHook &hook = GetWindowHook();
31   hook.AddMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this);
32 }
33 
~TaskbarTabPreview()34 TaskbarTabPreview::~TaskbarTabPreview() {
35   if (mIcon) {
36     ::DestroyIcon(mIcon);
37     mIcon = nullptr;
38   }
39 
40   // We need to ensure that proxy window disappears or else Bad Things happen.
41   if (mProxyWindow) Disable();
42 
43   NS_ASSERTION(!mProxyWindow, "Taskbar proxy window was not destroyed!");
44 
45   if (IsWindowAvailable()) {
46     DetachFromNSWindow();
47   } else {
48     mWnd = nullptr;
49   }
50 }
51 
ShowActive(bool active)52 nsresult TaskbarTabPreview::ShowActive(bool active) {
53   NS_ASSERTION(mVisible && CanMakeTaskbarCalls(),
54                "ShowActive called on invisible window or before taskbar calls "
55                "can be made for this window");
56   return FAILED(
57              mTaskbar->SetTabActive(active ? mProxyWindow : nullptr, mWnd, 0))
58              ? NS_ERROR_FAILURE
59              : NS_OK;
60 }
61 
PreviewWindow()62 HWND &TaskbarTabPreview::PreviewWindow() { return mProxyWindow; }
63 
GetHWND()64 nativeWindow TaskbarTabPreview::GetHWND() { return mProxyWindow; }
65 
EnsureRegistration()66 void TaskbarTabPreview::EnsureRegistration() {
67   NS_ASSERTION(mVisible && CanMakeTaskbarCalls(),
68                "EnsureRegistration called when it is not safe to do so");
69 
70   (void)UpdateTaskbarProperties();
71 }
72 
73 NS_IMETHODIMP
GetTitle(nsAString & aTitle)74 TaskbarTabPreview::GetTitle(nsAString &aTitle) {
75   aTitle = mTitle;
76   return NS_OK;
77 }
78 
79 NS_IMETHODIMP
SetTitle(const nsAString & aTitle)80 TaskbarTabPreview::SetTitle(const nsAString &aTitle) {
81   mTitle = aTitle;
82   return mVisible ? UpdateTitle() : NS_OK;
83 }
84 
85 NS_IMETHODIMP
SetIcon(imgIContainer * icon)86 TaskbarTabPreview::SetIcon(imgIContainer *icon) {
87   HICON hIcon = nullptr;
88   if (icon) {
89     nsresult rv;
90     rv = nsWindowGfx::CreateIcon(
91         icon, false, 0, 0, nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon),
92         &hIcon);
93     NS_ENSURE_SUCCESS(rv, rv);
94   }
95 
96   if (mIcon) ::DestroyIcon(mIcon);
97   mIcon = hIcon;
98   mIconImage = icon;
99   return mVisible ? UpdateIcon() : NS_OK;
100 }
101 
102 NS_IMETHODIMP
GetIcon(imgIContainer ** icon)103 TaskbarTabPreview::GetIcon(imgIContainer **icon) {
104   NS_IF_ADDREF(*icon = mIconImage);
105   return NS_OK;
106 }
107 
108 NS_IMETHODIMP
Move(nsITaskbarTabPreview * aNext)109 TaskbarTabPreview::Move(nsITaskbarTabPreview *aNext) {
110   if (aNext == this) return NS_ERROR_INVALID_ARG;
111   mNext = aNext;
112   return CanMakeTaskbarCalls() ? UpdateNext() : NS_OK;
113 }
114 
UpdateTaskbarProperties()115 nsresult TaskbarTabPreview::UpdateTaskbarProperties() {
116   if (mRegistered) return NS_OK;
117 
118   if (FAILED(mTaskbar->RegisterTab(mProxyWindow, mWnd)))
119     return NS_ERROR_FAILURE;
120 
121   nsresult rv = UpdateNext();
122   NS_ENSURE_SUCCESS(rv, rv);
123   rv = TaskbarPreview::UpdateTaskbarProperties();
124   mRegistered = true;
125   return rv;
126 }
127 
128 LRESULT
WndProc(UINT nMsg,WPARAM wParam,LPARAM lParam)129 TaskbarTabPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) {
130   RefPtr<TaskbarTabPreview> kungFuDeathGrip(this);
131   switch (nMsg) {
132     case WM_CREATE:
133       TaskbarPreview::EnableCustomDrawing(mProxyWindow, true);
134       return 0;
135     case WM_CLOSE:
136       mController->OnClose();
137       return 0;
138     case WM_ACTIVATE:
139       if (LOWORD(wParam) == WA_ACTIVE) {
140         // Activate the tab the user selected then restore the main window,
141         // keeping normal/max window state intact.
142         bool activateWindow;
143         nsresult rv = mController->OnActivate(&activateWindow);
144         if (NS_SUCCEEDED(rv) && activateWindow) {
145           nsWindow *win = WinUtils::GetNSWindowPtr(mWnd);
146           if (win) {
147             nsWindow *parent = win->GetTopLevelWindow(true);
148             if (parent) {
149               parent->Show(true);
150             }
151           }
152         }
153       }
154       return 0;
155     case WM_GETICON:
156       return (LRESULT)mIcon;
157     case WM_SYSCOMMAND:
158       // Send activation events to the top level window and select the proper
159       // tab through the controller.
160       if (wParam == SC_RESTORE || wParam == SC_MAXIMIZE) {
161         bool activateWindow;
162         nsresult rv = mController->OnActivate(&activateWindow);
163         if (NS_SUCCEEDED(rv) && activateWindow) {
164           // Note, restoring an iconic, maximized window here will only
165           // activate the maximized window. This is not a bug, it's default
166           // windows behavior.
167           ::SendMessageW(mWnd, WM_SYSCOMMAND, wParam, lParam);
168         }
169         return 0;
170       }
171       // Forward everything else to the top level window. Do not forward
172       // close since that's intended for the tab. When the preview proxy
173       // closes, we'll close the tab above.
174       return wParam == SC_CLOSE
175                  ? ::DefWindowProcW(mProxyWindow, WM_SYSCOMMAND, wParam, lParam)
176                  : ::SendMessageW(mWnd, WM_SYSCOMMAND, wParam, lParam);
177       return 0;
178   }
179   return TaskbarPreview::WndProc(nMsg, wParam, lParam);
180 }
181 
182 /* static */
GlobalWndProc(HWND hWnd,UINT nMsg,WPARAM wParam,LPARAM lParam)183 LRESULT CALLBACK TaskbarTabPreview::GlobalWndProc(HWND hWnd, UINT nMsg,
184                                                   WPARAM wParam,
185                                                   LPARAM lParam) {
186   TaskbarTabPreview *preview(nullptr);
187   if (nMsg == WM_CREATE) {
188     CREATESTRUCT *cs = reinterpret_cast<CREATESTRUCT *>(lParam);
189     preview = reinterpret_cast<TaskbarTabPreview *>(cs->lpCreateParams);
190     if (!::SetPropW(hWnd, TASKBARPREVIEW_HWNDID, preview))
191       NS_ERROR("Could not associate native window with tab preview");
192     preview->mProxyWindow = hWnd;
193   } else {
194     preview = reinterpret_cast<TaskbarTabPreview *>(
195         ::GetPropW(hWnd, TASKBARPREVIEW_HWNDID));
196     if (nMsg == WM_DESTROY) ::RemovePropW(hWnd, TASKBARPREVIEW_HWNDID);
197   }
198 
199   if (preview) return preview->WndProc(nMsg, wParam, lParam);
200   return ::DefWindowProcW(hWnd, nMsg, wParam, lParam);
201 }
202 
Enable()203 nsresult TaskbarTabPreview::Enable() {
204   WNDCLASSW wc;
205   HINSTANCE module = GetModuleHandle(nullptr);
206 
207   if (!GetClassInfoW(module, kWindowClass, &wc)) {
208     wc.style = 0;
209     wc.lpfnWndProc = GlobalWndProc;
210     wc.cbClsExtra = 0;
211     wc.cbWndExtra = 0;
212     wc.hInstance = module;
213     wc.hIcon = nullptr;
214     wc.hCursor = nullptr;
215     wc.hbrBackground = (HBRUSH) nullptr;
216     wc.lpszMenuName = (LPCWSTR) nullptr;
217     wc.lpszClassName = kWindowClass;
218     RegisterClassW(&wc);
219   }
220   ::CreateWindowW(kWindowClass, L"TaskbarPreviewWindow",
221                   WS_CAPTION | WS_SYSMENU, 0, 0, 200, 60, nullptr, nullptr,
222                   module, this);
223   // GlobalWndProc will set mProxyWindow so that WM_CREATE can have a valid HWND
224   if (!mProxyWindow) return NS_ERROR_INVALID_ARG;
225 
226   UpdateProxyWindowStyle();
227 
228   nsresult rv = TaskbarPreview::Enable();
229   nsresult rvUpdate;
230   rvUpdate = UpdateTitle();
231   if (NS_FAILED(rvUpdate)) rv = rvUpdate;
232 
233   rvUpdate = UpdateIcon();
234   if (NS_FAILED(rvUpdate)) rv = rvUpdate;
235 
236   return rv;
237 }
238 
Disable()239 nsresult TaskbarTabPreview::Disable() {
240   // TaskbarPreview::Disable assumes that mWnd is valid but this method can be
241   // called when it is null iff the nsWindow has already been destroyed and we
242   // are still visible for some reason during object destruction.
243   if (mWnd) TaskbarPreview::Disable();
244 
245   if (FAILED(mTaskbar->UnregisterTab(mProxyWindow))) return NS_ERROR_FAILURE;
246   mRegistered = false;
247 
248   // TaskbarPreview::WndProc will set mProxyWindow to null
249   if (!DestroyWindow(mProxyWindow)) return NS_ERROR_FAILURE;
250   mProxyWindow = nullptr;
251   return NS_OK;
252 }
253 
DetachFromNSWindow()254 void TaskbarTabPreview::DetachFromNSWindow() {
255   (void)SetVisible(false);
256   WindowHook &hook = GetWindowHook();
257   hook.RemoveMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this);
258 
259   TaskbarPreview::DetachFromNSWindow();
260 }
261 
262 /* static */
MainWindowHook(void * aContext,HWND hWnd,UINT nMsg,WPARAM wParam,LPARAM lParam,LRESULT * aResult)263 bool TaskbarTabPreview::MainWindowHook(void *aContext, HWND hWnd, UINT nMsg,
264                                        WPARAM wParam, LPARAM lParam,
265                                        LRESULT *aResult) {
266   if (nMsg == WM_WINDOWPOSCHANGED) {
267     TaskbarTabPreview *preview =
268         reinterpret_cast<TaskbarTabPreview *>(aContext);
269     WINDOWPOS *pos = reinterpret_cast<WINDOWPOS *>(lParam);
270     if (SWP_FRAMECHANGED == (pos->flags & SWP_FRAMECHANGED))
271       preview->UpdateProxyWindowStyle();
272   } else {
273     NS_NOTREACHED("Style changed hook fired on non-style changed message");
274   }
275   return false;
276 }
277 
UpdateProxyWindowStyle()278 void TaskbarTabPreview::UpdateProxyWindowStyle() {
279   if (!mProxyWindow) return;
280 
281   DWORD minMaxMask = WS_MINIMIZE | WS_MAXIMIZE;
282   DWORD windowStyle = GetWindowLongW(mWnd, GWL_STYLE);
283 
284   DWORD proxyStyle = GetWindowLongW(mProxyWindow, GWL_STYLE);
285   proxyStyle &= ~minMaxMask;
286   proxyStyle |= windowStyle & minMaxMask;
287   SetWindowLongW(mProxyWindow, GWL_STYLE, proxyStyle);
288 
289   DWORD exStyle =
290       (WS_MAXIMIZE == (windowStyle & WS_MAXIMIZE)) ? WS_EX_TOOLWINDOW : 0;
291   SetWindowLongW(mProxyWindow, GWL_EXSTYLE, exStyle);
292 }
293 
UpdateTitle()294 nsresult TaskbarTabPreview::UpdateTitle() {
295   NS_ASSERTION(mVisible, "UpdateTitle called on invisible preview");
296 
297   if (!::SetWindowTextW(mProxyWindow, mTitle.get())) return NS_ERROR_FAILURE;
298   return NS_OK;
299 }
300 
UpdateIcon()301 nsresult TaskbarTabPreview::UpdateIcon() {
302   NS_ASSERTION(mVisible, "UpdateIcon called on invisible preview");
303 
304   ::SendMessageW(mProxyWindow, WM_SETICON, ICON_SMALL, (LPARAM)mIcon);
305 
306   return NS_OK;
307 }
308 
UpdateNext()309 nsresult TaskbarTabPreview::UpdateNext() {
310   NS_ASSERTION(CanMakeTaskbarCalls() && mVisible,
311                "UpdateNext called on invisible tab preview");
312   HWND hNext = nullptr;
313   if (mNext) {
314     bool visible;
315     nsresult rv = mNext->GetVisible(&visible);
316 
317     NS_ENSURE_SUCCESS(rv, rv);
318 
319     // Can only move next to enabled previews
320     if (!visible) return NS_ERROR_FAILURE;
321 
322     hNext = (HWND)mNext->GetHWND();
323 
324     // hNext must be registered with the taskbar if the call is to succeed
325     mNext->EnsureRegistration();
326   }
327   if (FAILED(mTaskbar->SetTabOrder(mProxyWindow, hNext)))
328     return NS_ERROR_FAILURE;
329   return NS_OK;
330 }
331 
332 }  // namespace widget
333 }  // namespace mozilla
334