diff options
Diffstat (limited to 'tests/cefclient/browser/browser_window_osr_mac.mm')
-rw-r--r-- | tests/cefclient/browser/browser_window_osr_mac.mm | 1986 |
1 files changed, 1986 insertions, 0 deletions
diff --git a/tests/cefclient/browser/browser_window_osr_mac.mm b/tests/cefclient/browser/browser_window_osr_mac.mm new file mode 100644 index 00000000..d72f0a9f --- /dev/null +++ b/tests/cefclient/browser/browser_window_osr_mac.mm @@ -0,0 +1,1986 @@ +// 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> + +@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_; + NSString* fileUTI_; + + // For intreacting with IME. + NSTextInputContext* text_input_context_osr_mac_; + CefTextInputClientOSRMac* text_input_client_; + + // Manages Accessibility Tree + client::OsrAccessibilityHelper* accessibility_helper_; + + // Event monitor for scroll wheel end event. + id endWheelMonitor_; +} + +@end // @interface BrowserOpenGLView + +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_; +}; + +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}]; +#if !__has_feature(objc_arc) + [pixelFormat autorelease]; +#endif // !__has_feature(objc_arc) + + 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 !__has_feature(objc_arc) + if (text_input_context_osr_mac_) { + [text_input_client_ release]; + [text_input_context_osr_mac_ release]; + } + [super dealloc]; +#endif // !__has_feature(objc_arc) +} + +- (void)detach { + renderer_ = nullptr; + browser_window_ = nullptr; + if (text_input_client_) { + [text_input_client_ detach]; + } +} + +- (CefRefPtr<CefBrowser>)getBrowser { + if (browser_window_) { + return browser_window_->GetBrowser(); + } + return nullptr; +} + +- (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, + static_cast<int>([event clickCount])); +} + +- (void)mouseDown:(NSEvent*)event { + [self sendMouseClick:event button:MBT_LEFT isUp:false]; +} + +- (void)rightMouseDown:(NSEvent*)event { + if ([event modifierFlags] & NSEventModifierFlagShift) { + // 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] != NSEventTypeFlagsChanged) { + if (text_input_client_) { + [text_input_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]; + + [text_input_client_ HandleKeyEventAfterTextInputClient:keyEvent]; + } + } + + // Check for Caps lock and Toggle Touch Emulation + if (client::MainContext::Get()->TouchEventsEnabled()) { + [self toggleTouchEmulation:event]; + } +} + +// OSX does not have touch screens, so we emulate it by mapping multitouch +// events on TrackPad to Touch Events on Screen. To ensure it does not +// interfere with other Trackpad events, this mapping is only enabled if +// touch-events=enabled commandline is passed and caps lock key is on. +- (void)toggleTouchEmulation:(NSEvent*)event { + if ([event type] == NSEventTypeFlagsChanged && [event keyCode] == 0x39) { + NSUInteger flags = [event modifierFlags]; + BOOL touch_enabled = flags & NSEventModifierFlagCapsLock ? YES : NO; + if (touch_enabled) { + self.allowedTouchTypes |= NSTouchTypeMaskDirect; + } else { + self.allowedTouchTypes &= ~NSTouchTypeMaskDirect; + } + } +} + +- (cef_touch_event_type_t)getTouchPhase:(NSTouchPhase)phase { + cef_touch_event_type_t event_type = CEF_TET_RELEASED; + switch (phase) { + case NSTouchPhaseBegan: + event_type = CEF_TET_PRESSED; + break; + case NSTouchPhaseMoved: + event_type = CEF_TET_MOVED; + break; + case NSTouchPhaseEnded: + event_type = CEF_TET_RELEASED; + break; + case NSTouchPhaseCancelled: + event_type = CEF_TET_CANCELLED; + break; + default: + break; + } + return event_type; +} + +// Translate NSTouch events to CefTouchEvents and send to browser. +- (void)sendTouchEvent:(NSEvent*)event touchPhase:(NSTouchPhase)phase { + int modifiers = [self getModifiersForEvent:event]; + CefRefPtr<CefBrowser> browser = [self getBrowser]; + + NSSet* touches = [event touchesMatchingPhase:phase inView:self]; + + for (NSTouch* touch in touches) { + // Convert NSTouch to CefTouchEvent. + CefTouchEvent touch_event; + + // NSTouch.identity is unique during the life of the touch + touch_event.id = static_cast<int>(touch.identity.hash); + touch_event.type = [self getTouchPhase:phase]; + + NSPoint scaled_pos = [touch normalizedPosition]; + NSSize view_size = [self bounds].size; + + // Map point on Touch Device to View coordinates. + NSPoint touch_point = NSMakePoint(scaled_pos.x * view_size.width, + scaled_pos.y * view_size.height); + + NSPoint contentLocal = [self convertPoint:touch_point fromView:nil]; + NSPoint point; + point.x = contentLocal.x; + point.y = [self frame].size.height - contentLocal.y; // Flip y. + + // Convert to device coordinates. + point = [self convertPointToBackingInternal:point]; + + int device_x = point.x; + int device_y = point.y; + + const float device_scale_factor = [self getDeviceScaleFactor]; + // Convert to browser view coordinates. + touch_event.x = client::DeviceToLogical(device_x, device_scale_factor); + touch_event.y = client::DeviceToLogical(device_y, device_scale_factor); + + touch_event.radius_x = 0; + touch_event.radius_y = 0; + + touch_event.rotation_angle = 0; + touch_event.pressure = 0; + + touch_event.modifiers = modifiers; + + // Notify the browser of touch event. + browser->GetHost()->SendTouchEvent(touch_event); + } +} + +- (void)touchesBeganWithEvent:(NSEvent*)event { + [self sendTouchEvent:event touchPhase:NSTouchPhaseBegan]; +} + +- (void)touchesMovedWithEvent:(NSEvent*)event { + [self sendTouchEvent:event touchPhase:NSTouchPhaseMoved]; +} + +- (void)touchesEndedWithEvent:(NSEvent*)event { + [self sendTouchEvent:event touchPhase:NSTouchPhaseEnded]; +} + +- (void)touchesCancelledWithEvent:(NSEvent*)event { + [self sendTouchEvent:event touchPhase:NSTouchPhaseCancelled]; +} + +- (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:NSEventMaskScrollWheel + 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 = static_cast<int>( + CGEventGetIntegerValueField(cgEvent, kCGScrollWheelEventPointDeltaAxis2)); + int deltaY = static_cast<int>( + 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() != nullptr); +} + +- (BOOL)acceptsFirstResponder { + CefRefPtr<CefBrowser> browser = [self getBrowser]; + return (browser.get() != nullptr); +} + +- (BOOL)becomeFirstResponder { + CefRefPtr<CefBrowser> browser = [self getBrowser]; + if (browser.get()) { + browser->GetHost()->SetFocus(true); + return [super becomeFirstResponder]; + } + + return NO; +} + +- (BOOL)resignFirstResponder { + CefRefPtr<CefBrowser> browser = [self getBrowser]; + if (browser.get()) { + browser->GetHost()->SetFocus(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] == NSEventTypeKeyDown || [event type] == NSEventTypeKeyUp) { + 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] == NSEventTypeFlagsChanged) { + 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_) { + text_input_client_ = + [[CefTextInputClientOSRMac alloc] initWithBrowser:[self getBrowser]]; + text_input_context_osr_mac_ = + [[NSTextInputContext alloc] initWithClient:text_input_client_]; +#if !__has_feature(objc_arc) + [text_input_client_ retain]; + [text_input_context_osr_mac_ retain]; +#endif // !__has_feature(objc_arc) + } + + 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 = static_cast<uint32>([NSEvent modifierFlags]); +} + +- (int)getModifiersForEvent:(NSEvent*)event { + int modifiers = 0; + + if ([event modifierFlags] & NSEventModifierFlagControl) { + modifiers |= EVENTFLAG_CONTROL_DOWN; + } + if ([event modifierFlags] & NSEventModifierFlagShift) { + modifiers |= EVENTFLAG_SHIFT_DOWN; + } + if ([event modifierFlags] & NSEventModifierFlagOption) { + modifiers |= EVENTFLAG_ALT_DOWN; + } + if ([event modifierFlags] & NSEventModifierFlagCommand) { + modifiers |= EVENTFLAG_COMMAND_DOWN; + } + if ([event modifierFlags] & NSEventModifierFlagCapsLock) { + modifiers |= EVENTFLAG_CAPS_LOCK_ON; + } + + if ([event type] == NSEventTypeKeyUp || [event type] == NSEventTypeKeyDown || + [event type] == NSEventTypeFlagsChanged) { + // 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 NSEventTypeLeftMouseDragged: + case NSEventTypeLeftMouseDown: + case NSEventTypeLeftMouseUp: + modifiers |= EVENTFLAG_LEFT_MOUSE_BUTTON; + break; + case NSEventTypeRightMouseDragged: + case NSEventTypeRightMouseDown: + case NSEventTypeRightMouseUp: + modifiers |= EVENTFLAG_RIGHT_MOUSE_BUTTON; + break; + case NSEventTypeOtherMouseDragged: + case NSEventTypeOtherMouseDown: + case NSEventTypeOtherMouseUp: + modifiers |= EVENTFLAG_MIDDLE_MOUSE_BUTTON; + break; + default: + break; + } + + return modifiers; +} + +- (BOOL)isKeyUpEvent:(NSEvent*)event { + if ([event type] != NSEventTypeFlagsChanged) { + return [event type] == NSEventTypeKeyUp; + } + + // 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] & NSEventModifierFlagCommand) == 0; + + case 57: // Capslock + return ([event modifierFlags] & NSEventModifierFlagCapsLock) == 0; + + case 56: // Left Shift + case 60: // Right Shift + return ([event modifierFlags] & NSEventModifierFlagShift) == 0; + + case 58: // Left Alt + case 61: // Right Alt + return ([event modifierFlags] & NSEventModifierFlagOption) == 0; + + case 59: // Left Ctrl + case 62: // Right Ctrl + return ([event modifierFlags] & NSEventModifierFlagControl) == 0; + + case 63: // Function + return ([event modifierFlags] & NSEventModifierFlagFunction) == 0; + } + return false; +} + +- (BOOL)isKeyPadEvent:(NSEvent*)event { + if ([event modifierFlags] & NSEventModifierFlagNumericPad) { + 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:NSEventTypeLeftMouseDragged + location:position + modifierFlags:NSEventMaskLeftMouseDragged + 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(nullptr); + 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:fileUTI_]) { + size_t size = current_drag_data_->GetFileContents(nullptr); + 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: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 = CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT( + node->GetNativeAccessibleObject(nullptr)); + [kids addObject:child]; + return NSAccessibilityUnignoredChildren(kids); + } else { + return [super accessibilityAttributeValue:attribute]; + } +} + +- (id)accessibilityFocusedUIElement { + if (accessibility_helper_) { + client::OsrAXNode* node = accessibility_helper_->GetFocusedNode(); + return node ? CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT( + node->GetNativeAccessibleObject(nullptr)) + : nil; + } + return nil; +} + +// Utility methods. +- (void)resetDragDrop { + current_drag_op_ = NSDragOperationNone; + current_allowed_ops_ = NSDragOperationNone; + current_drag_data_ = nullptr; + if (fileUTI_) { +#if !__has_feature(objc_arc) + [fileUTI_ release]; +#endif // !__has_feature(objc_arc) + fileUTI_ = nil; + } + if (pasteboard_) { +#if !__has_feature(objc_arc) + [pasteboard_ release]; +#endif // !__has_feature(objc_arc) + pasteboard_ = nil; + } +} + +- (void)fillPasteboard { + DCHECK(!pasteboard_); + pasteboard_ = [NSPasteboard pasteboardWithName:NSPasteboardNameDrag]; +#if !__has_feature(objc_arc) + [pasteboard_ retain]; +#endif // !__has_feature(objc_arc) + + [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(nullptr); + CefString download_metadata = current_drag_data_->GetLinkMetadata(); + + // 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_ = (__bridge NSString*)UTTypeCreatePreferredIdentifierForTag( + kUTTagClassMIMEType, mimeTypeCF, nullptr); + CFRelease(mimeTypeCF); + // File (HFS) promise. + NSArray* fileUTIList = @[ 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 { + [super 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 { + if (text_input_client_) { + [text_input_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; +} + +- (void)UpdateAccessibilityLocation:(CefRefPtr<CefValue>)value { + if (accessibility_helper_) { + accessibility_helper_->UpdateAccessibilityLocation(value); + } + + if (accessibility_helper_) { + NSAccessibilityPostNotification(self, + NSAccessibilityValueChangedNotification); + } + return; +} +@end + +namespace client { + +class BrowserWindowOsrMacImpl { + public: + BrowserWindowOsrMacImpl(BrowserWindow::Delegate* delegate, + const std::string& startup_url, + const OsrRendererSettings& settings, + BrowserWindowOsrMac& browser_window); + ~BrowserWindowOsrMacImpl(); + + // BrowserWindow methods. + void CreateBrowser(ClientWindowHandle parent_handle, + const CefRect& rect, + const CefBrowserSettings& settings, + CefRefPtr<CefDictionaryValue> extra_info, + CefRefPtr<CefRequestContext> request_context); + void GetPopupConfig(CefWindowHandle temp_handle, + CefWindowInfo& windowInfo, + CefRefPtr<CefClient>& client, + CefBrowserSettings& settings); + void ShowPopup(ClientWindowHandle parent_handle, + int x, + int y, + size_t width, + size_t height); + void Show(); + void Hide(); + void SetBounds(int x, int y, size_t width, size_t height); + void SetFocus(bool focus); + void SetDeviceScaleFactor(float device_scale_factor); + float GetDeviceScaleFactor() const; + ClientWindowHandle GetWindowHandle() const; + + // ClientHandlerOsr::OsrDelegate methods. + void OnAfterCreated(CefRefPtr<CefBrowser> browser); + void OnBeforeClose(CefRefPtr<CefBrowser> browser); + bool GetRootScreenRect(CefRefPtr<CefBrowser> browser, CefRect& rect); + void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect); + bool GetScreenPoint(CefRefPtr<CefBrowser> browser, + int viewX, + int viewY, + int& screenX, + int& screenY); + bool GetScreenInfo(CefRefPtr<CefBrowser> browser, CefScreenInfo& screen_info); + void OnPopupShow(CefRefPtr<CefBrowser> browser, bool show); + void OnPopupSize(CefRefPtr<CefBrowser> browser, const CefRect& rect); + void OnPaint(CefRefPtr<CefBrowser> browser, + CefRenderHandler::PaintElementType type, + const CefRenderHandler::RectList& dirtyRects, + const void* buffer, + int width, + int height); + void OnCursorChange(CefRefPtr<CefBrowser> browser, + CefCursorHandle cursor, + cef_cursor_type_t type, + const CefCursorInfo& custom_cursor_info); + bool StartDragging(CefRefPtr<CefBrowser> browser, + CefRefPtr<CefDragData> drag_data, + CefRenderHandler::DragOperationsMask allowed_ops, + int x, + int y); + void UpdateDragCursor(CefRefPtr<CefBrowser> browser, + CefRenderHandler::DragOperation operation); + void OnImeCompositionRangeChanged( + CefRefPtr<CefBrowser> browser, + const CefRange& selection_range, + const CefRenderHandler::RectList& character_bounds); + + void UpdateAccessibilityTree(CefRefPtr<CefValue> value); + void UpdateAccessibilityLocation(CefRefPtr<CefValue> value); + + private: + // Create the NSView. + void Create(ClientWindowHandle parent_handle, const CefRect& rect); + + BrowserWindowOsrMac& browser_window_; + // The below members will only be accessed on the main thread which should be + // the same as the CEF UI thread. + OsrRenderer renderer_; + BrowserOpenGLView* native_browser_view_; + bool hidden_; + bool painting_popup_; +}; + +BrowserWindowOsrMacImpl::BrowserWindowOsrMacImpl( + BrowserWindow::Delegate* delegate, + const std::string& startup_url, + const OsrRendererSettings& settings, + BrowserWindowOsrMac& browser_window) + : browser_window_(browser_window), + renderer_(settings), + native_browser_view_(nil), + hidden_(false), + painting_popup_(false) {} + +BrowserWindowOsrMacImpl::~BrowserWindowOsrMacImpl() { + if (native_browser_view_) { + // Disassociate the view with |this|. + [native_browser_view_ detach]; + } +} + +void BrowserWindowOsrMacImpl::CreateBrowser( + ClientWindowHandle parent_handle, + const CefRect& rect, + const CefBrowserSettings& settings, + CefRefPtr<CefDictionaryValue> extra_info, + CefRefPtr<CefRequestContext> request_context) { + REQUIRE_MAIN_THREAD(); + + // Create the native NSView. + Create(parent_handle, rect); + + CefWindowInfo window_info; + window_info.SetAsWindowless( + CAST_NSVIEW_TO_CEF_WINDOW_HANDLE(native_browser_view_)); + + // Create the browser asynchronously. + CefBrowserHost::CreateBrowser(window_info, browser_window_.client_handler_, + browser_window_.client_handler_->startup_url(), + settings, extra_info, request_context); +} + +void BrowserWindowOsrMacImpl::GetPopupConfig(CefWindowHandle temp_handle, + CefWindowInfo& windowInfo, + CefRefPtr<CefClient>& client, + CefBrowserSettings& settings) { + CEF_REQUIRE_UI_THREAD(); + + windowInfo.SetAsWindowless(temp_handle); + client = browser_window_.client_handler_; +} + +void BrowserWindowOsrMacImpl::ShowPopup(ClientWindowHandle parent_handle, + int x, + int y, + size_t width, + size_t height) { + REQUIRE_MAIN_THREAD(); + DCHECK(browser_window_.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_window_.browser_->GetHost()->WasResized(); + + Show(); +} + +void BrowserWindowOsrMacImpl::Show() { + REQUIRE_MAIN_THREAD(); + + if (hidden_) { + // Set the browser as visible. + browser_window_.browser_->GetHost()->WasHidden(false); + hidden_ = false; + } + + // Give focus to the browser. + browser_window_.browser_->GetHost()->SetFocus(true); +} + +void BrowserWindowOsrMacImpl::Hide() { + REQUIRE_MAIN_THREAD(); + + if (!browser_window_.browser_.get()) { + return; + } + + // Remove focus from the browser. + browser_window_.browser_->GetHost()->SetFocus(false); + + if (!hidden_) { + // Set the browser as hidden. + browser_window_.browser_->GetHost()->WasHidden(true); + hidden_ = true; + } +} + +void BrowserWindowOsrMacImpl::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 BrowserWindowOsrMacImpl::SetFocus(bool focus) { + REQUIRE_MAIN_THREAD(); + if (native_browser_view_) { + [native_browser_view_.window makeFirstResponder:native_browser_view_]; + } +} + +void BrowserWindowOsrMacImpl::SetDeviceScaleFactor(float device_scale_factor) { + REQUIRE_MAIN_THREAD(); + if (native_browser_view_) { + [native_browser_view_ setDeviceScaleFactor:device_scale_factor]; + } +} + +float BrowserWindowOsrMacImpl::GetDeviceScaleFactor() const { + REQUIRE_MAIN_THREAD(); + if (native_browser_view_) { + return [native_browser_view_ getDeviceScaleFactor]; + } + return 1.0f; +} + +ClientWindowHandle BrowserWindowOsrMacImpl::GetWindowHandle() const { + REQUIRE_MAIN_THREAD(); + return CAST_NSVIEW_TO_CEF_WINDOW_HANDLE(native_browser_view_); +} + +void BrowserWindowOsrMacImpl::OnAfterCreated(CefRefPtr<CefBrowser> browser) { + CEF_REQUIRE_UI_THREAD(); +} + +void BrowserWindowOsrMacImpl::OnBeforeClose(CefRefPtr<CefBrowser> browser) { + CEF_REQUIRE_UI_THREAD(); + REQUIRE_MAIN_THREAD(); + + // Detach |this| from the ClientHandlerOsr. + static_cast<ClientHandlerOsr*>(browser_window_.client_handler_.get()) + ->DetachOsrDelegate(); +} + +bool BrowserWindowOsrMacImpl::GetRootScreenRect(CefRefPtr<CefBrowser> browser, + CefRect& rect) { + CEF_REQUIRE_UI_THREAD(); + return false; +} + +void BrowserWindowOsrMacImpl::GetViewRect(CefRefPtr<CefBrowser> browser, + CefRect& rect) { + CEF_REQUIRE_UI_THREAD(); + REQUIRE_MAIN_THREAD(); + + rect.x = rect.y = 0; + + if (!native_browser_view_) { + // Never return an empty rectangle. + rect.width = rect.height = 1; + return; + } + + const float device_scale_factor = [native_browser_view_ getDeviceScaleFactor]; + + // |bounds| is in OS X view coordinates. + NSRect bounds = native_browser_view_.bounds; + + // Convert to device coordinates. + bounds = [native_browser_view_ convertRectToBackingInternal:bounds]; + + // Convert to browser view coordinates. + rect.width = DeviceToLogical(bounds.size.width, device_scale_factor); + if (rect.width == 0) { + rect.width = 1; + } + rect.height = DeviceToLogical(bounds.size.height, device_scale_factor); + if (rect.height == 0) { + rect.height = 1; + } +} + +bool BrowserWindowOsrMacImpl::GetScreenPoint(CefRefPtr<CefBrowser> browser, + int viewX, + int viewY, + int& screenX, + int& screenY) { + CEF_REQUIRE_UI_THREAD(); + REQUIRE_MAIN_THREAD(); + + if (!native_browser_view_) { + return false; + } + + const float device_scale_factor = [native_browser_view_ 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 = [native_browser_view_ convertPointFromBackingInternal:view_pt]; + + // Reverse the Y component. + const NSRect bounds = native_browser_view_.bounds; + view_pt.y = bounds.size.height - view_pt.y; + + // Convert to screen coordinates. + NSPoint window_pt = [native_browser_view_ convertPoint:view_pt toView:nil]; + NSPoint screen_pt = + ConvertPointFromWindowToScreen(native_browser_view_.window, window_pt); + + screenX = screen_pt.x; + screenY = screen_pt.y; + return true; +} + +bool BrowserWindowOsrMacImpl::GetScreenInfo(CefRefPtr<CefBrowser> browser, + CefScreenInfo& screen_info) { + CEF_REQUIRE_UI_THREAD(); + REQUIRE_MAIN_THREAD(); + + if (!native_browser_view_) { + return false; + } + + CefRect view_rect; + GetViewRect(browser, view_rect); + + screen_info.device_scale_factor = [native_browser_view_ 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 BrowserWindowOsrMacImpl::OnPopupShow(CefRefPtr<CefBrowser> browser, + bool show) { + CEF_REQUIRE_UI_THREAD(); + REQUIRE_MAIN_THREAD(); + + if (!native_browser_view_) { + return; + } + + if (!show) { + renderer_.ClearPopupRects(); + browser->GetHost()->Invalidate(PET_VIEW); + } + renderer_.OnPopupShow(browser, show); +} + +void BrowserWindowOsrMacImpl::OnPopupSize(CefRefPtr<CefBrowser> browser, + const CefRect& rect) { + CEF_REQUIRE_UI_THREAD(); + REQUIRE_MAIN_THREAD(); + + if (!native_browser_view_) { + return; + } + + const float device_scale_factor = [native_browser_view_ 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 BrowserWindowOsrMacImpl::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 (!native_browser_view_) { + 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(native_browser_view_, 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 BrowserWindowOsrMacImpl::OnCursorChange( + CefRefPtr<CefBrowser> browser, + CefCursorHandle cursor, + cef_cursor_type_t type, + const CefCursorInfo& custom_cursor_info) { + CEF_REQUIRE_UI_THREAD(); + REQUIRE_MAIN_THREAD(); + + [CAST_CEF_CURSOR_HANDLE_TO_NSCURSOR(cursor) set]; +} + +bool BrowserWindowOsrMacImpl::StartDragging( + CefRefPtr<CefBrowser> browser, + CefRefPtr<CefDragData> drag_data, + CefRenderHandler::DragOperationsMask allowed_ops, + int x, + int y) { + CEF_REQUIRE_UI_THREAD(); + REQUIRE_MAIN_THREAD(); + + if (!native_browser_view_) { + return false; + } + + static float device_scale_factor = + [native_browser_view_ 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 = [native_browser_view_ convertPointFromBackingInternal:point]; + + return [native_browser_view_ + startDragging:drag_data + allowedOps:static_cast<NSDragOperation>(allowed_ops) + point:point]; +} + +void BrowserWindowOsrMacImpl::UpdateDragCursor( + CefRefPtr<CefBrowser> browser, + CefRenderHandler::DragOperation operation) { + CEF_REQUIRE_UI_THREAD(); + REQUIRE_MAIN_THREAD(); + + if (native_browser_view_) { + [native_browser_view_ setCurrentDragOp:operation]; + } +} + +void BrowserWindowOsrMacImpl::OnImeCompositionRangeChanged( + CefRefPtr<CefBrowser> browser, + const CefRange& selection_range, + const CefRenderHandler::RectList& bounds) { + CEF_REQUIRE_UI_THREAD(); + + if (native_browser_view_) { + [native_browser_view_ ChangeCompositionRange:selection_range + character_bounds:bounds]; + } +} + +void BrowserWindowOsrMacImpl::UpdateAccessibilityTree( + CefRefPtr<CefValue> value) { + CEF_REQUIRE_UI_THREAD(); + + if (native_browser_view_) { + [native_browser_view_ UpdateAccessibilityTree:value]; + } +} + +void BrowserWindowOsrMacImpl::UpdateAccessibilityLocation( + CefRefPtr<CefValue> value) { + CEF_REQUIRE_UI_THREAD(); + + if (native_browser_view_) { + [native_browser_view_ UpdateAccessibilityLocation:value]; + } +} + +void BrowserWindowOsrMacImpl::Create(ClientWindowHandle parent_handle, + const CefRect& rect) { + REQUIRE_MAIN_THREAD(); + DCHECK(!native_browser_view_); + + NSRect window_rect = NSMakeRect(rect.x, rect.y, rect.width, rect.height); + native_browser_view_ = + [[BrowserOpenGLView alloc] initWithFrame:window_rect + andBrowserWindow:&browser_window_ + andRenderer:&renderer_]; + native_browser_view_.autoresizingMask = + (NSViewWidthSizable | NSViewHeightSizable); + native_browser_view_.autoresizesSubviews = YES; + [CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(parent_handle) + addSubview:native_browser_view_]; + + // Determine the default scale factor. + [native_browser_view_ resetDeviceScaleFactor]; + + [[NSNotificationCenter defaultCenter] + addObserver:native_browser_view_ + selector:@selector(windowDidChangeBackingProperties:) + name:NSWindowDidChangeBackingPropertiesNotification + object:native_browser_view_.window]; +} + +BrowserWindowOsrMac::BrowserWindowOsrMac(BrowserWindow::Delegate* delegate, + bool with_controls, + const std::string& startup_url, + const OsrRendererSettings& settings) + : BrowserWindow(delegate) { + client_handler_ = + new ClientHandlerOsr(this, this, with_controls, startup_url); + impl_.reset( + new BrowserWindowOsrMacImpl(delegate, startup_url, settings, *this)); +} + +BrowserWindowOsrMac::~BrowserWindowOsrMac() {} + +void BrowserWindowOsrMac::CreateBrowser( + ClientWindowHandle parent_handle, + const CefRect& rect, + const CefBrowserSettings& settings, + CefRefPtr<CefDictionaryValue> extra_info, + CefRefPtr<CefRequestContext> request_context) { + impl_->CreateBrowser(parent_handle, rect, settings, extra_info, + request_context); +} + +void BrowserWindowOsrMac::GetPopupConfig(CefWindowHandle temp_handle, + CefWindowInfo& windowInfo, + CefRefPtr<CefClient>& client, + CefBrowserSettings& settings) { + impl_->GetPopupConfig(temp_handle, windowInfo, client, settings); +} + +void BrowserWindowOsrMac::ShowPopup(ClientWindowHandle parent_handle, + int x, + int y, + size_t width, + size_t height) { + impl_->ShowPopup(parent_handle, x, y, width, height); +} + +void BrowserWindowOsrMac::Show() { + impl_->Show(); +} + +void BrowserWindowOsrMac::Hide() { + impl_->Hide(); +} + +void BrowserWindowOsrMac::SetBounds(int x, int y, size_t width, size_t height) { + impl_->SetBounds(x, y, width, height); +} + +void BrowserWindowOsrMac::SetFocus(bool focus) { + impl_->SetFocus(focus); +} + +void BrowserWindowOsrMac::SetDeviceScaleFactor(float device_scale_factor) { + impl_->SetDeviceScaleFactor(device_scale_factor); +} + +float BrowserWindowOsrMac::GetDeviceScaleFactor() const { + return impl_->GetDeviceScaleFactor(); +} + +ClientWindowHandle BrowserWindowOsrMac::GetWindowHandle() const { + return impl_->GetWindowHandle(); +} + +void BrowserWindowOsrMac::OnAfterCreated(CefRefPtr<CefBrowser> browser) { + impl_->OnAfterCreated(browser); +} + +void BrowserWindowOsrMac::OnBeforeClose(CefRefPtr<CefBrowser> browser) { + impl_->OnBeforeClose(browser); +} + +bool BrowserWindowOsrMac::GetRootScreenRect(CefRefPtr<CefBrowser> browser, + CefRect& rect) { + return impl_->GetRootScreenRect(browser, rect); +} + +void BrowserWindowOsrMac::GetViewRect(CefRefPtr<CefBrowser> browser, + CefRect& rect) { + impl_->GetViewRect(browser, rect); +} + +bool BrowserWindowOsrMac::GetScreenPoint(CefRefPtr<CefBrowser> browser, + int viewX, + int viewY, + int& screenX, + int& screenY) { + return impl_->GetScreenPoint(browser, viewX, viewY, screenX, screenY); +} + +bool BrowserWindowOsrMac::GetScreenInfo(CefRefPtr<CefBrowser> browser, + CefScreenInfo& screen_info) { + return impl_->GetScreenInfo(browser, screen_info); +} + +void BrowserWindowOsrMac::OnPopupShow(CefRefPtr<CefBrowser> browser, + bool show) { + impl_->OnPopupShow(browser, show); +} + +void BrowserWindowOsrMac::OnPopupSize(CefRefPtr<CefBrowser> browser, + const CefRect& rect) { + impl_->OnPopupSize(browser, rect); +} + +void BrowserWindowOsrMac::OnPaint(CefRefPtr<CefBrowser> browser, + CefRenderHandler::PaintElementType type, + const CefRenderHandler::RectList& dirtyRects, + const void* buffer, + int width, + int height) { + impl_->OnPaint(browser, type, dirtyRects, buffer, width, height); +} + +void BrowserWindowOsrMac::OnCursorChange( + CefRefPtr<CefBrowser> browser, + CefCursorHandle cursor, + cef_cursor_type_t type, + const CefCursorInfo& custom_cursor_info) { + impl_->OnCursorChange(browser, cursor, type, custom_cursor_info); +} + +bool BrowserWindowOsrMac::StartDragging( + CefRefPtr<CefBrowser> browser, + CefRefPtr<CefDragData> drag_data, + CefRenderHandler::DragOperationsMask allowed_ops, + int x, + int y) { + return impl_->StartDragging(browser, drag_data, allowed_ops, x, y); +} + +void BrowserWindowOsrMac::UpdateDragCursor( + CefRefPtr<CefBrowser> browser, + CefRenderHandler::DragOperation operation) { + impl_->UpdateDragCursor(browser, operation); +} + +void BrowserWindowOsrMac::OnImeCompositionRangeChanged( + CefRefPtr<CefBrowser> browser, + const CefRange& selection_range, + const CefRenderHandler::RectList& character_bounds) { + impl_->OnImeCompositionRangeChanged(browser, selection_range, + character_bounds); +} + +void BrowserWindowOsrMac::UpdateAccessibilityTree(CefRefPtr<CefValue> value) { + impl_->UpdateAccessibilityTree(value); +} + +void BrowserWindowOsrMac::UpdateAccessibilityLocation( + CefRefPtr<CefValue> value) { + impl_->UpdateAccessibilityLocation(value); +} + +} // namespace client |