• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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