text: // Copyright 2017 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "gpu/ipc/service/child_window_win.h"
  
  #include <memory>
  
  #include "base/compiler_specific.h"
  #include "base/memory/ptr_util.h"
  #include "base/message_loop/message_loop.h"
  #include "base/threading/thread.h"
  #include "base/win/scoped_hdc.h"
  #include "base/win/wrapped_window_proc.h"
  #include "gpu/ipc/common/gpu_messages.h"
  #include "gpu/ipc/service/gpu_channel_manager.h"
  #include "gpu/ipc/service/gpu_channel_manager_delegate.h"
  #include "ui/gfx/native_widget_types.h"
  #include "ui/gfx/win/hwnd_util.h"
  #include "ui/gfx/win/window_impl.h"
  
  namespace gpu {
  
  // This owns the thread and contains data that's shared between the threads.
  struct SharedData {
    SharedData() : thread("Window owner thread") {}
  
    base::Lock rect_lock;
    gfx::Rect rect_to_clear;
  
    base::Thread thread;
  };
  
  namespace {
  
  ATOM g_window_class;
  
  // This runs on the window owner thread.
  LRESULT CALLBACK IntermediateWindowProc(HWND window,
                                          UINT message,
                                          WPARAM w_param,
                                          LPARAM l_param) {
    switch (message) {
      case WM_ERASEBKGND:
        // Prevent windows from erasing the background.
        return 1;
      case WM_PAINT:
        PAINTSTRUCT paint;
        if (BeginPaint(window, &paint)) {
          SharedData* shared_data =
              reinterpret_cast<SharedData*>(gfx::GetWindowUserData(window));
          DCHECK(shared_data);
          {
            base::AutoLock lock(shared_data->rect_lock);
            shared_data->rect_to_clear.Union(gfx::Rect(paint.rcPaint));
          }
  
          EndPaint(window, &paint);
        }
        return 0;
      default:
        return DefWindowProc(window, message, w_param, l_param);
    }
  }
  
  // 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<IntermediateWindowProc>, 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()
  };
  
  // This runs on the window owner thread.
  void CreateWindowsOnThread(const gfx::Size& size,
                             base::WaitableEvent* event,
                             SharedData* shared_data,
                             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.
    HWND window = CreateWindowEx(
        WS_EX_NOPARENTNOTIFY, reinterpret_cast<wchar_t*>(g_window_class), L"",
        WS_CHILDWINDOW | WS_DISABLED | WS_VISIBLE, 0, 0, size.width(),
        size.height(), *parent_window, NULL, NULL, NULL);
    CHECK(window);
    *child_window = window;
    gfx::SetWindowUserData(window, shared_data);
    event->Signal();
  }
  
  // This runs on the main thread after the window was destroyed on window owner
  // thread.
  void DestroySharedData(std::unique_ptr<SharedData> shared_data) {
    shared_data->thread.Stop();
  }
  
  // This runs on the window owner thread.
  void DestroyWindowsOnThread(HWND child_window, HWND hidden_popup_window) {
    DestroyWindow(child_window);
    HiddenPopupWindow::Destroy(hidden_popup_window);
  }
  
  }  // namespace
  
  ChildWindowWin::ChildWindowWin(
      base::WeakPtr<ImageTransportSurfaceDelegate> delegate,
      HWND parent_window)
      : parent_window_(parent_window), window_(nullptr), delegate_(delegate) {}
  
  bool ChildWindowWin::Initialize() {
    if (window_)
      return true;
  
    shared_data_ = base::MakeUnique<SharedData>();
  
    base::Thread::Options options(base::MessageLoop::TYPE_UI, 0);
    shared_data_->thread.StartWithOptions(options);
  
    base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
                              base::WaitableEvent::InitialState::NOT_SIGNALED);
  
    RECT window_rect;
    GetClientRect(parent_window_, &window_rect);
  
    shared_data_->thread.task_runner()->PostTask(
        FROM_HERE,
        base::Bind(&CreateWindowsOnThread, gfx::Rect(window_rect).size(), &event,
                   shared_data_.get(), &window_, &initial_parent_window_));
    event.Wait();
  
    delegate_->DidCreateAcceleratedSurfaceChildWindow(parent_window_, window_);
    return true;
  }
  
  void ChildWindowWin::ClearInvalidContents() {
    base::AutoLock lock(shared_data_->rect_lock);
    if (!shared_data_->rect_to_clear.IsEmpty()) {
      base::win::ScopedGetDC dc(window_);
  
      RECT rect = shared_data_->rect_to_clear.ToRECT();
  
      // DirectComposition composites with the contents under the SwapChain,
      // so ensure that's cleared. GDI treats black as transparent.
      FillRect(dc, &rect, reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)));
      shared_data_->rect_to_clear = gfx::Rect();
    }
  }
  
  ChildWindowWin::~ChildWindowWin() {
    if (shared_data_) {
      scoped_refptr<base::TaskRunner> task_runner =
          shared_data_->thread.task_runner();
      task_runner->PostTaskAndReply(
          FROM_HERE,
          base::Bind(&DestroyWindowsOnThread, window_, initial_parent_window_),
          base::Bind(&DestroySharedData, base::Passed(std::move(shared_data_))));
    }
  }
  
  scoped_refptr<base::TaskRunner> ChildWindowWin::GetTaskRunnerForTesting() {
    DCHECK(shared_data_);
    return shared_data_->thread.task_runner();
  }
  
  }  // namespace gpu
  