366 lines
		
	
	
	
		
			12 KiB
			
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			366 lines
		
	
	
	
		
			12 KiB
			
		
	
	
	
		
			Text
		
	
	
	
	
	
// Copyright (c) 2017 GitHub, Inc.
 | 
						|
// Use of this source code is governed by the MIT license that can be
 | 
						|
// found in the LICENSE file.
 | 
						|
 | 
						|
#include "shell/browser/native_browser_view_mac.h"
 | 
						|
 | 
						|
#import <objc/runtime.h>
 | 
						|
#include <vector>
 | 
						|
 | 
						|
#include "shell/browser/ui/drag_util.h"
 | 
						|
#include "shell/browser/ui/inspectable_web_contents.h"
 | 
						|
#include "shell/browser/ui/inspectable_web_contents_view.h"
 | 
						|
#include "skia/ext/skia_utils_mac.h"
 | 
						|
#include "ui/gfx/geometry/rect.h"
 | 
						|
 | 
						|
// Match view::Views behavior where the view sticks to the top-left origin.
 | 
						|
const NSAutoresizingMaskOptions kDefaultAutoResizingMask =
 | 
						|
    NSViewMaxXMargin | NSViewMinYMargin;
 | 
						|
 | 
						|
@interface DragRegionView : NSView
 | 
						|
 | 
						|
@property(assign) NSPoint initialLocation;
 | 
						|
 | 
						|
@end
 | 
						|
 | 
						|
@interface NSWindow ()
 | 
						|
- (void)performWindowDragWithEvent:(NSEvent*)event;
 | 
						|
@end
 | 
						|
 | 
						|
@implementation DragRegionView
 | 
						|
 | 
						|
@synthesize initialLocation;
 | 
						|
 | 
						|
+ (void)load {
 | 
						|
  if (getenv("ELECTRON_DEBUG_DRAG_REGIONS")) {
 | 
						|
    static dispatch_once_t onceToken;
 | 
						|
    dispatch_once(&onceToken, ^{
 | 
						|
      SEL originalSelector = @selector(drawRect:);
 | 
						|
      SEL swizzledSelector = @selector(drawDebugRect:);
 | 
						|
 | 
						|
      Method originalMethod =
 | 
						|
          class_getInstanceMethod([self class], originalSelector);
 | 
						|
      Method swizzledMethod =
 | 
						|
          class_getInstanceMethod([self class], swizzledSelector);
 | 
						|
      BOOL didAddMethod =
 | 
						|
          class_addMethod([self class], originalSelector,
 | 
						|
                          method_getImplementation(swizzledMethod),
 | 
						|
                          method_getTypeEncoding(swizzledMethod));
 | 
						|
 | 
						|
      if (didAddMethod) {
 | 
						|
        class_replaceMethod([self class], swizzledSelector,
 | 
						|
                            method_getImplementation(originalMethod),
 | 
						|
                            method_getTypeEncoding(originalMethod));
 | 
						|
      } else {
 | 
						|
        method_exchangeImplementations(originalMethod, swizzledMethod);
 | 
						|
      }
 | 
						|
    });
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
- (BOOL)mouseDownCanMoveWindow {
 | 
						|
  return NO;
 | 
						|
}
 | 
						|
 | 
						|
- (BOOL)shouldIgnoreMouseEvent {
 | 
						|
  NSEventType type = [[NSApp currentEvent] type];
 | 
						|
  return type != NSEventTypeLeftMouseDragged &&
 | 
						|
         type != NSEventTypeLeftMouseDown;
 | 
						|
}
 | 
						|
 | 
						|
- (NSView*)hitTest:(NSPoint)point {
 | 
						|
  // Pass-through events that hit one of the exclusion zones
 | 
						|
  for (NSView* exclusion_zones in [self subviews]) {
 | 
						|
    if ([exclusion_zones hitTest:point])
 | 
						|
      return nil;
 | 
						|
  }
 | 
						|
 | 
						|
  return self;
 | 
						|
}
 | 
						|
 | 
						|
- (void)mouseDown:(NSEvent*)event {
 | 
						|
  [super mouseDown:event];
 | 
						|
 | 
						|
  if ([self.window respondsToSelector:@selector(performWindowDragWithEvent)]) {
 | 
						|
    // According to Google, using performWindowDragWithEvent:
 | 
						|
    // does not generate a NSWindowWillMoveNotification. Hence post one.
 | 
						|
    [[NSNotificationCenter defaultCenter]
 | 
						|
        postNotificationName:NSWindowWillMoveNotification
 | 
						|
                      object:self];
 | 
						|
 | 
						|
    if (@available(macOS 10.11, *)) {
 | 
						|
      [self.window performWindowDragWithEvent:event];
 | 
						|
    }
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (self.window.styleMask & NSWindowStyleMaskFullScreen) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  self.initialLocation = [event locationInWindow];
 | 
						|
}
 | 
						|
 | 
						|
- (void)mouseDragged:(NSEvent*)event {
 | 
						|
  if ([self.window respondsToSelector:@selector(performWindowDragWithEvent)]) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (self.window.styleMask & NSWindowStyleMaskFullScreen) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  NSPoint currentLocation = [NSEvent mouseLocation];
 | 
						|
  NSPoint newOrigin;
 | 
						|
 | 
						|
  NSRect screenFrame = [[NSScreen mainScreen] frame];
 | 
						|
  NSSize screenSize = screenFrame.size;
 | 
						|
  NSRect windowFrame = [self.window frame];
 | 
						|
  NSSize windowSize = windowFrame.size;
 | 
						|
 | 
						|
  newOrigin.x = currentLocation.x - self.initialLocation.x;
 | 
						|
  newOrigin.y = currentLocation.y - self.initialLocation.y;
 | 
						|
 | 
						|
  BOOL inMenuBar = (newOrigin.y + windowSize.height) >
 | 
						|
                   (screenFrame.origin.y + screenSize.height);
 | 
						|
  BOOL screenAboveMainScreen = false;
 | 
						|
 | 
						|
  if (inMenuBar) {
 | 
						|
    for (NSScreen* screen in [NSScreen screens]) {
 | 
						|
      NSRect currentScreenFrame = [screen frame];
 | 
						|
      BOOL isHigher = currentScreenFrame.origin.y > screenFrame.origin.y;
 | 
						|
 | 
						|
      // If there's another screen that is generally above the current screen,
 | 
						|
      // we'll draw a new rectangle that is just above the current screen. If
 | 
						|
      // the "higher" screen intersects with this rectangle, we'll allow drawing
 | 
						|
      // above the menubar.
 | 
						|
      if (isHigher) {
 | 
						|
        NSRect aboveScreenRect =
 | 
						|
            NSMakeRect(screenFrame.origin.x,
 | 
						|
                       screenFrame.origin.y + screenFrame.size.height - 10,
 | 
						|
                       screenFrame.size.width, 200);
 | 
						|
 | 
						|
        BOOL screenAboveIntersects =
 | 
						|
            NSIntersectsRect(currentScreenFrame, aboveScreenRect);
 | 
						|
 | 
						|
        if (screenAboveIntersects) {
 | 
						|
          screenAboveMainScreen = true;
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Don't let window get dragged up under the menu bar
 | 
						|
  if (inMenuBar && !screenAboveMainScreen) {
 | 
						|
    newOrigin.y = screenFrame.origin.y +
 | 
						|
                  (screenFrame.size.height - windowFrame.size.height);
 | 
						|
  }
 | 
						|
 | 
						|
  // Move the window to the new location
 | 
						|
  [self.window setFrameOrigin:newOrigin];
 | 
						|
}
 | 
						|
 | 
						|
// For debugging purposes only.
 | 
						|
- (void)drawDebugRect:(NSRect)aRect {
 | 
						|
  [[[NSColor greenColor] colorWithAlphaComponent:0.5] set];
 | 
						|
  NSRectFill([self bounds]);
 | 
						|
}
 | 
						|
 | 
						|
@end
 | 
						|
 | 
						|
@interface ExcludeDragRegionView : NSView
 | 
						|
@end
 | 
						|
 | 
						|
@implementation ExcludeDragRegionView
 | 
						|
 | 
						|
+ (void)load {
 | 
						|
  if (getenv("ELECTRON_DEBUG_DRAG_REGIONS")) {
 | 
						|
    static dispatch_once_t onceToken;
 | 
						|
    dispatch_once(&onceToken, ^{
 | 
						|
      SEL originalSelector = @selector(drawRect:);
 | 
						|
      SEL swizzledSelector = @selector(drawDebugRect:);
 | 
						|
 | 
						|
      Method originalMethod =
 | 
						|
          class_getInstanceMethod([self class], originalSelector);
 | 
						|
      Method swizzledMethod =
 | 
						|
          class_getInstanceMethod([self class], swizzledSelector);
 | 
						|
      BOOL didAddMethod =
 | 
						|
          class_addMethod([self class], originalSelector,
 | 
						|
                          method_getImplementation(swizzledMethod),
 | 
						|
                          method_getTypeEncoding(swizzledMethod));
 | 
						|
 | 
						|
      if (didAddMethod) {
 | 
						|
        class_replaceMethod([self class], swizzledSelector,
 | 
						|
                            method_getImplementation(originalMethod),
 | 
						|
                            method_getTypeEncoding(originalMethod));
 | 
						|
      } else {
 | 
						|
        method_exchangeImplementations(originalMethod, swizzledMethod);
 | 
						|
      }
 | 
						|
    });
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
- (BOOL)mouseDownCanMoveWindow {
 | 
						|
  return NO;
 | 
						|
}
 | 
						|
 | 
						|
// For debugging purposes only.
 | 
						|
- (void)drawDebugRect:(NSRect)aRect {
 | 
						|
  [[[NSColor redColor] colorWithAlphaComponent:0.5] set];
 | 
						|
  NSRectFill([self bounds]);
 | 
						|
}
 | 
						|
 | 
						|
@end
 | 
						|
 | 
						|
namespace electron {
 | 
						|
 | 
						|
NativeBrowserViewMac::NativeBrowserViewMac(
 | 
						|
    InspectableWebContents* inspectable_web_contents)
 | 
						|
    : NativeBrowserView(inspectable_web_contents) {
 | 
						|
  auto* iwc_view = GetInspectableWebContentsView();
 | 
						|
  if (!iwc_view)
 | 
						|
    return;
 | 
						|
  auto* view = iwc_view->GetNativeView().GetNativeNSView();
 | 
						|
  view.autoresizingMask = kDefaultAutoResizingMask;
 | 
						|
}
 | 
						|
 | 
						|
NativeBrowserViewMac::~NativeBrowserViewMac() = default;
 | 
						|
 | 
						|
void NativeBrowserViewMac::SetAutoResizeFlags(uint8_t flags) {
 | 
						|
  NSAutoresizingMaskOptions autoresizing_mask = kDefaultAutoResizingMask;
 | 
						|
  if (flags & kAutoResizeWidth) {
 | 
						|
    autoresizing_mask |= NSViewWidthSizable;
 | 
						|
  }
 | 
						|
  if (flags & kAutoResizeHeight) {
 | 
						|
    autoresizing_mask |= NSViewHeightSizable;
 | 
						|
  }
 | 
						|
  if (flags & kAutoResizeHorizontal) {
 | 
						|
    autoresizing_mask |=
 | 
						|
        NSViewMaxXMargin | NSViewMinXMargin | NSViewWidthSizable;
 | 
						|
  }
 | 
						|
  if (flags & kAutoResizeVertical) {
 | 
						|
    autoresizing_mask |=
 | 
						|
        NSViewMaxYMargin | NSViewMinYMargin | NSViewHeightSizable;
 | 
						|
  }
 | 
						|
 | 
						|
  auto* iwc_view = GetInspectableWebContentsView();
 | 
						|
  if (!iwc_view)
 | 
						|
    return;
 | 
						|
  auto* view = iwc_view->GetNativeView().GetNativeNSView();
 | 
						|
  view.autoresizingMask = autoresizing_mask;
 | 
						|
}
 | 
						|
 | 
						|
void NativeBrowserViewMac::SetBounds(const gfx::Rect& bounds) {
 | 
						|
  auto* iwc_view = GetInspectableWebContentsView();
 | 
						|
  if (!iwc_view)
 | 
						|
    return;
 | 
						|
  auto* view = iwc_view->GetNativeView().GetNativeNSView();
 | 
						|
  auto* superview = view.superview;
 | 
						|
  const auto superview_height = superview ? superview.frame.size.height : 0;
 | 
						|
  view.frame =
 | 
						|
      NSMakeRect(bounds.x(), superview_height - bounds.y() - bounds.height(),
 | 
						|
                 bounds.width(), bounds.height());
 | 
						|
 | 
						|
  // Ensure draggable regions are properly updated to reflect new bounds.
 | 
						|
  UpdateDraggableRegions(draggable_regions_);
 | 
						|
}
 | 
						|
 | 
						|
gfx::Rect NativeBrowserViewMac::GetBounds() {
 | 
						|
  auto* iwc_view = GetInspectableWebContentsView();
 | 
						|
  if (!iwc_view)
 | 
						|
    return gfx::Rect();
 | 
						|
  NSView* view = iwc_view->GetNativeView().GetNativeNSView();
 | 
						|
  const int superview_height =
 | 
						|
      (view.superview) ? view.superview.frame.size.height : 0;
 | 
						|
  return gfx::Rect(
 | 
						|
      view.frame.origin.x,
 | 
						|
      superview_height - view.frame.origin.y - view.frame.size.height,
 | 
						|
      view.frame.size.width, view.frame.size.height);
 | 
						|
}
 | 
						|
 | 
						|
void NativeBrowserViewMac::SetBackgroundColor(SkColor color) {
 | 
						|
  auto* iwc_view = GetInspectableWebContentsView();
 | 
						|
  if (!iwc_view)
 | 
						|
    return;
 | 
						|
  auto* view = iwc_view->GetNativeView().GetNativeNSView();
 | 
						|
  view.wantsLayer = YES;
 | 
						|
  view.layer.backgroundColor = skia::CGColorCreateFromSkColor(color);
 | 
						|
}
 | 
						|
 | 
						|
void NativeBrowserViewMac::UpdateDraggableRegions(
 | 
						|
    const std::vector<gfx::Rect>& drag_exclude_rects) {
 | 
						|
  if (!inspectable_web_contents_)
 | 
						|
    return;
 | 
						|
  auto* web_contents = inspectable_web_contents_->GetWebContents();
 | 
						|
  auto* iwc_view = GetInspectableWebContentsView();
 | 
						|
  NSView* web_view = web_contents->GetNativeView().GetNativeNSView();
 | 
						|
  NSView* inspectable_view = iwc_view->GetNativeView().GetNativeNSView();
 | 
						|
  NSView* window_content_view = inspectable_view.superview;
 | 
						|
 | 
						|
  // Remove all DragRegionViews that were added last time. Note that we need
 | 
						|
  // to copy the `subviews` array to avoid mutation during iteration.
 | 
						|
  base::scoped_nsobject<NSArray> subviews([[web_view subviews] copy]);
 | 
						|
  for (NSView* subview in subviews.get()) {
 | 
						|
    if ([subview isKindOfClass:[DragRegionView class]]) {
 | 
						|
      [subview removeFromSuperview];
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Create one giant NSView that is draggable.
 | 
						|
  base::scoped_nsobject<NSView> drag_region_view(
 | 
						|
      [[DragRegionView alloc] initWithFrame:web_view.bounds]);
 | 
						|
  [web_view addSubview:drag_region_view];
 | 
						|
 | 
						|
  // Then, on top of that, add "exclusion zones".
 | 
						|
  auto const offset = GetBounds().OffsetFromOrigin();
 | 
						|
  const auto window_content_view_height = NSHeight(window_content_view.bounds);
 | 
						|
  for (const auto& rect : drag_exclude_rects) {
 | 
						|
    const auto x = rect.x() + offset.x();
 | 
						|
    const auto y = window_content_view_height - (rect.bottom() + offset.y());
 | 
						|
    const auto exclude_rect = NSMakeRect(x, y, rect.width(), rect.height());
 | 
						|
 | 
						|
    const auto drag_region_view_exclude_rect =
 | 
						|
        [window_content_view convertRect:exclude_rect toView:drag_region_view];
 | 
						|
 | 
						|
    base::scoped_nsobject<NSView> exclude_drag_region_view(
 | 
						|
        [[ExcludeDragRegionView alloc]
 | 
						|
            initWithFrame:drag_region_view_exclude_rect]);
 | 
						|
    [drag_region_view addSubview:exclude_drag_region_view];
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void NativeBrowserViewMac::UpdateDraggableRegions(
 | 
						|
    const std::vector<mojom::DraggableRegionPtr>& regions) {
 | 
						|
  if (!inspectable_web_contents_)
 | 
						|
    return;
 | 
						|
  auto* web_contents = inspectable_web_contents_->GetWebContents();
 | 
						|
  NSView* web_view = web_contents->GetNativeView().GetNativeNSView();
 | 
						|
 | 
						|
  NSInteger webViewWidth = NSWidth([web_view bounds]);
 | 
						|
  NSInteger webViewHeight = NSHeight([web_view bounds]);
 | 
						|
 | 
						|
  // Draggable regions are implemented by having the whole web view draggable
 | 
						|
  // and overlaying regions that are not draggable.
 | 
						|
  if (&draggable_regions_ != ®ions)
 | 
						|
    draggable_regions_ = mojo::Clone(regions);
 | 
						|
 | 
						|
  std::vector<gfx::Rect> drag_exclude_rects;
 | 
						|
  if (draggable_regions_.empty()) {
 | 
						|
    drag_exclude_rects.emplace_back(0, 0, webViewWidth, webViewHeight);
 | 
						|
  } else {
 | 
						|
    drag_exclude_rects = CalculateNonDraggableRegions(
 | 
						|
        DraggableRegionsToSkRegion(draggable_regions_), webViewWidth,
 | 
						|
        webViewHeight);
 | 
						|
  }
 | 
						|
 | 
						|
  UpdateDraggableRegions(drag_exclude_rects);
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
NativeBrowserView* NativeBrowserView::Create(
 | 
						|
    InspectableWebContents* inspectable_web_contents) {
 | 
						|
  return new NativeBrowserViewMac(inspectable_web_contents);
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace electron
 |