123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- // 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
- [self.window respondsToSelector:@selector(performWindowDragWithEvent:)];
- }
- - (BOOL)acceptsFirstMouse:(NSEvent*)event {
- return YES;
- }
- - (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];
- [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;
- // We need to use the content rect to calculate the titlebar height if the
- // superview is an framed NSWindow, otherwise it will be offset incorrectly by
- // the height of the titlebar.
- auto titlebar_height = 0;
- if (auto* win = [superview window]) {
- const auto content_rect_height =
- [win contentRectForFrameRect:superview.frame].size.height;
- titlebar_height = superview_height - content_rect_height;
- }
- auto new_height =
- superview_height - bounds.y() - bounds.height() + titlebar_height;
- view.frame =
- NSMakeRect(bounds.x(), new_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();
- auto* superview = view.superview;
- const int superview_height = superview ? superview.frame.size.height : 0;
- // We need to use the content rect to calculate the titlebar height if the
- // superview is an framed NSWindow, otherwise it will be offset incorrectly by
- // the height of the titlebar.
- auto titlebar_height = 0;
- if (auto* win = [superview window]) {
- const auto content_rect_height =
- [win contentRectForFrameRect:superview.frame].size.height;
- titlebar_height = superview_height - content_rect_height;
- }
- auto new_height = superview_height - view.frame.origin.y -
- view.frame.size.height + titlebar_height;
- return gfx::Rect(view.frame.origin.x, new_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
|