// 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/browser_window_osr_mac.h"

#include <Cocoa/Cocoa.h>
#include <OpenGL/gl.h>
#import <objc/runtime.h>

#include "include/base/cef_logging.h"
#include "include/cef_parser.h"
#include "include/wrapper/cef_closure_task.h"
#include "tests/cefclient/browser/bytes_write_handler.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/text_input_client_osr_mac.h"
#include "tests/shared/browser/geometry_util.h"
#include "tests/shared/browser/main_message_loop.h"

#import <AppKit/NSAccessibility.h>

namespace {

CefTextInputClientOSRMac* GetInputClientFromContext(
    const NSTextInputContext* context) {
  if (!context)
    return NULL;
  return reinterpret_cast<CefTextInputClientOSRMac*>([context client]);
}

}  // namespace

@interface BrowserOpenGLView
    : NSOpenGLView<NSDraggingSource, NSDraggingDestination, NSAccessibility> {
 @private
  NSTrackingArea* tracking_area_;
  client::BrowserWindowOsrMac* browser_window_;
  client::OsrRenderer* renderer_;
  NSPoint last_mouse_pos_;
  NSPoint cur_mouse_pos_;
  bool rotating_;

  bool was_last_mouse_down_on_view_;

  float device_scale_factor_;

  // Drag and drop.
  CefRefPtr<CefDragData> current_drag_data_;
  NSDragOperation current_drag_op_;
  NSDragOperation current_allowed_ops_;
  NSPasteboard* pasteboard_;
  CFStringRef fileUTI_;

  // For intreacting with IME.
  NSTextInputContext* text_input_context_osr_mac_;

  // Manages Accessibility Tree
  client::OsrAccessibilityHelper* accessibility_helper_;

  // Event monitor for scroll wheel end event.
  id endWheelMonitor_;
}

- (id)initWithFrame:(NSRect)frame
    andBrowserWindow:(client::BrowserWindowOsrMac*)browser_window
         andRenderer:(client::OsrRenderer*)renderer;
- (void)detach;

- (CefRefPtr<CefBrowser>)getBrowser;
- (NSPoint)getClickPointForEvent:(NSEvent*)event;
- (void)getKeyEvent:(CefKeyEvent&)keyEvent forEvent:(NSEvent*)event;
- (void)getMouseEvent:(CefMouseEvent&)mouseEvent forEvent:(NSEvent*)event;
- (void)getMouseEvent:(CefMouseEvent&)mouseEvent
          forDragInfo:(id<NSDraggingInfo>)info;
- (int)getModifiersForEvent:(NSEvent*)event;
- (BOOL)isKeyUpEvent:(NSEvent*)event;
- (BOOL)isKeyPadEvent:(NSEvent*)event;
- (BOOL)startDragging:(CefRefPtr<CefDragData>)drag_data
           allowedOps:(NSDragOperation)ops
                point:(NSPoint)p;
- (void)setCurrentDragOp:(NSDragOperation)op;

- (void)resetDragDrop;
- (void)fillPasteboard;
- (void)populateDropData:(CefRefPtr<CefDragData>)data
          fromPasteboard:(NSPasteboard*)pboard;
- (NSPoint)flipWindowPointToView:(const NSPoint&)windowPoint;
- (void)resetDeviceScaleFactor;
- (void)setDeviceScaleFactor:(float)device_scale_factor;
- (float)getDeviceScaleFactor;
- (void)windowDidChangeBackingProperties:(NSNotification*)notification;

- (bool)isOverPopupWidgetX:(int)x andY:(int)y;
- (void)applyPopupOffsetToX:(int&)x andY:(int&)y;
- (int)getPopupXOffset;
- (int)getPopupYOffset;

- (void)sendMouseClick:(NSEvent*)event
                button:(CefBrowserHost::MouseButtonType)type
                  isUp:(bool)isUp;

- (NSPoint)convertPointFromBackingInternal:(NSPoint)aPoint;
- (NSPoint)convertPointToBackingInternal:(NSPoint)aPoint;
- (NSRect)convertRectFromBackingInternal:(NSRect)aRect;
- (NSRect)convertRectToBackingInternal:(NSRect)aRect;
- (void)ChangeCompositionRange:(CefRange)range
              character_bounds:
                  (const CefRenderHandler::RectList&)character_bounds;
- (void)UpdateAccessibilityTree:(CefRefPtr<CefValue>)value;
@end

namespace {

NSString* const kCEFDragDummyPboardType = @"org.CEF.drag-dummy-type";
NSString* const kNSURLTitlePboardType = @"public.url-name";

class ScopedGLContext {
 public:
  ScopedGLContext(BrowserOpenGLView* view, bool swap_buffers)
      : swap_buffers_(swap_buffers) {
    context_ = [view openGLContext];
    [context_ makeCurrentContext];
  }
  ~ScopedGLContext() {
    [NSOpenGLContext clearCurrentContext];
    if (swap_buffers_)
      [context_ flushBuffer];
  }

 private:
  NSOpenGLContext* context_;
  const bool swap_buffers_;
};

BrowserOpenGLView* GLView(NSView* view) {
  return static_cast<BrowserOpenGLView*>(view);
}

NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) {
  NSRect point_rect = NSMakeRect(point.x, point.y, 0, 0);
  return [window convertRectToScreen:point_rect].origin;
}

}  // namespace

@implementation BrowserOpenGLView

- (id)initWithFrame:(NSRect)frame
    andBrowserWindow:(client::BrowserWindowOsrMac*)browser_window
         andRenderer:(client::OsrRenderer*)renderer {
  NSOpenGLPixelFormat* pixelFormat = [[NSOpenGLPixelFormat alloc]
      initWithAttributes:(NSOpenGLPixelFormatAttribute[]){
                             NSOpenGLPFADoubleBuffer, NSOpenGLPFADepthSize, 32,
                             0}];
  [pixelFormat autorelease];

  if (self = [super initWithFrame:frame pixelFormat:pixelFormat]) {
    browser_window_ = browser_window;
    renderer_ = renderer;
    rotating_ = false;
    endWheelMonitor_ = nil;
    device_scale_factor_ = 1.0f;

    tracking_area_ = [[NSTrackingArea alloc]
        initWithRect:frame
             options:NSTrackingMouseMoved | NSTrackingActiveInActiveApp |
                     NSTrackingInVisibleRect
               owner:self
            userInfo:nil];
    [self addTrackingArea:tracking_area_];

    // enable HiDPI buffer
    [self setWantsBestResolutionOpenGLSurface:YES];

    [self resetDragDrop];

    NSArray* types = [NSArray
        arrayWithObjects:kCEFDragDummyPboardType, NSStringPboardType,
                         NSFilenamesPboardType, NSPasteboardTypeString, nil];
    [self registerForDraggedTypes:types];
  }

  return self;
}

- (void)dealloc {
  [[NSNotificationCenter defaultCenter]
      removeObserver:self
                name:NSWindowDidChangeBackingPropertiesNotification
              object:nil];

  if (text_input_context_osr_mac_) {
    [GetInputClientFromContext(text_input_context_osr_mac_) release];
    [text_input_context_osr_mac_ release];
  }

  [super dealloc];
}

- (void)detach {
  renderer_ = NULL;
  browser_window_ = NULL;
  if (text_input_context_osr_mac_)
    [GetInputClientFromContext(text_input_context_osr_mac_) detach];
}

- (CefRefPtr<CefBrowser>)getBrowser {
  if (browser_window_)
    return browser_window_->GetBrowser();
  return NULL;
}

- (void)setFrame:(NSRect)frameRect {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (!browser.get())
    return;

  [super setFrame:frameRect];
  browser->GetHost()->WasResized();
}

- (void)sendMouseClick:(NSEvent*)event
                button:(CefBrowserHost::MouseButtonType)type
                  isUp:(bool)isUp {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (!browser.get())
    return;

  CefMouseEvent mouseEvent;
  [self getMouseEvent:mouseEvent forEvent:event];

  // |point| is in OS X view coordinates.
  NSPoint point = [self getClickPointForEvent:event];

  // Convert to device coordinates.
  point = [self convertPointToBackingInternal:point];

  if (!isUp) {
    was_last_mouse_down_on_view_ =
        ![self isOverPopupWidgetX:point.x andY:point.y];
  } else if (was_last_mouse_down_on_view_ &&
             [self isOverPopupWidgetX:point.x andY:point.y] &&
             ([self getPopupXOffset] || [self getPopupYOffset])) {
    return;
  }

  browser->GetHost()->SendMouseClickEvent(mouseEvent, type, isUp,
                                          [event clickCount]);
}

- (void)mouseDown:(NSEvent*)event {
  [self sendMouseClick:event button:MBT_LEFT isUp:false];
}

- (void)rightMouseDown:(NSEvent*)event {
  if ([event modifierFlags] & NSShiftKeyMask) {
    // Start rotation effect.
    last_mouse_pos_ = cur_mouse_pos_ = [self getClickPointForEvent:event];
    rotating_ = true;
    return;
  }

  [self sendMouseClick:event button:MBT_RIGHT isUp:false];
}

- (void)otherMouseDown:(NSEvent*)event {
  [self sendMouseClick:event button:MBT_MIDDLE isUp:false];
}

- (void)mouseUp:(NSEvent*)event {
  [self sendMouseClick:event button:MBT_LEFT isUp:true];
}

- (void)rightMouseUp:(NSEvent*)event {
  if (rotating_) {
    // End rotation effect.
    renderer_->SetSpin(0, 0);
    rotating_ = false;
    [self setNeedsDisplay:YES];
    return;
  }
  [self sendMouseClick:event button:MBT_RIGHT isUp:true];
}

- (void)otherMouseUp:(NSEvent*)event {
  [self sendMouseClick:event button:MBT_MIDDLE isUp:true];
}

- (void)mouseMoved:(NSEvent*)event {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (!browser.get())
    return;

  if (rotating_) {
    // Apply rotation effect.
    cur_mouse_pos_ = [self getClickPointForEvent:event];
    ;
    renderer_->IncrementSpin((cur_mouse_pos_.x - last_mouse_pos_.x),
                             (cur_mouse_pos_.y - last_mouse_pos_.y));
    last_mouse_pos_ = cur_mouse_pos_;
    [self setNeedsDisplay:YES];
    return;
  }

  CefMouseEvent mouseEvent;
  [self getMouseEvent:mouseEvent forEvent:event];

  browser->GetHost()->SendMouseMoveEvent(mouseEvent, false);
}

- (void)mouseDragged:(NSEvent*)event {
  [self mouseMoved:event];
}

- (void)rightMouseDragged:(NSEvent*)event {
  [self mouseMoved:event];
}

- (void)otherMouseDragged:(NSEvent*)event {
  [self mouseMoved:event];
}

- (void)mouseEntered:(NSEvent*)event {
  [self mouseMoved:event];
}

- (void)mouseExited:(NSEvent*)event {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (!browser.get())
    return;

  CefMouseEvent mouseEvent;
  [self getMouseEvent:mouseEvent forEvent:event];

  browser->GetHost()->SendMouseMoveEvent(mouseEvent, true);
}

- (void)keyDown:(NSEvent*)event {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (!browser.get() || !text_input_context_osr_mac_)
    return;

  if ([event type] != NSFlagsChanged) {
    CefTextInputClientOSRMac* client =
        GetInputClientFromContext(text_input_context_osr_mac_);

    if (client) {
      [client HandleKeyEventBeforeTextInputClient:event];

      // The return value of this method seems to always be set to YES, thus we
      // ignore it and ask the host view whether IME is active or not.
      [text_input_context_osr_mac_ handleEvent:event];

      CefKeyEvent keyEvent;
      [self getKeyEvent:keyEvent forEvent:event];

      [client HandleKeyEventAfterTextInputClient:keyEvent];
    }
  }
}

- (void)keyUp:(NSEvent*)event {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (!browser.get())
    return;

  CefKeyEvent keyEvent;
  [self getKeyEvent:keyEvent forEvent:event];

  keyEvent.type = KEYEVENT_KEYUP;
  browser->GetHost()->SendKeyEvent(keyEvent);
}

- (void)flagsChanged:(NSEvent*)event {
  if ([self isKeyUpEvent:event])
    [self keyUp:event];
  else
    [self keyDown:event];
}

- (void)shortCircuitScrollWheelEvent:(NSEvent*)event {
  if ([event phase] != NSEventPhaseEnded &&
      [event phase] != NSEventPhaseCancelled)
    return;

  [self sendScrollWheelEvet:event];

  if (endWheelMonitor_) {
    [NSEvent removeMonitor:endWheelMonitor_];
    endWheelMonitor_ = nil;
  }
}

- (void)scrollWheel:(NSEvent*)event {
  // Use an NSEvent monitor to listen for the wheel-end end. This ensures that
  // the event is received even when the mouse cursor is no longer over the
  // view when the scrolling ends. Also it avoids sending duplicate scroll
  // events to the renderer.
  if ([event phase] == NSEventPhaseBegan && !endWheelMonitor_) {
    endWheelMonitor_ = [NSEvent
        addLocalMonitorForEventsMatchingMask:NSScrollWheelMask
                                     handler:^(NSEvent* blockEvent) {
                                       [self shortCircuitScrollWheelEvent:
                                                 blockEvent];
                                       return blockEvent;
                                     }];
  }

  [self sendScrollWheelEvet:event];
}

- (void)sendScrollWheelEvet:(NSEvent*)event {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (!browser.get())
    return;

  CGEventRef cgEvent = [event CGEvent];
  DCHECK(cgEvent);

  int deltaX =
      CGEventGetIntegerValueField(cgEvent, kCGScrollWheelEventPointDeltaAxis2);
  int deltaY =
      CGEventGetIntegerValueField(cgEvent, kCGScrollWheelEventPointDeltaAxis1);

  CefMouseEvent mouseEvent;
  [self getMouseEvent:mouseEvent forEvent:event];

  browser->GetHost()->SendMouseWheelEvent(mouseEvent, deltaX, deltaY);
}

- (BOOL)canBecomeKeyView {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  return (browser.get() != NULL);
}

- (BOOL)acceptsFirstResponder {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  return (browser.get() != NULL);
}

- (BOOL)becomeFirstResponder {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (browser.get()) {
    browser->GetHost()->SendFocusEvent(true);
    return [super becomeFirstResponder];
  }

  return NO;
}

- (BOOL)resignFirstResponder {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (browser.get()) {
    browser->GetHost()->SendFocusEvent(false);
    return [super resignFirstResponder];
  }

  return NO;
}

- (void)undo:(id)sender {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (browser.get())
    browser->GetFocusedFrame()->Undo();
}

- (void)redo:(id)sender {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (browser.get())
    browser->GetFocusedFrame()->Redo();
}

- (void)cut:(id)sender {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (browser.get())
    browser->GetFocusedFrame()->Cut();
}

- (void)copy:(id)sender {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (browser.get())
    browser->GetFocusedFrame()->Copy();
}

- (void)paste:(id)sender {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (browser.get())
    browser->GetFocusedFrame()->Paste();
}

- (void) delete:(id)sender {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (browser.get())
    browser->GetFocusedFrame()->Delete();
}

- (void)selectAll:(id)sender {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (browser.get())
    browser->GetFocusedFrame()->SelectAll();
}

- (NSPoint)getClickPointForEvent:(NSEvent*)event {
  NSPoint windowLocal = [event locationInWindow];
  NSPoint contentLocal = [self convertPoint:windowLocal fromView:nil];

  NSPoint point;
  point.x = contentLocal.x;
  point.y = [self frame].size.height - contentLocal.y;  // Flip y.
  return point;
}

- (void)getKeyEvent:(CefKeyEvent&)keyEvent forEvent:(NSEvent*)event {
  if ([event type] == NSKeyDown || [event type] == NSKeyUp) {
    NSString* s = [event characters];
    if ([s length] > 0)
      keyEvent.character = [s characterAtIndex:0];

    s = [event charactersIgnoringModifiers];
    if ([s length] > 0)
      keyEvent.unmodified_character = [s characterAtIndex:0];
  }

  if ([event type] == NSFlagsChanged) {
    keyEvent.character = 0;
    keyEvent.unmodified_character = 0;
  }

  keyEvent.native_key_code = [event keyCode];

  keyEvent.modifiers = [self getModifiersForEvent:event];
}

- (NSTextInputContext*)inputContext {
  if (!text_input_context_osr_mac_) {
    CefTextInputClientOSRMac* text_input_client =
        [[[CefTextInputClientOSRMac alloc] initWithBrowser:[self getBrowser]]
            retain];

    text_input_context_osr_mac_ =
        [[[NSTextInputContext alloc] initWithClient:text_input_client] retain];
  }

  return text_input_context_osr_mac_;
}

- (void)getMouseEvent:(CefMouseEvent&)mouseEvent forEvent:(NSEvent*)event {
  const float device_scale_factor = [self getDeviceScaleFactor];

  // |point| is in OS X view coordinates.
  NSPoint point = [self getClickPointForEvent:event];

  // Convert to device coordinates.
  point = [self convertPointToBackingInternal:point];

  int device_x = point.x;
  int device_y = point.y;
  if ([self isOverPopupWidgetX:device_x andY:device_y])
    [self applyPopupOffsetToX:device_x andY:device_y];

  // Convert to browser view coordinates.
  mouseEvent.x = client::DeviceToLogical(device_x, device_scale_factor);
  mouseEvent.y = client::DeviceToLogical(device_y, device_scale_factor);

  mouseEvent.modifiers = [self getModifiersForEvent:event];
}

- (void)getMouseEvent:(CefMouseEvent&)mouseEvent
          forDragInfo:(id<NSDraggingInfo>)info {
  const float device_scale_factor = [self getDeviceScaleFactor];

  // |point| is in OS X view coordinates.
  NSPoint windowPoint = [info draggingLocation];
  NSPoint point = [self flipWindowPointToView:windowPoint];

  // Convert to device coordinates.
  point = [self convertPointToBackingInternal:point];

  // Convert to browser view coordinates.
  mouseEvent.x = client::DeviceToLogical(point.x, device_scale_factor);
  mouseEvent.y = client::DeviceToLogical(point.y, device_scale_factor);

  mouseEvent.modifiers = [NSEvent modifierFlags];
}

- (int)getModifiersForEvent:(NSEvent*)event {
  int modifiers = 0;

  if ([event modifierFlags] & NSControlKeyMask)
    modifiers |= EVENTFLAG_CONTROL_DOWN;
  if ([event modifierFlags] & NSShiftKeyMask)
    modifiers |= EVENTFLAG_SHIFT_DOWN;
  if ([event modifierFlags] & NSAlternateKeyMask)
    modifiers |= EVENTFLAG_ALT_DOWN;
  if ([event modifierFlags] & NSCommandKeyMask)
    modifiers |= EVENTFLAG_COMMAND_DOWN;
  if ([event modifierFlags] & NSAlphaShiftKeyMask)
    modifiers |= EVENTFLAG_CAPS_LOCK_ON;

  if ([event type] == NSKeyUp || [event type] == NSKeyDown ||
      [event type] == NSFlagsChanged) {
    // Only perform this check for key events
    if ([self isKeyPadEvent:event])
      modifiers |= EVENTFLAG_IS_KEY_PAD;
  }

  // OS X does not have a modifier for NumLock, so I'm not entirely sure how to
  // set EVENTFLAG_NUM_LOCK_ON;
  //
  // There is no EVENTFLAG for the function key either.

  // Mouse buttons
  switch ([event type]) {
    case NSLeftMouseDragged:
    case NSLeftMouseDown:
    case NSLeftMouseUp:
      modifiers |= EVENTFLAG_LEFT_MOUSE_BUTTON;
      break;
    case NSRightMouseDragged:
    case NSRightMouseDown:
    case NSRightMouseUp:
      modifiers |= EVENTFLAG_RIGHT_MOUSE_BUTTON;
      break;
    case NSOtherMouseDragged:
    case NSOtherMouseDown:
    case NSOtherMouseUp:
      modifiers |= EVENTFLAG_MIDDLE_MOUSE_BUTTON;
      break;
    default:
      break;
  }

  return modifiers;
}

- (BOOL)isKeyUpEvent:(NSEvent*)event {
  if ([event type] != NSFlagsChanged)
    return [event type] == NSKeyUp;

  // FIXME: This logic fails if the user presses both Shift keys at once, for
  // example: we treat releasing one of them as keyDown.
  switch ([event keyCode]) {
    case 54:  // Right Command
    case 55:  // Left Command
      return ([event modifierFlags] & NSCommandKeyMask) == 0;

    case 57:  // Capslock
      return ([event modifierFlags] & NSAlphaShiftKeyMask) == 0;

    case 56:  // Left Shift
    case 60:  // Right Shift
      return ([event modifierFlags] & NSShiftKeyMask) == 0;

    case 58:  // Left Alt
    case 61:  // Right Alt
      return ([event modifierFlags] & NSAlternateKeyMask) == 0;

    case 59:  // Left Ctrl
    case 62:  // Right Ctrl
      return ([event modifierFlags] & NSControlKeyMask) == 0;

    case 63:  // Function
      return ([event modifierFlags] & NSFunctionKeyMask) == 0;
  }
  return false;
}

- (BOOL)isKeyPadEvent:(NSEvent*)event {
  if ([event modifierFlags] & NSNumericPadKeyMask)
    return true;

  switch ([event keyCode]) {
    case 71:  // Clear
    case 81:  // =
    case 75:  // /
    case 67:  // *
    case 78:  // -
    case 69:  // +
    case 76:  // Enter
    case 65:  // .
    case 82:  // 0
    case 83:  // 1
    case 84:  // 2
    case 85:  // 3
    case 86:  // 4
    case 87:  // 5
    case 88:  // 6
    case 89:  // 7
    case 91:  // 8
    case 92:  // 9
      return true;
  }

  return false;
}

- (void)windowDidChangeBackingProperties:(NSNotification*)notification {
  // This delegate method is only called on 10.7 and later, so don't worry about
  // other backing changes calling it on 10.6 or earlier
  [self resetDeviceScaleFactor];
}

- (void)drawRect:(NSRect)dirtyRect {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if ([self inLiveResize] || !browser.get()) {
    // Fill with the background color.
    const cef_color_t background_color =
        client::MainContext::Get()->GetBackgroundColor();
    NSColor* color = [NSColor
        colorWithCalibratedRed:float(CefColorGetR(background_color)) / 255.0f
                         green:float(CefColorGetG(background_color)) / 255.0f
                          blue:float(CefColorGetB(background_color)) / 255.0f
                         alpha:1.f];
    [color setFill];
    NSRectFill(dirtyRect);
  }

  // The Invalidate below fixes flicker when resizing.
  if ([self inLiveResize] && browser.get())
    browser->GetHost()->Invalidate(PET_VIEW);
}

// Drag and drop

- (BOOL)startDragging:(CefRefPtr<CefDragData>)drag_data
           allowedOps:(NSDragOperation)ops
                point:(NSPoint)position {
  DCHECK(!pasteboard_);
  DCHECK(!fileUTI_);
  DCHECK(!current_drag_data_.get());

  [self resetDragDrop];

  current_allowed_ops_ = ops;
  current_drag_data_ = drag_data;

  [self fillPasteboard];

  NSEvent* currentEvent = [[NSApplication sharedApplication] currentEvent];
  NSWindow* window = [self window];
  NSTimeInterval eventTime = [currentEvent timestamp];

  NSEvent* dragEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged
                                          location:position
                                     modifierFlags:NSLeftMouseDraggedMask
                                         timestamp:eventTime
                                      windowNumber:[window windowNumber]
                                           context:nil
                                       eventNumber:0
                                        clickCount:1
                                          pressure:1.0];

  // TODO(cef): Pass a non-nil value to dragImage (see issue #1715). For now
  // work around the "callee requires a non-null argument" error that occurs
  // when building with the 10.11 SDK.
  id nilArg = nil;
  [window dragImage:nilArg
                 at:position
             offset:NSZeroSize
              event:dragEvent
         pasteboard:pasteboard_
             source:self
          slideBack:YES];
  return YES;
}

- (void)setCurrentDragOp:(NSDragOperation)op {
  current_drag_op_ = op;
}

// NSDraggingSource Protocol

- (NSDragOperation)draggingSession:(NSDraggingSession*)session
    sourceOperationMaskForDraggingContext:(NSDraggingContext)context {
  switch (context) {
    case NSDraggingContextOutsideApplication:
      return current_allowed_ops_;

    case NSDraggingContextWithinApplication:
    default:
      return current_allowed_ops_;
  }
}

- (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDest {
  if (![dropDest isFileURL])
    return nil;

  if (!current_drag_data_)
    return nil;

  size_t expected_size = current_drag_data_->GetFileContents(NULL);
  if (expected_size == 0)
    return nil;

  std::string path = [[dropDest path] UTF8String];
  path.append("/");
  path.append(current_drag_data_->GetFileName().ToString());

  CefRefPtr<CefStreamWriter> writer = CefStreamWriter::CreateForFile(path);
  if (!writer)
    return nil;

  if (current_drag_data_->GetFileContents(writer) != expected_size)
    return nil;

  return @[ [NSString stringWithUTF8String:path.c_str()] ];
}

- (void)draggedImage:(NSImage*)anImage
             endedAt:(NSPoint)screenPoint
           operation:(NSDragOperation)operation {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (!browser.get())
    return;

  if (operation == (NSDragOperationMove | NSDragOperationCopy))
    operation &= ~NSDragOperationMove;

  NSPoint windowPoint = [[self window] convertScreenToBase:screenPoint];
  NSPoint pt = [self flipWindowPointToView:windowPoint];
  CefRenderHandler::DragOperation op =
      static_cast<CefRenderHandler::DragOperation>(operation);
  browser->GetHost()->DragSourceEndedAt(pt.x, pt.y, op);
  browser->GetHost()->DragSourceSystemDragEnded();
  [self resetDragDrop];
}

// NSDraggingDestination Protocol

- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (!browser.get())
    return NSDragOperationNone;

  CefRefPtr<CefDragData> drag_data;
  if (!current_drag_data_) {
    drag_data = CefDragData::Create();
    [self populateDropData:drag_data fromPasteboard:[info draggingPasteboard]];
  } else {
    drag_data = current_drag_data_->Clone();
    drag_data->ResetFileContents();
  }

  CefMouseEvent mouseEvent;
  [self getMouseEvent:mouseEvent forDragInfo:info];

  NSDragOperation mask = [info draggingSourceOperationMask];
  CefBrowserHost::DragOperationsMask allowed_ops =
      static_cast<CefBrowserHost::DragOperationsMask>(mask);

  browser->GetHost()->DragTargetDragEnter(drag_data, mouseEvent, allowed_ops);
  browser->GetHost()->DragTargetDragOver(mouseEvent, allowed_ops);

  current_drag_op_ = NSDragOperationCopy;
  return current_drag_op_;
}

- (void)draggingExited:(id<NSDraggingInfo>)sender {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (browser.get())
    browser->GetHost()->DragTargetDragLeave();
}

- (BOOL)prepareForDragOperation:(id<NSDraggingInfo>)info {
  return YES;
}

- (BOOL)performDragOperation:(id<NSDraggingInfo>)info {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (!browser.get())
    return NO;

  CefMouseEvent mouseEvent;
  [self getMouseEvent:mouseEvent forDragInfo:info];

  browser->GetHost()->DragTargetDrop(mouseEvent);

  return YES;
}

- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info {
  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (!browser.get())
    return NSDragOperationNone;

  CefMouseEvent mouseEvent;
  [self getMouseEvent:mouseEvent forDragInfo:info];

  NSDragOperation mask = [info draggingSourceOperationMask];
  CefBrowserHost::DragOperationsMask allowed_ops =
      static_cast<CefBrowserHost::DragOperationsMask>(mask);

  browser->GetHost()->DragTargetDragOver(mouseEvent, allowed_ops);

  return current_drag_op_;
}

// NSPasteboardOwner Protocol

- (void)pasteboard:(NSPasteboard*)pboard provideDataForType:(NSString*)type {
  if (!current_drag_data_) {
    return;
  }

  // URL.
  if ([type isEqualToString:NSURLPboardType]) {
    DCHECK(current_drag_data_->IsLink());
    NSString* strUrl =
        [NSString stringWithUTF8String:current_drag_data_->GetLinkURL()
                                           .ToString()
                                           .c_str()];
    NSURL* url = [NSURL URLWithString:strUrl];
    [url writeToPasteboard:pboard];
    // URL title.
  } else if ([type isEqualToString:kNSURLTitlePboardType]) {
    NSString* strTitle =
        [NSString stringWithUTF8String:current_drag_data_->GetLinkTitle()
                                           .ToString()
                                           .c_str()];
    [pboard setString:strTitle forType:kNSURLTitlePboardType];

    // File contents.
  } else if ([type isEqualToString:(NSString*)fileUTI_]) {
    size_t size = current_drag_data_->GetFileContents(NULL);
    DCHECK_GT(size, 0U);
    CefRefPtr<client::BytesWriteHandler> handler =
        new client::BytesWriteHandler(size);
    CefRefPtr<CefStreamWriter> writer =
        CefStreamWriter::CreateForHandler(handler.get());
    current_drag_data_->GetFileContents(writer);
    DCHECK_EQ(handler->GetDataSize(), static_cast<int64>(size));

    [pboard setData:[NSData dataWithBytes:handler->GetData()
                                   length:handler->GetDataSize()]
            forType:(NSString*)fileUTI_];

    // Plain text.
  } else if ([type isEqualToString:NSStringPboardType]) {
    NSString* strTitle =
        [NSString stringWithUTF8String:current_drag_data_->GetFragmentText()
                                           .ToString()
                                           .c_str()];
    [pboard setString:strTitle forType:NSStringPboardType];

  } else if ([type isEqualToString:kCEFDragDummyPboardType]) {
    // The dummy type _was_ promised and someone decided to call the bluff.
    [pboard setData:[NSData data] forType:kCEFDragDummyPboardType];
  }
}

// NSAccessibility Protocol implementation.
- (BOOL)accessibilityIsIgnored {
  if (!accessibility_helper_)
    return YES;
  else
    return NO;
}

- (id)accessibilityAttributeValue:(NSString*)attribute {
  if (!accessibility_helper_)
    return [super accessibilityAttributeValue:attribute];
  if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
    return NSAccessibilityGroupRole;
  } else if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute]) {
    client::OsrAXNode* node = accessibility_helper_->GetRootNode();
    std::string desc = node ? node->AxDescription() : "";
    return [NSString stringWithUTF8String:desc.c_str()];
  } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
    client::OsrAXNode* node = accessibility_helper_->GetRootNode();
    std::string desc = node ? node->AxValue() : "";
    return [NSString stringWithUTF8String:desc.c_str()];
  } else if ([attribute
                 isEqualToString:NSAccessibilityRoleDescriptionAttribute]) {
    return NSAccessibilityRoleDescriptionForUIElement(self);
  } else if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
    client::OsrAXNode* node = accessibility_helper_->GetRootNode();
    // Add Root as first Kid
    NSMutableArray* kids = [NSMutableArray arrayWithCapacity:1];
    NSObject* child = node->GetNativeAccessibleObject(NULL);
    [kids addObject:child];
    return NSAccessibilityUnignoredChildren(kids);
  } else {
    return [super accessibilityAttributeValue:attribute];
  }
}

- (id)accessibilityFocusedUIElement {
  if (accessibility_helper_) {
    client::OsrAXNode* node = accessibility_helper_->GetFocusedNode();
    return node ? node->GetNativeAccessibleObject(NULL) : nil;
  }
  return nil;
}

// Utility methods.
- (void)resetDragDrop {
  current_drag_op_ = NSDragOperationNone;
  current_allowed_ops_ = NSDragOperationNone;
  current_drag_data_ = NULL;
  if (fileUTI_) {
    CFRelease(fileUTI_);
    fileUTI_ = NULL;
  }
  if (pasteboard_) {
    [pasteboard_ release];
    pasteboard_ = nil;
  }
}

- (void)fillPasteboard {
  DCHECK(!pasteboard_);
  pasteboard_ = [[NSPasteboard pasteboardWithName:NSDragPboard] retain];

  [pasteboard_ declareTypes:@[ kCEFDragDummyPboardType ] owner:self];

  // URL (and title).
  if (current_drag_data_->IsLink()) {
    [pasteboard_ addTypes:@[ NSURLPboardType, kNSURLTitlePboardType ]
                    owner:self];
  }

  // MIME type.
  CefString mimeType;
  size_t contents_size = current_drag_data_->GetFileContents(NULL);
  CefString download_metadata = current_drag_data_->GetLinkMetadata();
  CefString file_name = current_drag_data_->GetFileName();

  // File.
  if (contents_size > 0) {
    std::string file_name = current_drag_data_->GetFileName().ToString();
    size_t sep = file_name.find_last_of(".");
    CefString extension = file_name.substr(sep + 1);

    mimeType = CefGetMimeType(extension);

    if (!mimeType.empty()) {
      CFStringRef mimeTypeCF;
      mimeTypeCF = CFStringCreateWithCString(kCFAllocatorDefault,
                                             mimeType.ToString().c_str(),
                                             kCFStringEncodingUTF8);
      fileUTI_ = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType,
                                                       mimeTypeCF, NULL);
      CFRelease(mimeTypeCF);
      // File (HFS) promise.
      NSArray* fileUTIList = @[ (NSString*)fileUTI_ ];
      [pasteboard_ addTypes:@[ NSFilesPromisePboardType ] owner:self];
      [pasteboard_ setPropertyList:fileUTIList
                           forType:NSFilesPromisePboardType];

      [pasteboard_ addTypes:fileUTIList owner:self];
    }
  }

  // Plain text.
  if (!current_drag_data_->GetFragmentText().empty()) {
    [pasteboard_ addTypes:@[ NSStringPboardType ] owner:self];
  }
}

- (void)populateDropData:(CefRefPtr<CefDragData>)data
          fromPasteboard:(NSPasteboard*)pboard {
  DCHECK(data);
  DCHECK(pboard);
  DCHECK(data && !data->IsReadOnly());
  NSArray* types = [pboard types];

  // Get plain text.
  if ([types containsObject:NSStringPboardType]) {
    data->SetFragmentText(
        [[pboard stringForType:NSStringPboardType] UTF8String]);
  }

  // Get files.
  if ([types containsObject:NSFilenamesPboardType]) {
    NSArray* files = [pboard propertyListForType:NSFilenamesPboardType];
    if ([files isKindOfClass:[NSArray class]] && [files count]) {
      for (NSUInteger i = 0; i < [files count]; i++) {
        NSString* filename = [files objectAtIndex:i];
        BOOL exists =
            [[NSFileManager defaultManager] fileExistsAtPath:filename];
        if (exists) {
          data->AddFile([filename UTF8String], CefString());
        }
      }
    }
  }
}

- (NSPoint)flipWindowPointToView:(const NSPoint&)windowPoint {
  NSPoint viewPoint = [self convertPoint:windowPoint fromView:nil];
  NSRect viewFrame = [self frame];
  viewPoint.y = viewFrame.size.height - viewPoint.y;
  return viewPoint;
}

- (void)resetDeviceScaleFactor {
  float device_scale_factor = 1.0f;
  NSWindow* window = [self window];
  if (window)
    device_scale_factor = [window backingScaleFactor];
  [self setDeviceScaleFactor:device_scale_factor];
}

- (void)setDeviceScaleFactor:(float)device_scale_factor {
  if (device_scale_factor == device_scale_factor_)
    return;

  // Apply some sanity checks.
  if (device_scale_factor < 1.0f || device_scale_factor > 4.0f)
    return;

  device_scale_factor_ = device_scale_factor;

  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (browser) {
    browser->GetHost()->NotifyScreenInfoChanged();
    browser->GetHost()->WasResized();
  }
}

- (float)getDeviceScaleFactor {
  return device_scale_factor_;
}

- (void)viewDidChangeBackingProperties {
  const CGFloat device_scale_factor = [self getDeviceScaleFactor];

  if (device_scale_factor == device_scale_factor_)
    return;

  CefRefPtr<CefBrowser> browser = [self getBrowser];
  if (browser) {
    browser->GetHost()->NotifyScreenInfoChanged();
    browser->GetHost()->WasResized();
  }
}

- (bool)isOverPopupWidgetX:(int)x andY:(int)y {
  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)getPopupXOffset {
  return renderer_->original_popup_rect().x - renderer_->popup_rect().x;
}

- (int)getPopupYOffset {
  return renderer_->original_popup_rect().y - renderer_->popup_rect().y;
}

- (void)applyPopupOffsetToX:(int&)x andY:(int&)y {
  if ([self isOverPopupWidgetX:x andY:y]) {
    x += [self getPopupXOffset];
    y += [self getPopupYOffset];
  }
}

// Convert from scaled coordinates to view coordinates.
- (NSPoint)convertPointFromBackingInternal:(NSPoint)aPoint {
  return [self convertPointFromBacking:aPoint];
}

// Convert from view coordinates to scaled coordinates.
- (NSPoint)convertPointToBackingInternal:(NSPoint)aPoint {
  return [self convertPointToBacking:aPoint];
}

// Convert from scaled coordinates to view coordinates.
- (NSRect)convertRectFromBackingInternal:(NSRect)aRect {
  return [self convertRectFromBacking:aRect];
}

// Convert from view coordinates to scaled coordinates.
- (NSRect)convertRectToBackingInternal:(NSRect)aRect {
  return [self convertRectToBacking:aRect];
}

- (void)ChangeCompositionRange:(CefRange)range
              character_bounds:(const CefRenderHandler::RectList&)bounds {
  CefTextInputClientOSRMac* client =
      GetInputClientFromContext(text_input_context_osr_mac_);
  if (client)
    [client ChangeCompositionRange:range character_bounds:bounds];
}

- (void)UpdateAccessibilityTree:(CefRefPtr<CefValue>)value {
  if (!accessibility_helper_) {
    accessibility_helper_ =
        new client::OsrAccessibilityHelper(value, [self getBrowser]);
  } else {
    accessibility_helper_->UpdateAccessibilityTree(value);
  }

  if (accessibility_helper_) {
    NSAccessibilityPostNotification(self,
                                    NSAccessibilityValueChangedNotification);
  }
  return;
}
@end

namespace client {

BrowserWindowOsrMac::BrowserWindowOsrMac(BrowserWindow::Delegate* delegate,
                                         const std::string& startup_url,
                                         const OsrRenderer::Settings& settings)
    : BrowserWindow(delegate),
      renderer_(settings),
      nsview_(NULL),
      hidden_(false),
      painting_popup_(false) {
  client_handler_ = new ClientHandlerOsr(this, this, startup_url);
}

BrowserWindowOsrMac::~BrowserWindowOsrMac() {
  if (nsview_) {
    // Disassociate the view with |this|.
    [GLView(nsview_) detach];
  }
}

void BrowserWindowOsrMac::CreateBrowser(
    ClientWindowHandle parent_handle,
    const CefRect& rect,
    const CefBrowserSettings& settings,
    CefRefPtr<CefRequestContext> request_context) {
  REQUIRE_MAIN_THREAD();

  // Create the native NSView.
  Create(parent_handle, rect);

  CefWindowInfo window_info;
  window_info.SetAsWindowless(nsview_);

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

void BrowserWindowOsrMac::GetPopupConfig(CefWindowHandle temp_handle,
                                         CefWindowInfo& windowInfo,
                                         CefRefPtr<CefClient>& client,
                                         CefBrowserSettings& settings) {
  CEF_REQUIRE_UI_THREAD();

  windowInfo.SetAsWindowless(temp_handle);
  client = client_handler_;
}

void BrowserWindowOsrMac::ShowPopup(ClientWindowHandle parent_handle,
                                    int x,
                                    int y,
                                    size_t width,
                                    size_t height) {
  REQUIRE_MAIN_THREAD();
  DCHECK(browser_.get());

  // Create the native NSView.
  Create(parent_handle,
         CefRect(x, y, static_cast<int>(width), static_cast<int>(height)));

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

  Show();
}

void BrowserWindowOsrMac::Show() {
  REQUIRE_MAIN_THREAD();

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

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

void BrowserWindowOsrMac::Hide() {
  REQUIRE_MAIN_THREAD();

  if (!browser_.get())
    return;

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

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

void BrowserWindowOsrMac::SetBounds(int x, int y, size_t width, size_t height) {
  REQUIRE_MAIN_THREAD();
  // Nothing to do here. GTK will take care of positioning in the container.
}

void BrowserWindowOsrMac::SetFocus(bool focus) {
  REQUIRE_MAIN_THREAD();
  if (nsview_)
    [[nsview_ window] makeFirstResponder:nsview_];
}

void BrowserWindowOsrMac::SetDeviceScaleFactor(float device_scale_factor) {
  REQUIRE_MAIN_THREAD();
  if (nsview_)
    [GLView(nsview_) setDeviceScaleFactor:device_scale_factor];
}

float BrowserWindowOsrMac::GetDeviceScaleFactor() const {
  REQUIRE_MAIN_THREAD();
  if (nsview_)
    return [GLView(nsview_) getDeviceScaleFactor];
  return 1.0f;
}

ClientWindowHandle BrowserWindowOsrMac::GetWindowHandle() const {
  REQUIRE_MAIN_THREAD();
  return nsview_;
}

void BrowserWindowOsrMac::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
  CEF_REQUIRE_UI_THREAD();
}

void BrowserWindowOsrMac::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
  CEF_REQUIRE_UI_THREAD();
  REQUIRE_MAIN_THREAD();

  // Detach |this| from the ClientHandlerOsr.
  static_cast<ClientHandlerOsr*>(client_handler_.get())->DetachOsrDelegate();
}

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

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

  if (!nsview_)
    return false;

  const float device_scale_factor = [GLView(nsview_) getDeviceScaleFactor];

  // |bounds| is in OS X view coordinates.
  NSRect bounds = [nsview_ bounds];

  // Convert to device coordinates.
  bounds = [GLView(nsview_) convertRectToBackingInternal:bounds];

  // Convert to browser view coordinates.
  rect.x = rect.y = 0;
  rect.width = DeviceToLogical(bounds.size.width, device_scale_factor);
  rect.height = DeviceToLogical(bounds.size.height, device_scale_factor);

  return true;
}

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

  if (!nsview_)
    return false;

  const float device_scale_factor = [GLView(nsview_) getDeviceScaleFactor];

  // (viewX, viewX) is in browser view coordinates.
  // Convert to device coordinates.
  NSPoint view_pt = NSMakePoint(LogicalToDevice(viewX, device_scale_factor),
                                LogicalToDevice(viewY, device_scale_factor));

  // Convert to OS X view coordinates.
  view_pt = [GLView(nsview_) convertPointFromBackingInternal:view_pt];

  // Reverse the Y component.
  const NSRect bounds = [nsview_ bounds];
  view_pt.y = bounds.size.height - view_pt.y;

  // Convert to screen coordinates.
  NSPoint window_pt = [nsview_ convertPoint:view_pt toView:nil];
  NSPoint screen_pt =
      ConvertPointFromWindowToScreen([nsview_ window], window_pt);

  screenX = screen_pt.x;
  screenY = screen_pt.y;
  return true;
}

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

  if (!nsview_)
    return false;

  CefRect view_rect;
  GetViewRect(browser, view_rect);

  screen_info.device_scale_factor = [GLView(nsview_) getDeviceScaleFactor];

  // 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 BrowserWindowOsrMac::OnPopupShow(CefRefPtr<CefBrowser> browser,
                                      bool show) {
  CEF_REQUIRE_UI_THREAD();
  REQUIRE_MAIN_THREAD();

  if (!nsview_)
    return;

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

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

  if (!nsview_)
    return;

  const float device_scale_factor = [GLView(nsview_) getDeviceScaleFactor];

  // |rect| is in browser view coordinates. Convert to device coordinates.
  CefRect device_rect = LogicalToDevice(rect, device_scale_factor);

  renderer_.OnPopupSize(browser, device_rect);
}

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

  if (!nsview_)
    return;

  if (width <= 2 && height <= 2) {
    // Ignore really small buffer sizes while the widget is starting up.
    return;
  }

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

  ScopedGLContext scoped_gl_context(GLView(nsview_), 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 BrowserWindowOsrMac::OnCursorChange(
    CefRefPtr<CefBrowser> browser,
    CefCursorHandle cursor,
    CefRenderHandler::CursorType type,
    const CefCursorInfo& custom_cursor_info) {
  CEF_REQUIRE_UI_THREAD();
  REQUIRE_MAIN_THREAD();

  [cursor set];
}

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

  if (!nsview_)
    return false;

  static float device_scale_factor = [GLView(nsview_) getDeviceScaleFactor];

  // |point| is in browser view coordinates.
  NSPoint point = NSMakePoint(x, y);

  // Convert to device coordinates.
  point.x = LogicalToDevice(point.x, device_scale_factor);
  point.y = LogicalToDevice(point.y, device_scale_factor);

  // Convert to OS X view coordinates.
  point = [GLView(nsview_) convertPointFromBackingInternal:point];

  return
      [GLView(nsview_) startDragging:drag_data
                          allowedOps:static_cast<NSDragOperation>(allowed_ops)
                               point:point];
}

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

  if (nsview_)
    [GLView(nsview_) setCurrentDragOp:operation];
}

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

  if (nsview_) {
    [GLView(nsview_) ChangeCompositionRange:selection_range
                           character_bounds:bounds];
  }
}

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

  if (nsview_) {
    [GLView(nsview_) UpdateAccessibilityTree:value];
  }
}

void BrowserWindowOsrMac::Create(ClientWindowHandle parent_handle,
                                 const CefRect& rect) {
  REQUIRE_MAIN_THREAD();
  DCHECK(!nsview_);

  NSRect window_rect = NSMakeRect(rect.x, rect.y, rect.width, rect.height);
  nsview_ = [[BrowserOpenGLView alloc] initWithFrame:window_rect
                                    andBrowserWindow:this
                                         andRenderer:&renderer_];
  [nsview_ setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
  [nsview_ setAutoresizesSubviews:true];
  [parent_handle addSubview:nsview_];

  // Determine the default scale factor.
  [GLView(nsview_) resetDeviceScaleFactor];

  [[NSNotificationCenter defaultCenter]
      addObserver:nsview_
         selector:@selector(windowDidChangeBackingProperties:)
             name:NSWindowDidChangeBackingPropertiesNotification
           object:[nsview_ window]];
}

}  // namespace client
