1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/win/message_window.h"
6
7 #include <windows.h>
8
9 #include <map>
10 #include <utility>
11
12 #include "base/check.h"
13 #include "base/check_op.h"
14 #include "base/compiler_specific.h"
15 #include "base/lazy_instance.h"
16 #include "base/logging.h"
17 #include "base/memory/ptr_util.h"
18 #include "base/memory/raw_ref.h"
19 #include "base/no_destructor.h"
20 #include "base/strings/string_util.h"
21 #include "base/thread_annotations.h"
22 #include "base/threading/thread_checker.h"
23 #include "base/threading/thread_local.h"
24 #include "base/win/current_module.h"
25 #include "base/win/resource_exhaustion.h"
26 #include "base/win/wrapped_window_proc.h"
27
28 // To avoid conflicts with the macro from the Windows SDK...
29 #undef FindWindow
30
31 const wchar_t kMessageWindowClassName[] = L"Chrome_MessageWindow";
32
33 namespace {
34
35 // This class can be accessed from multiple threads,
36 // this is handled by each thread having a different instance.
37 class MessageWindowMap {
38 public:
GetInstanceForCurrentThread()39 static MessageWindowMap& GetInstanceForCurrentThread() {
40 static base::NoDestructor<base::ThreadLocalOwnedPointer<MessageWindowMap>>
41 instance;
42 if (!instance->Get()) {
43 instance->Set(base::WrapUnique(new MessageWindowMap));
44 }
45 return *(instance->Get());
46 }
47
48 MessageWindowMap(const MessageWindowMap&) = delete;
49 MessageWindowMap& operator=(const MessageWindowMap&) = delete;
50
51 // Each key should only be inserted once.
Insert(HWND hwnd,base::win::MessageWindow & message_window)52 void Insert(HWND hwnd, base::win::MessageWindow& message_window) {
53 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
54 CHECK(map_.emplace(hwnd, message_window).second);
55 }
56
57 // Erase should only be called on an existing key.
Erase(HWND hwnd)58 void Erase(HWND hwnd) {
59 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
60 // Check that exactly one element is erased from the map.
61 CHECK_EQ(map_.erase(hwnd), 1u);
62 }
63
64 // Will return nullptr if `hwnd` is not in the map.
Get(HWND hwnd) const65 base::win::MessageWindow* Get(HWND hwnd) const {
66 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
67 if (auto search = map_.find(hwnd); search != map_.end()) {
68 return &(search->second.get());
69 }
70 return nullptr;
71 }
72
73 private:
74 MessageWindowMap() = default;
75 THREAD_CHECKER(thread_checker_);
76 std::map<HWND, const raw_ref<base::win::MessageWindow>> map_
77 GUARDED_BY_CONTEXT(thread_checker_);
78 };
79
80 } // namespace
81
82 namespace base {
83 namespace win {
84
85 // Used along with LazyInstance to register a window class for message-only
86 // windows created by MessageWindow.
87 class MessageWindow::WindowClass {
88 public:
89 WindowClass();
90
91 WindowClass(const WindowClass&) = delete;
92 WindowClass& operator=(const WindowClass&) = delete;
93
94 ~WindowClass();
95
atom()96 ATOM atom() { return atom_; }
instance()97 HINSTANCE instance() { return instance_; }
98
99 private:
100 ATOM atom_ = 0;
101 HINSTANCE instance_ = CURRENT_MODULE();
102 };
103
104 static LazyInstance<MessageWindow::WindowClass>::DestructorAtExit
105 g_window_class = LAZY_INSTANCE_INITIALIZER;
106
WindowClass()107 MessageWindow::WindowClass::WindowClass() {
108 WNDCLASSEX window_class;
109 window_class.cbSize = sizeof(window_class);
110 window_class.style = 0;
111 window_class.lpfnWndProc = &WrappedWindowProc<WindowProc>;
112 window_class.cbClsExtra = 0;
113 window_class.cbWndExtra = 0;
114 window_class.hInstance = instance_;
115 window_class.hIcon = nullptr;
116 window_class.hCursor = nullptr;
117 window_class.hbrBackground = nullptr;
118 window_class.lpszMenuName = nullptr;
119 window_class.lpszClassName = kMessageWindowClassName;
120 window_class.hIconSm = nullptr;
121 atom_ = RegisterClassEx(&window_class);
122 if (atom_ == 0) {
123 PLOG(ERROR)
124 << "Failed to register the window class for a message-only window";
125 OnResourceExhausted();
126 }
127 }
128
~WindowClass()129 MessageWindow::WindowClass::~WindowClass() {
130 if (atom_ != 0) {
131 BOOL result = UnregisterClass(MAKEINTATOM(atom_), instance_);
132 // Hitting this DCHECK usually means that some MessageWindow objects were
133 // leaked. For example not calling
134 // ui::Clipboard::DestroyClipboardForCurrentThread() results in a leaked
135 // MessageWindow.
136 DCHECK(result);
137 }
138 }
139
140 MessageWindow::MessageWindow() = default;
141
~MessageWindow()142 MessageWindow::~MessageWindow() {
143 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
144
145 if (window_ != nullptr) {
146 BOOL result = DestroyWindow(window_);
147 DCHECK(result);
148 }
149 }
150
Create(MessageCallback message_callback)151 bool MessageWindow::Create(MessageCallback message_callback) {
152 return DoCreate(std::move(message_callback), nullptr);
153 }
154
CreateNamed(MessageCallback message_callback,const std::wstring & window_name)155 bool MessageWindow::CreateNamed(MessageCallback message_callback,
156 const std::wstring& window_name) {
157 return DoCreate(std::move(message_callback), window_name.c_str());
158 }
159
160 // static
FindWindow(const std::wstring & window_name)161 HWND MessageWindow::FindWindow(const std::wstring& window_name) {
162 return FindWindowEx(HWND_MESSAGE, nullptr, kMessageWindowClassName,
163 window_name.c_str());
164 }
165
DoCreate(MessageCallback message_callback,const wchar_t * window_name)166 bool MessageWindow::DoCreate(MessageCallback message_callback,
167 const wchar_t* window_name) {
168 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
169 DCHECK(message_callback_.is_null());
170 DCHECK(!window_);
171
172 message_callback_ = std::move(message_callback);
173
174 WindowClass& window_class = g_window_class.Get();
175 window_ =
176 CreateWindow(MAKEINTATOM(window_class.atom()), window_name, 0, 0, 0, 0, 0,
177 HWND_MESSAGE, nullptr, window_class.instance(), this);
178 if (!window_) {
179 PLOG(ERROR) << "Failed to create a message-only window";
180 return false;
181 }
182
183 return true;
184 }
185
186 // static
WindowProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)187 LRESULT CALLBACK MessageWindow::WindowProc(HWND hwnd,
188 UINT message,
189 WPARAM wparam,
190 LPARAM lparam) {
191 // This can be called from different threads for different windows,
192 // each thread has its own MessageWindowMap instance.
193 auto& message_window_map = MessageWindowMap::GetInstanceForCurrentThread();
194 MessageWindow* self = message_window_map.Get(hwnd);
195
196 // CreateWindow will send a WM_CREATE message during window creation.
197 if (UNLIKELY(!self && message == WM_CREATE)) {
198 CREATESTRUCT* const cs = reinterpret_cast<CREATESTRUCT*>(lparam);
199 self = reinterpret_cast<MessageWindow*>(cs->lpCreateParams);
200
201 // Tell the MessageWindow instance the HWND that CreateWindow has produced.
202 self->window_ = hwnd;
203
204 // Associate the MessageWindow instance with the HWND in the map.
205 message_window_map.Insert(hwnd, *self);
206 }
207
208 if (UNLIKELY(!self)) {
209 return DefWindowProc(hwnd, message, wparam, lparam);
210 }
211
212 LRESULT message_result = {};
213 if (!self->message_callback_.Run(message, wparam, lparam, &message_result)) {
214 message_result = DefWindowProc(hwnd, message, wparam, lparam);
215 }
216
217 if (UNLIKELY(message == WM_DESTROY)) {
218 // Tell the MessageWindow instance that it no longer has an HWND.
219 self->window_ = nullptr;
220
221 // Remove this HWND's MessageWindow from the map since it is going away.
222 message_window_map.Erase(hwnd);
223 }
224
225 return message_result;
226 }
227
228 } // namespace win
229 } // namespace base
230