// Copyright (c) 2015 The Chromium Embedded Framework 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 "tests/cefclient/browser/osr_window_win.h"

#include <windowsx.h>
#if defined(CEF_USE_ATL)
#include <oleacc.h>
#endif

#include "include/base/cef_build.h"
#include "tests/cefclient/browser/main_context.h"
#include "tests/cefclient/browser/osr_accessibility_helper.h"
#include "tests/cefclient/browser/osr_accessibility_node.h"
#include "tests/cefclient/browser/osr_ime_handler_win.h"
#include "tests/cefclient/browser/resource.h"
#include "tests/shared/browser/geometry_util.h"
#include "tests/shared/browser/main_message_loop.h"
#include "tests/shared/browser/util_win.h"

namespace client {

namespace {

const wchar_t kWndClass[] = L"Client_OsrWindow";

// Render at 30fps during rotation.
const int kRenderDelay = 1000 / 30;

// Helper that calls wglMakeCurrent.
class ScopedGLContext {
 public:
  ScopedGLContext(HDC hdc, HGLRC hglrc, bool swap_buffers)
      : hdc_(hdc), swap_buffers_(swap_buffers) {
    BOOL result = wglMakeCurrent(hdc, hglrc);
    ALLOW_UNUSED_LOCAL(result);
    DCHECK(result);
  }
  ~ScopedGLContext() {
    BOOL result = wglMakeCurrent(NULL, NULL);
    DCHECK(result);
    if (swap_buffers_) {
      result = SwapBuffers(hdc_);
      DCHECK(result);
    }
  }

 private:
  const HDC hdc_;
  const bool swap_buffers_;
};

}  // namespace

OsrWindowWin::OsrWindowWin(Delegate* delegate,
                           const OsrRenderer::Settings& settings)
    : delegate_(delegate),
      renderer_(settings),
      hwnd_(NULL),
      hdc_(NULL),
      hrc_(NULL),
      device_scale_factor_(client::GetDeviceScaleFactor()),
      painting_popup_(false),
      render_task_pending_(false),
      hidden_(false),
      last_mouse_pos_(),
      current_mouse_pos_(),
      mouse_rotation_(false),
      mouse_tracking_(false),
      last_click_x_(0),
      last_click_y_(0),
      last_click_button_(MBT_LEFT),
      last_click_count_(0),
      last_click_time_(0),
      last_mouse_down_on_view_(false) {
  DCHECK(delegate_);
}

OsrWindowWin::~OsrWindowWin() {
  CEF_REQUIRE_UI_THREAD();
  // The native window should have already been destroyed.
  DCHECK(!hwnd_);
}

void OsrWindowWin::CreateBrowser(HWND parent_hwnd,
                                 const RECT& rect,
                                 CefRefPtr<CefClient> handler,
                                 const CefBrowserSettings& settings,
                                 CefRefPtr<CefRequestContext> request_context,
                                 const std::string& startup_url) {
  if (!CefCurrentlyOn(TID_UI)) {
    // Execute this method on the UI thread.
    CefPostTask(TID_UI, base::Bind(&OsrWindowWin::CreateBrowser, this,
                                   parent_hwnd, rect, handler, settings,
                                   request_context, startup_url));
    return;
  }

  // Create the native window.
  Create(parent_hwnd, rect);

  CefWindowInfo window_info;
  window_info.SetAsWindowless(hwnd_);

  // Create the browser asynchronously.
  CefBrowserHost::CreateBrowser(window_info, handler, startup_url, settings,
                                request_context);
}

void OsrWindowWin::ShowPopup(HWND parent_hwnd,
                             int x,
                             int y,
                             size_t width,
                             size_t height) {
  if (!CefCurrentlyOn(TID_UI)) {
    // Execute this method on the UI thread.
    CefPostTask(TID_UI, base::Bind(&OsrWindowWin::ShowPopup, this, parent_hwnd,
                                   x, y, width, height));
    return;
  }

  DCHECK(browser_.get());

  // Create the native window.
  const RECT rect = {x, y, x + static_cast<int>(width),
                     y + static_cast<int>(height)};
  Create(parent_hwnd, rect);

  // Send resize notification so the compositor is assigned the correct
  // viewport size and begins rendering.
  browser_->GetHost()->WasResized();

  Show();
}

void OsrWindowWin::Show() {
  if (!CefCurrentlyOn(TID_UI)) {
    // Execute this method on the UI thread.
    CefPostTask(TID_UI, base::Bind(&OsrWindowWin::Show, this));
    return;
  }

  if (!browser_)
    return;

  // Show the native window if not currently visible.
  if (hwnd_ && !::IsWindowVisible(hwnd_))
    ShowWindow(hwnd_, SW_SHOW);

  if (hidden_) {
    // Set the browser as visible.
    browser_->GetHost()->WasHidden(false);
    hidden_ = false;
  }

  // Give focus to the browser.
  browser_->GetHost()->SendFocusEvent(true);
}

void OsrWindowWin::Hide() {
  if (!CefCurrentlyOn(TID_UI)) {
    // Execute this method on the UI thread.
    CefPostTask(TID_UI, base::Bind(&OsrWindowWin::Hide, this));
    return;
  }

  if (!browser_)
    return;

  // Remove focus from the browser.
  browser_->GetHost()->SendFocusEvent(false);

  if (!hidden_) {
    // Set the browser as hidden.
    browser_->GetHost()->WasHidden(true);
    hidden_ = true;
  }
}

void OsrWindowWin::SetBounds(int x, int y, size_t width, size_t height) {
  if (!CefCurrentlyOn(TID_UI)) {
    // Execute this method on the UI thread.
    CefPostTask(TID_UI, base::Bind(&OsrWindowWin::SetBounds, this, x, y, width,
                                   height));
    return;
  }

  if (hwnd_) {
    // Set the browser window bounds.
    ::SetWindowPos(hwnd_, NULL, x, y, static_cast<int>(width),
                   static_cast<int>(height), SWP_NOZORDER);
  }
}

void OsrWindowWin::SetFocus() {
  if (!CefCurrentlyOn(TID_UI)) {
    // Execute this method on the UI thread.
    CefPostTask(TID_UI, base::Bind(&OsrWindowWin::SetFocus, this));
    return;
  }

  if (hwnd_) {
    // Give focus to the native window.
    ::SetFocus(hwnd_);
  }
}

void OsrWindowWin::SetDeviceScaleFactor(float device_scale_factor) {
  if (!CefCurrentlyOn(TID_UI)) {
    // Execute this method on the UI thread.
    CefPostTask(TID_UI, base::Bind(&OsrWindowWin::SetDeviceScaleFactor, this,
                                   device_scale_factor));
    return;
  }

  if (device_scale_factor == device_scale_factor_)
    return;

  device_scale_factor_ = device_scale_factor;
  if (browser_) {
    browser_->GetHost()->NotifyScreenInfoChanged();
    browser_->GetHost()->WasResized();
  }
}

void OsrWindowWin::Create(HWND parent_hwnd, const RECT& rect) {
  CEF_REQUIRE_UI_THREAD();
  DCHECK(!hwnd_ && !hdc_ && !hrc_);
  DCHECK(parent_hwnd);
  DCHECK(!::IsRectEmpty(&rect));

  HINSTANCE hInst = ::GetModuleHandle(NULL);

  const cef_color_t background_color = MainContext::Get()->GetBackgroundColor();
  const HBRUSH background_brush = CreateSolidBrush(
      RGB(CefColorGetR(background_color), CefColorGetG(background_color),
          CefColorGetB(background_color)));

  RegisterOsrClass(hInst, background_brush);

  // Create the native window with a border so it's easier to visually identify
  // OSR windows.
  hwnd_ = ::CreateWindow(
      kWndClass, 0,
      WS_BORDER | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE,
      rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
      parent_hwnd, 0, hInst, 0);
  CHECK(hwnd_);

  client_rect_ = rect;

  // Associate |this| with the window.
  SetUserDataPtr(hwnd_, this);

#if defined(CEF_USE_ATL)
  accessibility_root_ = NULL;

  // Create/register the drag&drop handler.
  drop_target_ = DropTargetWin::Create(this, hwnd_);
  HRESULT register_res = RegisterDragDrop(hwnd_, drop_target_);
  DCHECK_EQ(register_res, S_OK);
#endif

  ime_handler_.reset(new OsrImeHandlerWin(hwnd_));

  // Notify the window owner.
  NotifyNativeWindowCreated(hwnd_);
}

void OsrWindowWin::Destroy() {
  CEF_REQUIRE_UI_THREAD();
  DCHECK(hwnd_ != NULL);

#if defined(CEF_USE_ATL)
  // Revoke/delete the drag&drop handler.
  RevokeDragDrop(hwnd_);
  drop_target_ = NULL;
#endif

  DisableGL();

  // Destroy the native window.
  ::DestroyWindow(hwnd_);
  ime_handler_.reset();
  hwnd_ = NULL;
}

void OsrWindowWin::EnableGL() {
  CEF_REQUIRE_UI_THREAD();

  PIXELFORMATDESCRIPTOR pfd;
  int format;

  // Get the device context.
  hdc_ = GetDC(hwnd_);

  // Set the pixel format for the DC.
  ZeroMemory(&pfd, sizeof(pfd));
  pfd.nSize = sizeof(pfd);
  pfd.nVersion = 1;
  pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
  pfd.iPixelType = PFD_TYPE_RGBA;
  pfd.cColorBits = 24;
  pfd.cDepthBits = 16;
  pfd.iLayerType = PFD_MAIN_PLANE;
  format = ChoosePixelFormat(hdc_, &pfd);
  SetPixelFormat(hdc_, format, &pfd);

  // Create and enable the render context.
  hrc_ = wglCreateContext(hdc_);

  ScopedGLContext scoped_gl_context(hdc_, hrc_, false);
  renderer_.Initialize();
}

void OsrWindowWin::DisableGL() {
  CEF_REQUIRE_UI_THREAD();

  if (!hdc_)
    return;

  {
    ScopedGLContext scoped_gl_context(hdc_, hrc_, false);
    renderer_.Cleanup();
  }

  if (IsWindow(hwnd_)) {
    // wglDeleteContext will make the context not current before deleting it.
    BOOL result = wglDeleteContext(hrc_);
    ALLOW_UNUSED_LOCAL(result);
    DCHECK(result);
    ReleaseDC(hwnd_, hdc_);
  }

  hdc_ = NULL;
  hrc_ = NULL;
}

void OsrWindowWin::Invalidate() {
  CEF_REQUIRE_UI_THREAD();

  // Don't post another task if the previous task is still pending.
  if (render_task_pending_)
    return;
  render_task_pending_ = true;

  CefPostDelayedTask(TID_UI, base::Bind(&OsrWindowWin::Render, this),
                     kRenderDelay);
}

void OsrWindowWin::Render() {
  CEF_REQUIRE_UI_THREAD();

  if (render_task_pending_)
    render_task_pending_ = false;

  if (!hdc_)
    EnableGL();

  ScopedGLContext scoped_gl_context(hdc_, hrc_, true);
  renderer_.Render();
}

void OsrWindowWin::NotifyNativeWindowCreated(HWND hwnd) {
  if (!CURRENTLY_ON_MAIN_THREAD()) {
    // Execute this method on the main thread.
    MAIN_POST_CLOSURE(
        base::Bind(&OsrWindowWin::NotifyNativeWindowCreated, this, hwnd));
    return;
  }

  delegate_->OnOsrNativeWindowCreated(hwnd);
}

// static
void OsrWindowWin::RegisterOsrClass(HINSTANCE hInstance,
                                    HBRUSH background_brush) {
  // Only register the class one time.
  static bool class_registered = false;
  if (class_registered)
    return;
  class_registered = true;

  WNDCLASSEX wcex;

  wcex.cbSize = sizeof(WNDCLASSEX);
  wcex.style = CS_OWNDC;
  wcex.lpfnWndProc = OsrWndProc;
  wcex.cbClsExtra = 0;
  wcex.cbWndExtra = 0;
  wcex.hInstance = hInstance;
  wcex.hIcon = NULL;
  wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
  wcex.hbrBackground = background_brush;
  wcex.lpszMenuName = NULL;
  wcex.lpszClassName = kWndClass;
  wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

  RegisterClassEx(&wcex);
}

void OsrWindowWin::OnIMESetContext(UINT message, WPARAM wParam, LPARAM lParam) {
  // We handle the IME Composition Window ourselves (but let the IME Candidates
  // Window be handled by IME through DefWindowProc()), so clear the
  // ISC_SHOWUICOMPOSITIONWINDOW flag:
  lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
  ::DefWindowProc(hwnd_, message, wParam, lParam);

  // Create Caret Window if required
  if (ime_handler_) {
    ime_handler_->CreateImeWindow();
    ime_handler_->MoveImeWindow();
  }
}

void OsrWindowWin::OnIMEStartComposition() {
  if (ime_handler_) {
    ime_handler_->CreateImeWindow();
    ime_handler_->MoveImeWindow();
    ime_handler_->ResetComposition();
  }
}

void OsrWindowWin::OnIMEComposition(UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam) {
  if (browser_ && ime_handler_) {
    CefString cTextStr;
    if (ime_handler_->GetResult(lParam, cTextStr)) {
      // Send the text to the browser. The |replacement_range| and
      // |relative_cursor_pos| params are not used on Windows, so provide
      // default invalid values.
      browser_->GetHost()->ImeCommitText(cTextStr,
                                         CefRange(UINT32_MAX, UINT32_MAX), 0);
      ime_handler_->ResetComposition();
      // Continue reading the composition string - Japanese IMEs send both
      // GCS_RESULTSTR and GCS_COMPSTR.
    }

    std::vector<CefCompositionUnderline> underlines;
    int composition_start = 0;

    if (ime_handler_->GetComposition(lParam, cTextStr, underlines,
                                     composition_start)) {
      // Send the composition string to the browser. The |replacement_range|
      // param is not used on Windows, so provide a default invalid value.
      browser_->GetHost()->ImeSetComposition(
          cTextStr, underlines, CefRange(UINT32_MAX, UINT32_MAX),
          CefRange(composition_start,
                   static_cast<int>(composition_start + cTextStr.length())));

      // Update the Candidate Window position. The cursor is at the end so
      // subtract 1. This is safe because IMM32 does not support non-zero-width
      // in a composition. Also,  negative values are safely ignored in
      // MoveImeWindow
      ime_handler_->UpdateCaretPosition(composition_start - 1);
    } else {
      OnIMECancelCompositionEvent();
    }
  }
}

void OsrWindowWin::OnIMECancelCompositionEvent() {
  if (browser_ && ime_handler_) {
    browser_->GetHost()->ImeCancelComposition();
    ime_handler_->ResetComposition();
    ime_handler_->DestroyImeWindow();
  }
}

// static
LRESULT CALLBACK OsrWindowWin::OsrWndProc(HWND hWnd,
                                          UINT message,
                                          WPARAM wParam,
                                          LPARAM lParam) {
  CEF_REQUIRE_UI_THREAD();

  OsrWindowWin* self = GetUserDataPtr<OsrWindowWin*>(hWnd);
  if (!self)
    return DefWindowProc(hWnd, message, wParam, lParam);

  // We want to handle IME events before the OS does any default handling.
  switch (message) {
    case WM_IME_SETCONTEXT:
      self->OnIMESetContext(message, wParam, lParam);
      return 0;
    case WM_IME_STARTCOMPOSITION:
      self->OnIMEStartComposition();
      return 0;
    case WM_IME_COMPOSITION:
      self->OnIMEComposition(message, wParam, lParam);
      return 0;
    case WM_IME_ENDCOMPOSITION:
      self->OnIMECancelCompositionEvent();
      // Let WTL call::DefWindowProc() and release its resources.
      break;
#if defined(CEF_USE_ATL)
    case WM_GETOBJECT: {
      // Only the lower 32 bits of lParam are valid when checking the object id
      // because it sometimes gets sign-extended incorrectly (but not always).
      DWORD obj_id = static_cast<DWORD>(static_cast<DWORD_PTR>(lParam));

      // Accessibility readers will send an OBJID_CLIENT message.
      if (static_cast<DWORD>(OBJID_CLIENT) == obj_id) {
        if (self->accessibility_root_) {
          return LresultFromObject(
              IID_IAccessible, wParam,
              static_cast<IAccessible*>(self->accessibility_root_));
        } else {
          // Notify the renderer to enable accessibility.
          if (self->browser_ && self->browser_->GetHost())
            self->browser_->GetHost()->SetAccessibilityState(STATE_ENABLED);
        }
      }
    } break;
#endif
    case WM_LBUTTONDOWN:
    case WM_RBUTTONDOWN:
    case WM_MBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_RBUTTONUP:
    case WM_MBUTTONUP:
    case WM_MOUSEMOVE:
    case WM_MOUSELEAVE:
    case WM_MOUSEWHEEL:
      self->OnMouseEvent(message, wParam, lParam);
      break;

    case WM_SIZE:
      self->OnSize();
      break;

    case WM_SETFOCUS:
    case WM_KILLFOCUS:
      self->OnFocus(message == WM_SETFOCUS);
      break;

    case WM_CAPTURECHANGED:
    case WM_CANCELMODE:
      self->OnCaptureLost();
      break;

    case WM_SYSCHAR:
    case WM_SYSKEYDOWN:
    case WM_SYSKEYUP:
    case WM_KEYDOWN:
    case WM_KEYUP:
    case WM_CHAR:
      self->OnKeyEvent(message, wParam, lParam);
      break;

    case WM_PAINT:
      self->OnPaint();
      return 0;

    case WM_ERASEBKGND:
      if (self->OnEraseBkgnd())
        break;
      // Don't erase the background.
      return 0;

    case WM_NCDESTROY:
      // Clear the reference to |self|.
      SetUserDataPtr(hWnd, NULL);
      self->hwnd_ = NULL;
      break;
  }

  return DefWindowProc(hWnd, message, wParam, lParam);
}

void OsrWindowWin::OnMouseEvent(UINT message, WPARAM wParam, LPARAM lParam) {
  CefRefPtr<CefBrowserHost> browser_host;
  if (browser_)
    browser_host = browser_->GetHost();

  LONG currentTime = 0;
  bool cancelPreviousClick = false;

  if (message == WM_LBUTTONDOWN || message == WM_RBUTTONDOWN ||
      message == WM_MBUTTONDOWN || message == WM_MOUSEMOVE ||
      message == WM_MOUSELEAVE) {
    currentTime = GetMessageTime();
    int x = GET_X_LPARAM(lParam);
    int y = GET_Y_LPARAM(lParam);
    cancelPreviousClick =
        (abs(last_click_x_ - x) > (GetSystemMetrics(SM_CXDOUBLECLK) / 2)) ||
        (abs(last_click_y_ - y) > (GetSystemMetrics(SM_CYDOUBLECLK) / 2)) ||
        ((currentTime - last_click_time_) > GetDoubleClickTime());
    if (cancelPreviousClick &&
        (message == WM_MOUSEMOVE || message == WM_MOUSELEAVE)) {
      last_click_count_ = 0;
      last_click_x_ = 0;
      last_click_y_ = 0;
      last_click_time_ = 0;
    }
  }

  switch (message) {
    case WM_LBUTTONDOWN:
    case WM_RBUTTONDOWN:
    case WM_MBUTTONDOWN: {
      ::SetCapture(hwnd_);
      ::SetFocus(hwnd_);
      int x = GET_X_LPARAM(lParam);
      int y = GET_Y_LPARAM(lParam);
      if (wParam & MK_SHIFT) {
        // Start rotation effect.
        last_mouse_pos_.x = current_mouse_pos_.x = x;
        last_mouse_pos_.y = current_mouse_pos_.y = y;
        mouse_rotation_ = true;
      } else {
        CefBrowserHost::MouseButtonType btnType =
            (message == WM_LBUTTONDOWN
                 ? MBT_LEFT
                 : (message == WM_RBUTTONDOWN ? MBT_RIGHT : MBT_MIDDLE));
        if (!cancelPreviousClick && (btnType == last_click_button_)) {
          ++last_click_count_;
        } else {
          last_click_count_ = 1;
          last_click_x_ = x;
          last_click_y_ = y;
        }
        last_click_time_ = currentTime;
        last_click_button_ = btnType;

        if (browser_host) {
          CefMouseEvent mouse_event;
          mouse_event.x = x;
          mouse_event.y = y;
          last_mouse_down_on_view_ = !IsOverPopupWidget(x, y);
          ApplyPopupOffset(mouse_event.x, mouse_event.y);
          DeviceToLogical(mouse_event, device_scale_factor_);
          mouse_event.modifiers = GetCefMouseModifiers(wParam);
          browser_host->SendMouseClickEvent(mouse_event, btnType, false,
                                            last_click_count_);
        }
      }
    } break;

    case WM_LBUTTONUP:
    case WM_RBUTTONUP:
    case WM_MBUTTONUP:
      if (GetCapture() == hwnd_)
        ReleaseCapture();
      if (mouse_rotation_) {
        // End rotation effect.
        mouse_rotation_ = false;
        renderer_.SetSpin(0, 0);
        Invalidate();
      } else {
        int x = GET_X_LPARAM(lParam);
        int y = GET_Y_LPARAM(lParam);
        CefBrowserHost::MouseButtonType btnType =
            (message == WM_LBUTTONUP
                 ? MBT_LEFT
                 : (message == WM_RBUTTONUP ? MBT_RIGHT : MBT_MIDDLE));
        if (browser_host) {
          CefMouseEvent mouse_event;
          mouse_event.x = x;
          mouse_event.y = y;
          if (last_mouse_down_on_view_ && IsOverPopupWidget(x, y) &&
              (GetPopupXOffset() || GetPopupYOffset())) {
            break;
          }
          ApplyPopupOffset(mouse_event.x, mouse_event.y);
          DeviceToLogical(mouse_event, device_scale_factor_);
          mouse_event.modifiers = GetCefMouseModifiers(wParam);
          browser_host->SendMouseClickEvent(mouse_event, btnType, true,
                                            last_click_count_);
        }
      }
      break;

    case WM_MOUSEMOVE: {
      int x = GET_X_LPARAM(lParam);
      int y = GET_Y_LPARAM(lParam);
      if (mouse_rotation_) {
        // Apply rotation effect.
        current_mouse_pos_.x = x;
        current_mouse_pos_.y = y;
        renderer_.IncrementSpin(current_mouse_pos_.x - last_mouse_pos_.x,
                                current_mouse_pos_.y - last_mouse_pos_.y);
        last_mouse_pos_.x = current_mouse_pos_.x;
        last_mouse_pos_.y = current_mouse_pos_.y;
        Invalidate();
      } else {
        if (!mouse_tracking_) {
          // Start tracking mouse leave. Required for the WM_MOUSELEAVE event to
          // be generated.
          TRACKMOUSEEVENT tme;
          tme.cbSize = sizeof(TRACKMOUSEEVENT);
          tme.dwFlags = TME_LEAVE;
          tme.hwndTrack = hwnd_;
          TrackMouseEvent(&tme);
          mouse_tracking_ = true;
        }

        if (browser_host) {
          CefMouseEvent mouse_event;
          mouse_event.x = x;
          mouse_event.y = y;
          ApplyPopupOffset(mouse_event.x, mouse_event.y);
          DeviceToLogical(mouse_event, device_scale_factor_);
          mouse_event.modifiers = GetCefMouseModifiers(wParam);
          browser_host->SendMouseMoveEvent(mouse_event, false);
        }
      }
      break;
    }

    case WM_MOUSELEAVE: {
      if (mouse_tracking_) {
        // Stop tracking mouse leave.
        TRACKMOUSEEVENT tme;
        tme.cbSize = sizeof(TRACKMOUSEEVENT);
        tme.dwFlags = TME_LEAVE & TME_CANCEL;
        tme.hwndTrack = hwnd_;
        TrackMouseEvent(&tme);
        mouse_tracking_ = false;
      }

      if (browser_host) {
        // Determine the cursor position in screen coordinates.
        POINT p;
        ::GetCursorPos(&p);
        ::ScreenToClient(hwnd_, &p);

        CefMouseEvent mouse_event;
        mouse_event.x = p.x;
        mouse_event.y = p.y;
        DeviceToLogical(mouse_event, device_scale_factor_);
        mouse_event.modifiers = GetCefMouseModifiers(wParam);
        browser_host->SendMouseMoveEvent(mouse_event, true);
      }
    } break;

    case WM_MOUSEWHEEL:
      if (browser_host) {
        POINT screen_point = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
        HWND scrolled_wnd = ::WindowFromPoint(screen_point);
        if (scrolled_wnd != hwnd_)
          break;

        ScreenToClient(hwnd_, &screen_point);
        int delta = GET_WHEEL_DELTA_WPARAM(wParam);

        CefMouseEvent mouse_event;
        mouse_event.x = screen_point.x;
        mouse_event.y = screen_point.y;
        ApplyPopupOffset(mouse_event.x, mouse_event.y);
        DeviceToLogical(mouse_event, device_scale_factor_);
        mouse_event.modifiers = GetCefMouseModifiers(wParam);
        browser_host->SendMouseWheelEvent(mouse_event,
                                          IsKeyDown(VK_SHIFT) ? delta : 0,
                                          !IsKeyDown(VK_SHIFT) ? delta : 0);
      }
      break;
  }
}

void OsrWindowWin::OnSize() {
  // Keep |client_rect_| up to date.
  ::GetClientRect(hwnd_, &client_rect_);

  if (browser_)
    browser_->GetHost()->WasResized();
}

void OsrWindowWin::OnFocus(bool setFocus) {
  if (browser_)
    browser_->GetHost()->SendFocusEvent(setFocus);
}

void OsrWindowWin::OnCaptureLost() {
  if (mouse_rotation_)
    return;

  if (browser_)
    browser_->GetHost()->SendCaptureLostEvent();
}

void OsrWindowWin::OnKeyEvent(UINT message, WPARAM wParam, LPARAM lParam) {
  if (!browser_)
    return;

  CefKeyEvent event;
  event.windows_key_code = wParam;
  event.native_key_code = lParam;
  event.is_system_key = message == WM_SYSCHAR || message == WM_SYSKEYDOWN ||
                        message == WM_SYSKEYUP;

  if (message == WM_KEYDOWN || message == WM_SYSKEYDOWN)
    event.type = KEYEVENT_RAWKEYDOWN;
  else if (message == WM_KEYUP || message == WM_SYSKEYUP)
    event.type = KEYEVENT_KEYUP;
  else
    event.type = KEYEVENT_CHAR;
  event.modifiers = GetCefKeyboardModifiers(wParam, lParam);

  browser_->GetHost()->SendKeyEvent(event);
}

void OsrWindowWin::OnPaint() {
  // Paint nothing here. Invalidate will cause OnPaint to be called for the
  // render handler.
  PAINTSTRUCT ps;
  BeginPaint(hwnd_, &ps);
  EndPaint(hwnd_, &ps);

  if (browser_)
    browser_->GetHost()->Invalidate(PET_VIEW);
}

bool OsrWindowWin::OnEraseBkgnd() {
  // Erase the background when the browser does not exist.
  return (browser_ == NULL);
}

bool OsrWindowWin::IsOverPopupWidget(int x, int y) const {
  CEF_REQUIRE_UI_THREAD();
  const CefRect& rc = renderer_.popup_rect();
  int popup_right = rc.x + rc.width;
  int popup_bottom = rc.y + rc.height;
  return (x >= rc.x) && (x < popup_right) && (y >= rc.y) && (y < popup_bottom);
}

int OsrWindowWin::GetPopupXOffset() const {
  CEF_REQUIRE_UI_THREAD();
  return renderer_.original_popup_rect().x - renderer_.popup_rect().x;
}

int OsrWindowWin::GetPopupYOffset() const {
  CEF_REQUIRE_UI_THREAD();
  return renderer_.original_popup_rect().y - renderer_.popup_rect().y;
}

void OsrWindowWin::ApplyPopupOffset(int& x, int& y) const {
  if (IsOverPopupWidget(x, y)) {
    x += GetPopupXOffset();
    y += GetPopupYOffset();
  }
}

void OsrWindowWin::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
  CEF_REQUIRE_UI_THREAD();
  DCHECK(!browser_);
  browser_ = browser;

  if (hwnd_) {
    // Show the browser window. Called asynchronously so that the browser has
    // time to create associated internal objects.
    CefPostTask(TID_UI, base::Bind(&OsrWindowWin::Show, this));
  }
}

void OsrWindowWin::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
  CEF_REQUIRE_UI_THREAD();
  // Detach |this| from the ClientHandlerOsr.
  static_cast<ClientHandlerOsr*>(browser_->GetHost()->GetClient().get())
      ->DetachOsrDelegate();
  browser_ = NULL;
  Destroy();
}

bool OsrWindowWin::GetRootScreenRect(CefRefPtr<CefBrowser> browser,
                                     CefRect& rect) {
  CEF_REQUIRE_UI_THREAD();
  return false;
}

bool OsrWindowWin::GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect) {
  CEF_REQUIRE_UI_THREAD();

  rect.x = rect.y = 0;
  rect.width = DeviceToLogical(client_rect_.right - client_rect_.left,
                               device_scale_factor_);
  rect.height = DeviceToLogical(client_rect_.bottom - client_rect_.top,
                                device_scale_factor_);
  return true;
}

bool OsrWindowWin::GetScreenPoint(CefRefPtr<CefBrowser> browser,
                                  int viewX,
                                  int viewY,
                                  int& screenX,
                                  int& screenY) {
  CEF_REQUIRE_UI_THREAD();

  if (!::IsWindow(hwnd_))
    return false;

  // Convert the point from view coordinates to actual screen coordinates.
  POINT screen_pt = {LogicalToDevice(viewX, device_scale_factor_),
                     LogicalToDevice(viewY, device_scale_factor_)};
  ClientToScreen(hwnd_, &screen_pt);
  screenX = screen_pt.x;
  screenY = screen_pt.y;
  return true;
}

bool OsrWindowWin::GetScreenInfo(CefRefPtr<CefBrowser> browser,
                                 CefScreenInfo& screen_info) {
  CEF_REQUIRE_UI_THREAD();

  if (!::IsWindow(hwnd_))
    return false;

  CefRect view_rect;
  GetViewRect(browser, view_rect);

  screen_info.device_scale_factor = device_scale_factor_;

  // The screen info rectangles are used by the renderer to create and position
  // popups. Keep popups inside the view rectangle.
  screen_info.rect = view_rect;
  screen_info.available_rect = view_rect;
  return true;
}

void OsrWindowWin::OnPopupShow(CefRefPtr<CefBrowser> browser, bool show) {
  CEF_REQUIRE_UI_THREAD();

  if (!show) {
    renderer_.ClearPopupRects();
    browser->GetHost()->Invalidate(PET_VIEW);
  }
  renderer_.OnPopupShow(browser, show);
}

void OsrWindowWin::OnPopupSize(CefRefPtr<CefBrowser> browser,
                               const CefRect& rect) {
  CEF_REQUIRE_UI_THREAD();

  renderer_.OnPopupSize(browser, LogicalToDevice(rect, device_scale_factor_));
}

void OsrWindowWin::OnPaint(CefRefPtr<CefBrowser> browser,
                           CefRenderHandler::PaintElementType type,
                           const CefRenderHandler::RectList& dirtyRects,
                           const void* buffer,
                           int width,
                           int height) {
  CEF_REQUIRE_UI_THREAD();

  if (painting_popup_) {
    renderer_.OnPaint(browser, type, dirtyRects, buffer, width, height);
    return;
  }
  if (!hdc_)
    EnableGL();

  ScopedGLContext scoped_gl_context(hdc_, hrc_, true);
  renderer_.OnPaint(browser, type, dirtyRects, buffer, width, height);
  if (type == PET_VIEW && !renderer_.popup_rect().IsEmpty()) {
    painting_popup_ = true;
    browser->GetHost()->Invalidate(PET_POPUP);
    painting_popup_ = false;
  }
  renderer_.Render();
}

void OsrWindowWin::OnCursorChange(CefRefPtr<CefBrowser> browser,
                                  CefCursorHandle cursor,
                                  CefRenderHandler::CursorType type,
                                  const CefCursorInfo& custom_cursor_info) {
  CEF_REQUIRE_UI_THREAD();

  if (!::IsWindow(hwnd_))
    return;

  // Change the plugin window's cursor.
  SetClassLongPtr(hwnd_, GCLP_HCURSOR,
                  static_cast<LONG>(reinterpret_cast<LONG_PTR>(cursor)));
  SetCursor(cursor);
}

bool OsrWindowWin::StartDragging(
    CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefDragData> drag_data,
    CefRenderHandler::DragOperationsMask allowed_ops,
    int x,
    int y) {
  CEF_REQUIRE_UI_THREAD();

#if defined(CEF_USE_ATL)
  if (!drop_target_)
    return false;

  current_drag_op_ = DRAG_OPERATION_NONE;
  CefBrowserHost::DragOperationsMask result =
      drop_target_->StartDragging(browser, drag_data, allowed_ops, x, y);
  current_drag_op_ = DRAG_OPERATION_NONE;
  POINT pt = {};
  GetCursorPos(&pt);
  ScreenToClient(hwnd_, &pt);

  browser->GetHost()->DragSourceEndedAt(
      DeviceToLogical(pt.x, device_scale_factor_),
      DeviceToLogical(pt.y, device_scale_factor_), result);
  browser->GetHost()->DragSourceSystemDragEnded();
  return true;
#else
  // Cancel the drag. The dragging implementation requires ATL support.
  return false;
#endif
}

void OsrWindowWin::UpdateDragCursor(CefRefPtr<CefBrowser> browser,
                                    CefRenderHandler::DragOperation operation) {
  CEF_REQUIRE_UI_THREAD();

#if defined(CEF_USE_ATL)
  current_drag_op_ = operation;
#endif
}

void OsrWindowWin::OnImeCompositionRangeChanged(
    CefRefPtr<CefBrowser> browser,
    const CefRange& selection_range,
    const CefRenderHandler::RectList& character_bounds) {
  CEF_REQUIRE_UI_THREAD();

  if (ime_handler_) {
    // Convert from view coordinates to device coordinates.
    CefRenderHandler::RectList device_bounds;
    CefRenderHandler::RectList::const_iterator it = character_bounds.begin();
    for (; it != character_bounds.end(); ++it) {
      device_bounds.push_back(LogicalToDevice(*it, device_scale_factor_));
    }

    ime_handler_->ChangeCompositionRange(selection_range, device_bounds);
  }
}

void OsrWindowWin::UpdateAccessibilityTree(CefRefPtr<CefValue> value) {
  CEF_REQUIRE_UI_THREAD();

#if defined(CEF_USE_ATL)
  if (!accessibility_handler_) {
    accessibility_handler_.reset(new OsrAccessibilityHelper(value, browser_));
  } else {
    accessibility_handler_->UpdateAccessibilityTree(value);
  }

  // Update |accessibility_root_| because UpdateAccessibilityTree may have
  // cleared it.
  OsrAXNode* root = accessibility_handler_->GetRootNode();
  accessibility_root_ = root ? root->GetNativeAccessibleObject(NULL) : NULL;
#endif  // defined(CEF_USE_ATL)
}

#if defined(CEF_USE_ATL)

CefBrowserHost::DragOperationsMask OsrWindowWin::OnDragEnter(
    CefRefPtr<CefDragData> drag_data,
    CefMouseEvent ev,
    CefBrowserHost::DragOperationsMask effect) {
  if (browser_) {
    DeviceToLogical(ev, device_scale_factor_);
    browser_->GetHost()->DragTargetDragEnter(drag_data, ev, effect);
    browser_->GetHost()->DragTargetDragOver(ev, effect);
  }
  return current_drag_op_;
}

CefBrowserHost::DragOperationsMask OsrWindowWin::OnDragOver(
    CefMouseEvent ev,
    CefBrowserHost::DragOperationsMask effect) {
  if (browser_) {
    DeviceToLogical(ev, device_scale_factor_);
    browser_->GetHost()->DragTargetDragOver(ev, effect);
  }
  return current_drag_op_;
}

void OsrWindowWin::OnDragLeave() {
  if (browser_)
    browser_->GetHost()->DragTargetDragLeave();
}

CefBrowserHost::DragOperationsMask OsrWindowWin::OnDrop(
    CefMouseEvent ev,
    CefBrowserHost::DragOperationsMask effect) {
  if (browser_) {
    DeviceToLogical(ev, device_scale_factor_);
    browser_->GetHost()->DragTargetDragOver(ev, effect);
    browser_->GetHost()->DragTargetDrop(ev);
  }
  return current_drag_op_;
}

#endif  // defined(CEF_USE_ATL)

}  // namespace client
