// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/gl/child_window_win.h"
#include "base/compiler_specific.h"
#include "base/debug/alias.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_pump_type.h"
#include "base/threading/thread.h"
#include "base/threading/thread_checker.h"
#include "base/win/wrapped_window_proc.h"
#include "ui/gfx/win/hwnd_util.h"
#include "ui/gfx/win/window_impl.h"
namespace gl {
namespace {
ATOM g_window_class;
// This runs on the window owner thread.
void InitializeWindowClass() {
if (g_window_class)
return;
WNDCLASSEX intermediate_class;
base::win::InitializeWindowClass(
L"Intermediate D3D Window",
&base::win::WrappedWindowProc<::DefWindowProc>, CS_OWNDC, 0, 0, nullptr,
reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)), nullptr, nullptr,
nullptr, &intermediate_class);
g_window_class = RegisterClassEx(&intermediate_class);
if (!g_window_class) {
LOG(ERROR) << "RegisterClass failed.";
return;
}
}
// Hidden popup window used as a parent for the child surface window.
// Must be created and destroyed on the thread.
class HiddenPopupWindow : public gfx::WindowImpl {
public:
static HWND Create() {
gfx::WindowImpl* window = new HiddenPopupWindow;
window->set_window_style(WS_POPUP);
window->set_window_ex_style(WS_EX_TOOLWINDOW);
window->Init(GetDesktopWindow(), gfx::Rect());
EnableWindow(window->hwnd(), FALSE);
// The |window| instance is now owned by the window user data.
DCHECK_EQ(window, gfx::GetWindowUserData(window->hwnd()));
return window->hwnd();
}
static void Destroy(HWND window) {
// This uses the fact that the window user data contains a pointer
// to gfx::WindowImpl instance.
gfx::WindowImpl* window_data =
reinterpret_cast<gfx::WindowImpl*>(gfx::GetWindowUserData(window));
DCHECK_EQ(window, window_data->hwnd());
DestroyWindow(window);
delete window_data;
}
private:
// Explicitly do nothing in Close. We do this as some external apps may get a
// handle to this window and attempt to close it.
void OnClose() {}
CR_BEGIN_MSG_MAP_EX(HiddenPopupWindow)
CR_MSG_WM_CLOSE(OnClose)
CR_END_MSG_MAP()
CR_MSG_MAP_CLASS_DECLARATIONS(HiddenPopupWindow)
};
// This runs on the window owner thread.
void CreateWindowsOnThread(base::WaitableEvent* event,
HWND* child_window,
HWND* parent_window) {
InitializeWindowClass();
DCHECK(g_window_class);
// Create hidden parent window on the current thread.
*parent_window = HiddenPopupWindow::Create();
// Create child window.
// WS_EX_NOPARENTNOTIFY and WS_EX_LAYERED make the window transparent for
// input. WS_EX_NOREDIRECTIONBITMAP avoids allocating a
// bitmap that would otherwise be allocated with WS_EX_LAYERED, the bitmap is
// only necessary if using Gdi objects with the window.
// Using a size of 1x1 is fine because the window will be subsequently resized
// using SetWindowPos whenever the parent window size changes.
const HWND window = CreateWindowEx(
WS_EX_NOPARENTNOTIFY | WS_EX_LAYERED | WS_EX_TRANSPARENT |
WS_EX_NOREDIRECTIONBITMAP,
reinterpret_cast<wchar_t*>(g_window_class), L"",
WS_CHILDWINDOW | WS_DISABLED | WS_VISIBLE, 0, 0, /*width*/ 1,
/*height*/ 1, *parent_window, nullptr, nullptr, nullptr);
if (!window) {
logging::SystemErrorCode error = logging::GetLastSystemErrorCode();
base::debug::Alias(&error);
CHECK(false);
}
*child_window = window;
event->Signal();
}
// This runs on the window owner thread.
void DestroyWindowsOnThread(HWND child_window, HWND hidden_popup_window) {
DestroyWindow(child_window);
HiddenPopupWindow::Destroy(hidden_popup_window);
}
#if DCHECK_IS_ON()
base::ThreadChecker& GetThreadChecker() {
static base::ThreadChecker thread_checker;
return thread_checker;
}
#endif
} // namespace
class ChildWindowWin::ChildWindowThread
: public base::RefCounted<ChildWindowThread> {
public:
// Returns the singleton instance of the thread.
static scoped_refptr<ChildWindowThread> GetInstance() {
DCHECK_CALLED_ON_VALID_THREAD(GetThreadChecker());
static base::WeakPtr<ChildWindowThread> weak_instance;
auto instance = base::WrapRefCounted(weak_instance.get());
if (!instance) {
instance = base::WrapRefCounted(new ChildWindowThread);
weak_instance = instance->weak_ptr_factory_.GetWeakPtr();
}
return instance;
}
scoped_refptr<base::TaskRunner> task_runner() {
DCHECK_CALLED_ON_VALID_THREAD(GetThreadChecker());
return thread_.task_runner();
}
private:
friend class base::RefCounted<ChildWindowThread>;
ChildWindowThread() : thread_("Window owner thread") {
DCHECK_CALLED_ON_VALID_THREAD(GetThreadChecker());
base::Thread::Options options(base::MessagePumpType::UI, 0);
thread_.StartWithOptions(std::move(options));
}
~ChildWindowThread() {
DCHECK_CALLED_ON_VALID_THREAD(GetThreadChecker());
thread_.Stop();
}
base::Thread thread_;
base::WeakPtrFactory<ChildWindowThread> weak_ptr_factory_{this};
};
ChildWindowWin::ChildWindowWin() = default;
void ChildWindowWin::Initialize() {
if (window_)
return;
thread_ = ChildWindowThread::GetInstance();
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
thread_->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&CreateWindowsOnThread, &event, &window_,
&initial_parent_window_));
event.Wait();
}
ChildWindowWin::~ChildWindowWin() {
if (thread_) {
scoped_refptr<base::TaskRunner> task_runner = thread_->task_runner();
task_runner->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&DestroyWindowsOnThread, window_,
initial_parent_window_),
base::DoNothingWithBoundArgs(std::move(thread_)));
}
}
void ChildWindowWin::Resize(const gfx::Size& size) {
// Force a resize and redraw (but not a move, activate, etc.).
constexpr UINT kFlags = SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE |
SWP_NOOWNERZORDER | SWP_NOREDRAW |
SWP_NOSENDCHANGING | SWP_NOZORDER;
// When the browser process destroys its window, Windows will destroy
// all of its child windows, including our window. This leads to a race
// condition where SetWindowPos may return false if our window has been
// destroyed before we finish processing Reshape requests for the
// window.
// Returning a failure from ChildWindowWin::Resize will cause the
// outer Skia output device code to flag CONTEXT_LOST_RESHAPE_FAILED and
// terminate the GPU process. Instead of handling failures from SetWindowPos,
// we ignore its return value. The outer code will eventually be told of the
// window's demise.
if (!::SetWindowPos(window_, nullptr, 0, 0, size.width(), size.height(),
kFlags)) {
DPLOG(WARNING) << "::SetWindowPos failed";
}
}
scoped_refptr<base::TaskRunner> ChildWindowWin::GetTaskRunnerForTesting() {
DCHECK(thread_);
return thread_->task_runner();
}
} // namespace gl