1// Copyright 2015 The Chromium Embedded Framework Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "libcef/browser/native/browser_platform_delegate_native_mac.h" 6 7#import <Cocoa/Cocoa.h> 8#import <CoreServices/CoreServices.h> 9 10#include "libcef/browser/alloy/alloy_browser_host_impl.h" 11#include "libcef/browser/context.h" 12#include "libcef/browser/native/file_dialog_runner_mac.h" 13#include "libcef/browser/native/javascript_dialog_runner_mac.h" 14#include "libcef/browser/native/menu_runner_mac.h" 15#include "libcef/browser/thread_util.h" 16 17#include "base/mac/scoped_nsautorelease_pool.h" 18#include "base/memory/ptr_util.h" 19#include "base/threading/thread_restrictions.h" 20#include "content/browser/renderer_host/render_widget_host_view_mac.h" 21#include "content/public/browser/native_web_keyboard_event.h" 22#include "content/public/browser/render_widget_host_view.h" 23#include "content/public/browser/web_contents.h" 24#include "third_party/blink/public/common/input/web_input_event.h" 25#include "third_party/blink/public/common/input/web_mouse_event.h" 26#include "third_party/blink/public/common/input/web_mouse_wheel_event.h" 27#import "ui/base/cocoa/cocoa_base_utils.h" 28#import "ui/base/cocoa/underlay_opengl_hosting_window.h" 29#include "ui/events/base_event_utils.h" 30#include "ui/events/keycodes/keyboard_codes_posix.h" 31#include "ui/gfx/geometry/rect.h" 32 33// Wrapper NSView for the native view. Necessary to destroy the browser when 34// the view is deleted. 35@interface CefBrowserHostView : NSView { 36 @private 37 CefBrowserHostBase* browser_; // weak 38} 39 40@property(nonatomic, assign) CefBrowserHostBase* browser; 41 42@end 43 44@implementation CefBrowserHostView 45 46@synthesize browser = browser_; 47 48- (void)dealloc { 49 if (browser_) { 50 // Force the browser to be destroyed and release the reference added in 51 // PlatformCreateWindow(). 52 static_cast<AlloyBrowserHostImpl*>(browser_)->WindowDestroyed(); 53 } 54 55 [super dealloc]; 56} 57 58@end 59 60// Receives notifications from the browser window. Will delete itself when done. 61@interface CefWindowDelegate : NSObject <NSWindowDelegate> { 62 @private 63 CefBrowserHostBase* browser_; // weak 64 NSWindow* window_; 65} 66- (id)initWithWindow:(NSWindow*)window andBrowser:(CefBrowserHostBase*)browser; 67@end 68 69@implementation CefWindowDelegate 70 71- (id)initWithWindow:(NSWindow*)window andBrowser:(CefBrowserHostBase*)browser { 72 if (self = [super init]) { 73 window_ = window; 74 browser_ = browser; 75 76 [window_ setDelegate:self]; 77 } 78 return self; 79} 80 81- (void)dealloc { 82 [[NSNotificationCenter defaultCenter] removeObserver:self]; 83 84 [super dealloc]; 85} 86 87- (BOOL)windowShouldClose:(id)window { 88 if (browser_ && !browser_->TryCloseBrowser()) { 89 // Cancel the close. 90 return NO; 91 } 92 93 // Clean ourselves up after clearing the stack of anything that might have the 94 // window on it. 95 [self performSelectorOnMainThread:@selector(cleanup:) 96 withObject:window 97 waitUntilDone:NO]; 98 99 // Allow the close. 100 return YES; 101} 102 103- (void)cleanup:(id)window { 104 [window_ setDelegate:nil]; 105 [self release]; 106} 107 108@end 109 110namespace { 111 112NSTimeInterval currentEventTimestamp() { 113 NSEvent* currentEvent = [NSApp currentEvent]; 114 if (currentEvent) 115 return [currentEvent timestamp]; 116 else { 117 // FIXME(API): In case there is no current event, the timestamp could be 118 // obtained by getting the time since the application started. This involves 119 // taking some more static functions from Chromium code. 120 // Another option is to have the timestamp as a field in CefEvent structures 121 // and let the client provide it. 122 return 0; 123 } 124} 125 126NSUInteger NativeModifiers(int cef_modifiers) { 127 NSUInteger native_modifiers = 0; 128 if (cef_modifiers & EVENTFLAG_SHIFT_DOWN) 129 native_modifiers |= NSShiftKeyMask; 130 if (cef_modifiers & EVENTFLAG_CONTROL_DOWN) 131 native_modifiers |= NSControlKeyMask; 132 if (cef_modifiers & EVENTFLAG_ALT_DOWN) 133 native_modifiers |= NSAlternateKeyMask; 134 if (cef_modifiers & EVENTFLAG_COMMAND_DOWN) 135 native_modifiers |= NSCommandKeyMask; 136 if (cef_modifiers & EVENTFLAG_CAPS_LOCK_ON) 137 native_modifiers |= NSAlphaShiftKeyMask; 138 if (cef_modifiers & EVENTFLAG_NUM_LOCK_ON) 139 native_modifiers |= NSNumericPadKeyMask; 140 141 return native_modifiers; 142} 143 144} // namespace 145 146CefBrowserPlatformDelegateNativeMac::CefBrowserPlatformDelegateNativeMac( 147 const CefWindowInfo& window_info, 148 SkColor background_color) 149 : CefBrowserPlatformDelegateNative(window_info, background_color), 150 host_window_created_(false) {} 151 152void CefBrowserPlatformDelegateNativeMac::BrowserDestroyed( 153 CefBrowserHostBase* browser) { 154 CefBrowserPlatformDelegateNative::BrowserDestroyed(browser); 155 156 if (host_window_created_) { 157 // Release the reference added in CreateHostWindow(). 158 browser->Release(); 159 } 160} 161 162bool CefBrowserPlatformDelegateNativeMac::CreateHostWindow() { 163 base::mac::ScopedNSAutoreleasePool autorelease_pool; 164 165 NSWindow* newWnd = nil; 166 167 NSView* parentView = 168 CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(window_info_.parent_view); 169 NSRect contentRect = {{static_cast<CGFloat>(window_info_.bounds.x), 170 static_cast<CGFloat>(window_info_.bounds.y)}, 171 {static_cast<CGFloat>(window_info_.bounds.width), 172 static_cast<CGFloat>(window_info_.bounds.height)}}; 173 if (parentView == nil) { 174 // Create a new window. 175 NSRect screen_rect = [[NSScreen mainScreen] visibleFrame]; 176 NSRect window_rect = { 177 {static_cast<CGFloat>(window_info_.bounds.x), 178 screen_rect.size.height - static_cast<CGFloat>(window_info_.bounds.y)}, 179 {static_cast<CGFloat>(window_info_.bounds.width), 180 static_cast<CGFloat>(window_info_.bounds.height)}}; 181 if (window_rect.size.width == 0) 182 window_rect.size.width = 750; 183 if (window_rect.size.height == 0) 184 window_rect.size.height = 750; 185 186 contentRect.origin.x = 0; 187 contentRect.origin.y = 0; 188 contentRect.size.width = window_rect.size.width; 189 contentRect.size.height = window_rect.size.height; 190 191 newWnd = [[UnderlayOpenGLHostingWindow alloc] 192 initWithContentRect:window_rect 193 styleMask:(NSTitledWindowMask | NSClosableWindowMask | 194 NSMiniaturizableWindowMask | 195 NSResizableWindowMask | 196 NSUnifiedTitleAndToolbarWindowMask) 197 backing:NSBackingStoreBuffered 198 defer:NO]; 199 200 // Create the delegate for control and browser window events. 201 [[CefWindowDelegate alloc] initWithWindow:newWnd andBrowser:browser_]; 202 203 parentView = [newWnd contentView]; 204 window_info_.parent_view = parentView; 205 206 // Make the content view for the window have a layer. This will make all 207 // sub-views have layers. This is necessary to ensure correct layer 208 // ordering of all child views and their layers. 209 [parentView setWantsLayer:YES]; 210 } 211 212 host_window_created_ = true; 213 214 // Add a reference that will be released in BrowserDestroyed(). 215 browser_->AddRef(); 216 217 // Create the browser view. 218 CefBrowserHostView* browser_view = 219 [[CefBrowserHostView alloc] initWithFrame:contentRect]; 220 browser_view.browser = browser_; 221 [parentView addSubview:browser_view]; 222 [browser_view setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; 223 [browser_view setNeedsDisplay:YES]; 224 [browser_view release]; 225 226 // Parent the TabContents to the browser view. 227 const NSRect bounds = [browser_view bounds]; 228 NSView* native_view = web_contents_->GetNativeView().GetNativeNSView(); 229 [browser_view addSubview:native_view]; 230 [native_view setFrame:bounds]; 231 [native_view setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; 232 [native_view setNeedsDisplay:YES]; 233 234 window_info_.view = browser_view; 235 236 if (newWnd != nil && !window_info_.hidden) { 237 // Show the window. 238 [newWnd makeKeyAndOrderFront:nil]; 239 } 240 241 return true; 242} 243 244void CefBrowserPlatformDelegateNativeMac::CloseHostWindow() { 245 NSView* nsview = CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(window_info_.view); 246 if (nsview != nil) { 247 [[nsview window] performSelectorOnMainThread:@selector(performClose:) 248 withObject:nil 249 waitUntilDone:NO]; 250 } 251} 252 253CefWindowHandle CefBrowserPlatformDelegateNativeMac::GetHostWindowHandle() 254 const { 255 if (windowless_handler_) 256 return windowless_handler_->GetParentWindowHandle(); 257 return window_info_.view; 258} 259 260void CefBrowserPlatformDelegateNativeMac::SendKeyEvent( 261 const CefKeyEvent& event) { 262 auto view = GetHostView(); 263 if (!view) 264 return; 265 266 content::NativeWebKeyboardEvent web_event = TranslateWebKeyEvent(event); 267 view->ForwardKeyboardEvent(web_event, ui::LatencyInfo()); 268} 269 270void CefBrowserPlatformDelegateNativeMac::SendMouseClickEvent( 271 const CefMouseEvent& event, 272 CefBrowserHost::MouseButtonType type, 273 bool mouseUp, 274 int clickCount) { 275 auto view = GetHostView(); 276 if (!view) 277 return; 278 279 blink::WebMouseEvent web_event = 280 TranslateWebClickEvent(event, type, mouseUp, clickCount); 281 view->RouteOrProcessMouseEvent(web_event); 282} 283 284void CefBrowserPlatformDelegateNativeMac::SendMouseMoveEvent( 285 const CefMouseEvent& event, 286 bool mouseLeave) { 287 auto view = GetHostView(); 288 if (!view) 289 return; 290 291 blink::WebMouseEvent web_event = TranslateWebMoveEvent(event, mouseLeave); 292 view->RouteOrProcessMouseEvent(web_event); 293} 294 295void CefBrowserPlatformDelegateNativeMac::SendMouseWheelEvent( 296 const CefMouseEvent& event, 297 int deltaX, 298 int deltaY) { 299 auto view = GetHostView(); 300 if (!view) 301 return; 302 303 blink::WebMouseWheelEvent web_event = 304 TranslateWebWheelEvent(event, deltaX, deltaY); 305 view->RouteOrProcessMouseEvent(web_event); 306} 307 308void CefBrowserPlatformDelegateNativeMac::SendTouchEvent( 309 const CefTouchEvent& event) { 310 NOTIMPLEMENTED(); 311} 312 313void CefBrowserPlatformDelegateNativeMac::SetFocus(bool setFocus) { 314 auto view = GetHostView(); 315 if (view) { 316 view->SetActive(setFocus); 317 318 if (setFocus) { 319 // Give keyboard focus to the native view. 320 NSView* view = web_contents_->GetContentNativeView().GetNativeNSView(); 321 DCHECK([view canBecomeKeyView]); 322 [[view window] makeFirstResponder:view]; 323 } 324 } 325} 326 327gfx::Point CefBrowserPlatformDelegateNativeMac::GetScreenPoint( 328 const gfx::Point& view) const { 329 if (windowless_handler_) 330 return windowless_handler_->GetParentScreenPoint(view); 331 332 NSView* nsview = CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(window_info_.parent_view); 333 if (nsview) { 334 NSRect bounds = [nsview bounds]; 335 NSPoint view_pt = {static_cast<CGFloat>(view.x()), 336 bounds.size.height - static_cast<CGFloat>(view.y())}; 337 NSPoint window_pt = [nsview convertPoint:view_pt toView:nil]; 338 NSPoint screen_pt = 339 ui::ConvertPointFromWindowToScreen([nsview window], window_pt); 340 return gfx::Point(screen_pt.x, screen_pt.y); 341 } 342 return gfx::Point(); 343} 344 345void CefBrowserPlatformDelegateNativeMac::ViewText(const std::string& text) { 346 // TODO(cef): Implement this functionality. 347 NOTIMPLEMENTED(); 348} 349 350bool CefBrowserPlatformDelegateNativeMac::HandleKeyboardEvent( 351 const content::NativeWebKeyboardEvent& event) { 352 // Give the top level menu equivalents a chance to handle the event. 353 if ([event.os_event type] == NSKeyDown) 354 return [[NSApp mainMenu] performKeyEquivalent:event.os_event]; 355 return false; 356} 357 358// static 359void CefBrowserPlatformDelegate::HandleExternalProtocol(const GURL& url) {} 360 361CefEventHandle CefBrowserPlatformDelegateNativeMac::GetEventHandle( 362 const content::NativeWebKeyboardEvent& event) const { 363 return event.os_event; 364} 365 366std::unique_ptr<CefFileDialogRunner> 367CefBrowserPlatformDelegateNativeMac::CreateFileDialogRunner() { 368 return base::WrapUnique(new CefFileDialogRunnerMac); 369} 370 371std::unique_ptr<CefJavaScriptDialogRunner> 372CefBrowserPlatformDelegateNativeMac::CreateJavaScriptDialogRunner() { 373 return base::WrapUnique(new CefJavaScriptDialogRunnerMac); 374} 375 376std::unique_ptr<CefMenuRunner> 377CefBrowserPlatformDelegateNativeMac::CreateMenuRunner() { 378 return base::WrapUnique(new CefMenuRunnerMac); 379} 380 381gfx::Point CefBrowserPlatformDelegateNativeMac::GetDialogPosition( 382 const gfx::Size& size) { 383 // Dialogs are always re-positioned by the constrained window sheet controller 384 // so nothing interesting to return yet. 385 return gfx::Point(); 386} 387 388gfx::Size CefBrowserPlatformDelegateNativeMac::GetMaximumDialogSize() { 389 if (!web_contents_) 390 return gfx::Size(); 391 392 // The dialog should try to fit within the overlay for the web contents. 393 // Note that, for things like print preview, this is just a suggested maximum. 394 return web_contents_->GetContainerBounds().size(); 395} 396 397content::NativeWebKeyboardEvent 398CefBrowserPlatformDelegateNativeMac::TranslateWebKeyEvent( 399 const CefKeyEvent& key_event) const { 400 content::NativeWebKeyboardEvent result( 401 blink::WebInputEvent::Type::kUndefined, 402 blink::WebInputEvent::Modifiers::kNoModifiers, ui::EventTimeForNow()); 403 404 // Use a synthetic NSEvent in order to obtain the windowsKeyCode member from 405 // the NativeWebKeyboardEvent constructor. This is the only member which can 406 // not be easily translated (without hardcoding keyCodes) 407 // Determining whether a modifier key is left or right seems to be done 408 // through the key code as well. 409 NSEventType event_type; 410 if (key_event.character == 0 && key_event.unmodified_character == 0) { 411 // Check if both character and unmodified_characther are empty to determine 412 // if this was a NSFlagsChanged event. 413 // A dead key will have an empty character, but a non-empty unmodified 414 // character 415 event_type = NSFlagsChanged; 416 } else { 417 switch (key_event.type) { 418 case KEYEVENT_RAWKEYDOWN: 419 case KEYEVENT_KEYDOWN: 420 case KEYEVENT_CHAR: 421 event_type = NSKeyDown; 422 break; 423 case KEYEVENT_KEYUP: 424 event_type = NSKeyUp; 425 break; 426 } 427 } 428 429 NSString* charactersIgnoringModifiers = 430 [[[NSString alloc] initWithCharacters:&key_event.unmodified_character 431 length:1] autorelease]; 432 NSString* characters = 433 [[[NSString alloc] initWithCharacters:&key_event.character 434 length:1] autorelease]; 435 436 NSEvent* synthetic_event = 437 [NSEvent keyEventWithType:event_type 438 location:NSMakePoint(0, 0) 439 modifierFlags:NativeModifiers(key_event.modifiers) 440 timestamp:currentEventTimestamp() 441 windowNumber:0 442 context:nil 443 characters:characters 444 charactersIgnoringModifiers:charactersIgnoringModifiers 445 isARepeat:NO 446 keyCode:key_event.native_key_code]; 447 448 result = content::NativeWebKeyboardEvent(synthetic_event); 449 if (key_event.type == KEYEVENT_CHAR) 450 result.SetType(blink::WebInputEvent::Type::kChar); 451 452 result.is_system_key = key_event.is_system_key; 453 454 return result; 455} 456 457blink::WebMouseEvent 458CefBrowserPlatformDelegateNativeMac::TranslateWebClickEvent( 459 const CefMouseEvent& mouse_event, 460 CefBrowserHost::MouseButtonType type, 461 bool mouseUp, 462 int clickCount) const { 463 blink::WebMouseEvent result; 464 TranslateWebMouseEvent(result, mouse_event); 465 466 switch (type) { 467 case MBT_LEFT: 468 result.SetType(mouseUp ? blink::WebInputEvent::Type::kMouseUp 469 : blink::WebInputEvent::Type::kMouseDown); 470 result.button = blink::WebMouseEvent::Button::kLeft; 471 break; 472 case MBT_MIDDLE: 473 result.SetType(mouseUp ? blink::WebInputEvent::Type::kMouseUp 474 : blink::WebInputEvent::Type::kMouseDown); 475 result.button = blink::WebMouseEvent::Button::kMiddle; 476 break; 477 case MBT_RIGHT: 478 result.SetType(mouseUp ? blink::WebInputEvent::Type::kMouseUp 479 : blink::WebInputEvent::Type::kMouseDown); 480 result.button = blink::WebMouseEvent::Button::kRight; 481 break; 482 default: 483 NOTREACHED(); 484 } 485 486 result.click_count = clickCount; 487 488 return result; 489} 490 491blink::WebMouseEvent CefBrowserPlatformDelegateNativeMac::TranslateWebMoveEvent( 492 const CefMouseEvent& mouse_event, 493 bool mouseLeave) const { 494 blink::WebMouseEvent result; 495 TranslateWebMouseEvent(result, mouse_event); 496 497 if (!mouseLeave) { 498 result.SetType(blink::WebInputEvent::Type::kMouseMove); 499 if (mouse_event.modifiers & EVENTFLAG_LEFT_MOUSE_BUTTON) 500 result.button = blink::WebMouseEvent::Button::kLeft; 501 else if (mouse_event.modifiers & EVENTFLAG_MIDDLE_MOUSE_BUTTON) 502 result.button = blink::WebMouseEvent::Button::kMiddle; 503 else if (mouse_event.modifiers & EVENTFLAG_RIGHT_MOUSE_BUTTON) 504 result.button = blink::WebMouseEvent::Button::kRight; 505 else 506 result.button = blink::WebMouseEvent::Button::kNoButton; 507 } else { 508 result.SetType(blink::WebInputEvent::Type::kMouseLeave); 509 result.button = blink::WebMouseEvent::Button::kNoButton; 510 } 511 512 result.click_count = 0; 513 514 return result; 515} 516 517blink::WebMouseWheelEvent 518CefBrowserPlatformDelegateNativeMac::TranslateWebWheelEvent( 519 const CefMouseEvent& mouse_event, 520 int deltaX, 521 int deltaY) const { 522 blink::WebMouseWheelEvent result; 523 TranslateWebMouseEvent(result, mouse_event); 524 525 result.SetType(blink::WebInputEvent::Type::kMouseWheel); 526 527 static const double scrollbarPixelsPerCocoaTick = 40.0; 528 result.delta_x = deltaX; 529 result.delta_y = deltaY; 530 result.wheel_ticks_x = deltaX / scrollbarPixelsPerCocoaTick; 531 result.wheel_ticks_y = deltaY / scrollbarPixelsPerCocoaTick; 532 result.delta_units = ui::ScrollGranularity::kScrollByPrecisePixel; 533 534 if (mouse_event.modifiers & EVENTFLAG_LEFT_MOUSE_BUTTON) 535 result.button = blink::WebMouseEvent::Button::kLeft; 536 else if (mouse_event.modifiers & EVENTFLAG_MIDDLE_MOUSE_BUTTON) 537 result.button = blink::WebMouseEvent::Button::kMiddle; 538 else if (mouse_event.modifiers & EVENTFLAG_RIGHT_MOUSE_BUTTON) 539 result.button = blink::WebMouseEvent::Button::kRight; 540 else 541 result.button = blink::WebMouseEvent::Button::kNoButton; 542 543 return result; 544} 545 546void CefBrowserPlatformDelegateNativeMac::TranslateWebMouseEvent( 547 blink::WebMouseEvent& result, 548 const CefMouseEvent& mouse_event) const { 549 // position 550 result.SetPositionInWidget(mouse_event.x, mouse_event.y); 551 552 const gfx::Point& screen_pt = 553 GetScreenPoint(gfx::Point(mouse_event.x, mouse_event.y)); 554 result.SetPositionInScreen(screen_pt.x(), screen_pt.y()); 555 556 // modifiers 557 result.SetModifiers(result.GetModifiers() | 558 TranslateWebEventModifiers(mouse_event.modifiers)); 559 560 // timestamp 561 result.SetTimeStamp(base::TimeTicks() + 562 base::Seconds(currentEventTimestamp())); 563 564 result.pointer_type = blink::WebPointerProperties::PointerType::kMouse; 565} 566 567content::RenderWidgetHostViewMac* 568CefBrowserPlatformDelegateNativeMac::GetHostView() const { 569 if (!web_contents_) 570 return nullptr; 571 return static_cast<content::RenderWidgetHostViewMac*>( 572 web_contents_->GetRenderWidgetHostView()); 573} 574