Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / views / status_icons / status_tray_win.cc
blob0b560bdb188fd64081b6e88d5bcb502827592364
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/views/status_icons/status_tray_win.h"
7 #include <commctrl.h>
9 #include "base/bind.h"
10 #include "base/location.h"
11 #include "base/profiler/scoped_tracker.h"
12 #include "base/single_thread_task_runner.h"
13 #include "base/threading/non_thread_safe.h"
14 #include "base/threading/thread.h"
15 #include "base/win/wrapped_window_proc.h"
16 #include "chrome/browser/lifetime/application_lifetime.h"
17 #include "chrome/browser/ui/views/status_icons/status_icon_win.h"
18 #include "chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h"
19 #include "chrome/common/chrome_constants.h"
20 #include "components/browser_watcher/exit_funnel_win.h"
21 #include "ui/gfx/screen.h"
22 #include "ui/gfx/win/hwnd_util.h"
24 static const UINT kStatusIconMessage = WM_APP + 1;
26 namespace {
27 // |kBaseIconId| is 2 to avoid conflicts with plugins that hard-code id 1.
28 const UINT kBaseIconId = 2;
30 UINT ReservedIconId(StatusTray::StatusIconType type) {
31 return kBaseIconId + static_cast<UINT>(type);
34 // See http://crbug.com/412384.
35 void TraceSessionEnding(LPARAM lparam) {
36 browser_watcher::ExitFunnel funnel;
37 if (!funnel.Init(chrome::kBrowserExitCodesRegistryPath,
38 base::GetCurrentProcessHandle())) {
39 return;
42 // This exit path is the prime suspect for most our unclean shutdowns.
43 // Trace all the possible options to WM_ENDSESSION. This may result in
44 // multiple events for a single shutdown, but that's fine.
45 funnel.RecordEvent(L"TraybarEndSession");
47 if (lparam & ENDSESSION_CLOSEAPP)
48 funnel.RecordEvent(L"ES_CloseApp");
49 if (lparam & ENDSESSION_CRITICAL)
50 funnel.RecordEvent(L"ES_Critical");
51 if (lparam & ENDSESSION_LOGOFF)
52 funnel.RecordEvent(L"ES_Logoff");
53 const LPARAM kKnownBits =
54 ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF;
55 if (lparam & ~kKnownBits)
56 funnel.RecordEvent(L"ES_Other");
59 } // namespace
61 // Default implementation for StatusTrayStateChanger that communicates to
62 // Exporer.exe via COM. It spawns a background thread with a fresh COM
63 // apartment and requests that the visibility be increased unless the user
64 // has explicitly set the icon to be hidden.
65 class StatusTrayStateChangerProxyImpl : public StatusTrayStateChangerProxy,
66 public base::NonThreadSafe {
67 public:
68 StatusTrayStateChangerProxyImpl()
69 : pending_requests_(0),
70 worker_thread_("StatusIconCOMWorkerThread"),
71 weak_factory_(this) {
72 worker_thread_.init_com_with_mta(false);
75 void EnqueueChange(UINT icon_id, HWND window) override {
76 DCHECK(CalledOnValidThread());
77 if (pending_requests_ == 0)
78 worker_thread_.Start();
80 ++pending_requests_;
81 worker_thread_.task_runner()->PostTaskAndReply(
82 FROM_HERE,
83 base::Bind(
84 &StatusTrayStateChangerProxyImpl::EnqueueChangeOnWorkerThread,
85 icon_id, window),
86 base::Bind(&StatusTrayStateChangerProxyImpl::ChangeDone,
87 weak_factory_.GetWeakPtr()));
90 private:
91 // Must be called only on |worker_thread_|, to ensure the correct COM
92 // apartment.
93 static void EnqueueChangeOnWorkerThread(UINT icon_id, HWND window) {
94 // It appears that IUnknowns are coincidentally compatible with
95 // scoped_refptr. Normally I wouldn't depend on that but it seems that
96 // base::win::IUnknownImpl itself depends on that coincidence so it's
97 // already being assumed elsewhere.
98 scoped_refptr<StatusTrayStateChangerWin> status_tray_state_changer(
99 new StatusTrayStateChangerWin(icon_id, window));
100 status_tray_state_changer->EnsureTrayIconVisible();
103 // Called on UI thread.
104 void ChangeDone() {
105 DCHECK(CalledOnValidThread());
106 DCHECK_GT(pending_requests_, 0);
108 if (--pending_requests_ == 0)
109 worker_thread_.Stop();
112 private:
113 int pending_requests_;
114 base::Thread worker_thread_;
115 base::WeakPtrFactory<StatusTrayStateChangerProxyImpl> weak_factory_;
117 DISALLOW_COPY_AND_ASSIGN(StatusTrayStateChangerProxyImpl);
120 StatusTrayWin::StatusTrayWin()
121 : next_icon_id_(1),
122 atom_(0),
123 instance_(NULL),
124 window_(NULL) {
125 // Register our window class
126 WNDCLASSEX window_class;
127 base::win::InitializeWindowClass(
128 chrome::kStatusTrayWindowClass,
129 &base::win::WrappedWindowProc<StatusTrayWin::WndProcStatic>,
130 0, 0, 0, NULL, NULL, NULL, NULL, NULL,
131 &window_class);
132 instance_ = window_class.hInstance;
133 atom_ = RegisterClassEx(&window_class);
134 CHECK(atom_);
136 // If the taskbar is re-created after we start up, we have to rebuild all of
137 // our icons.
138 taskbar_created_message_ = RegisterWindowMessage(TEXT("TaskbarCreated"));
140 // Create an offscreen window for handling messages for the status icons. We
141 // create a hidden WS_POPUP window instead of an HWND_MESSAGE window, because
142 // only top-level windows such as popups can receive broadcast messages like
143 // "TaskbarCreated".
144 window_ = CreateWindow(MAKEINTATOM(atom_),
145 0, WS_POPUP, 0, 0, 0, 0, 0, 0, instance_, 0);
146 gfx::CheckWindowCreated(window_);
147 gfx::SetWindowUserData(window_, this);
150 StatusTrayWin::~StatusTrayWin() {
151 if (window_)
152 DestroyWindow(window_);
154 if (atom_)
155 UnregisterClass(MAKEINTATOM(atom_), instance_);
158 void StatusTrayWin::UpdateIconVisibilityInBackground(
159 StatusIconWin* status_icon) {
160 if (!state_changer_proxy_.get())
161 state_changer_proxy_.reset(new StatusTrayStateChangerProxyImpl);
163 state_changer_proxy_->EnqueueChange(status_icon->icon_id(),
164 status_icon->window());
167 LRESULT CALLBACK StatusTrayWin::WndProcStatic(HWND hwnd,
168 UINT message,
169 WPARAM wparam,
170 LPARAM lparam) {
171 StatusTrayWin* msg_wnd = reinterpret_cast<StatusTrayWin*>(
172 GetWindowLongPtr(hwnd, GWLP_USERDATA));
173 if (msg_wnd)
174 return msg_wnd->WndProc(hwnd, message, wparam, lparam);
175 else
176 return ::DefWindowProc(hwnd, message, wparam, lparam);
179 LRESULT CALLBACK StatusTrayWin::WndProc(HWND hwnd,
180 UINT message,
181 WPARAM wparam,
182 LPARAM lparam) {
183 if (message == taskbar_created_message_) {
184 // We need to reset all of our icons because the taskbar went away.
185 for (StatusIcons::const_iterator i(status_icons().begin());
186 i != status_icons().end(); ++i) {
187 StatusIconWin* win_icon = static_cast<StatusIconWin*>(*i);
188 win_icon->ResetIcon();
190 return TRUE;
191 } else if (message == kStatusIconMessage) {
192 StatusIconWin* win_icon = NULL;
194 // Find the selected status icon.
195 for (StatusIcons::const_iterator i(status_icons().begin());
196 i != status_icons().end();
197 ++i) {
198 StatusIconWin* current_win_icon = static_cast<StatusIconWin*>(*i);
199 if (current_win_icon->icon_id() == wparam) {
200 win_icon = current_win_icon;
201 break;
205 // It is possible for this procedure to be called with an obsolete icon
206 // id. In that case we should just return early before handling any
207 // actions.
208 if (!win_icon)
209 return TRUE;
211 switch (lparam) {
212 case TB_INDETERMINATE:
213 win_icon->HandleBalloonClickEvent();
214 return TRUE;
216 case WM_LBUTTONDOWN:
217 case WM_RBUTTONDOWN:
218 case WM_CONTEXTMENU:
219 // Walk our icons, find which one was clicked on, and invoke its
220 // HandleClickEvent() method.
221 gfx::Point cursor_pos(
222 gfx::Screen::GetNativeScreen()->GetCursorScreenPoint());
223 win_icon->HandleClickEvent(cursor_pos, lparam == WM_LBUTTONDOWN);
224 return TRUE;
226 } else if (message == WM_ENDSESSION) {
227 // If Chrome is in background-only mode, this is the only notification
228 // it gets that Windows is exiting. Make sure we shutdown in an orderly
229 // fashion.
230 TraceSessionEnding(lparam);
231 chrome::SessionEnding();
233 return ::DefWindowProc(hwnd, message, wparam, lparam);
236 StatusIcon* StatusTrayWin::CreatePlatformStatusIcon(
237 StatusTray::StatusIconType type,
238 const gfx::ImageSkia& image,
239 const base::string16& tool_tip) {
240 UINT next_icon_id;
241 if (type == StatusTray::OTHER_ICON)
242 next_icon_id = NextIconId();
243 else
244 next_icon_id = ReservedIconId(type);
246 StatusIcon* icon =
247 new StatusIconWin(this, next_icon_id, window_, kStatusIconMessage);
249 icon->SetImage(image);
250 icon->SetToolTip(tool_tip);
251 return icon;
254 UINT StatusTrayWin::NextIconId() {
255 UINT icon_id = next_icon_id_++;
256 return kBaseIconId + static_cast<UINT>(NAMED_STATUS_ICON_COUNT) + icon_id;
259 void StatusTrayWin::SetStatusTrayStateChangerProxyForTest(
260 scoped_ptr<StatusTrayStateChangerProxy> proxy) {
261 state_changer_proxy_ = proxy.Pass();
264 StatusTray* StatusTray::Create() {
265 return new StatusTrayWin();