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