native_browser_view_mac.mm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. // Copyright (c) 2017 GitHub, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/native_browser_view_mac.h"
  5. #import <objc/runtime.h>
  6. #include <vector>
  7. #include "shell/browser/ui/drag_util.h"
  8. #include "shell/browser/ui/inspectable_web_contents.h"
  9. #include "shell/browser/ui/inspectable_web_contents_view.h"
  10. #include "skia/ext/skia_utils_mac.h"
  11. #include "ui/gfx/geometry/rect.h"
  12. // Match view::Views behavior where the view sticks to the top-left origin.
  13. const NSAutoresizingMaskOptions kDefaultAutoResizingMask =
  14. NSViewMaxXMargin | NSViewMinYMargin;
  15. @interface DragRegionView : NSView
  16. @property(assign) NSPoint initialLocation;
  17. @end
  18. @interface NSWindow ()
  19. - (void)performWindowDragWithEvent:(NSEvent*)event;
  20. @end
  21. @implementation DragRegionView
  22. @synthesize initialLocation;
  23. + (void)load {
  24. if (getenv("ELECTRON_DEBUG_DRAG_REGIONS")) {
  25. static dispatch_once_t onceToken;
  26. dispatch_once(&onceToken, ^{
  27. SEL originalSelector = @selector(drawRect:);
  28. SEL swizzledSelector = @selector(drawDebugRect:);
  29. Method originalMethod =
  30. class_getInstanceMethod([self class], originalSelector);
  31. Method swizzledMethod =
  32. class_getInstanceMethod([self class], swizzledSelector);
  33. BOOL didAddMethod =
  34. class_addMethod([self class], originalSelector,
  35. method_getImplementation(swizzledMethod),
  36. method_getTypeEncoding(swizzledMethod));
  37. if (didAddMethod) {
  38. class_replaceMethod([self class], swizzledSelector,
  39. method_getImplementation(originalMethod),
  40. method_getTypeEncoding(originalMethod));
  41. } else {
  42. method_exchangeImplementations(originalMethod, swizzledMethod);
  43. }
  44. });
  45. }
  46. }
  47. - (BOOL)mouseDownCanMoveWindow {
  48. return NO;
  49. }
  50. - (BOOL)shouldIgnoreMouseEvent {
  51. NSEventType type = [[NSApp currentEvent] type];
  52. return type != NSEventTypeLeftMouseDragged &&
  53. type != NSEventTypeLeftMouseDown;
  54. }
  55. - (NSView*)hitTest:(NSPoint)point {
  56. // Pass-through events that hit one of the exclusion zones
  57. for (NSView* exclusion_zones in [self subviews]) {
  58. if ([exclusion_zones hitTest:point])
  59. return nil;
  60. }
  61. return self;
  62. }
  63. - (void)mouseDown:(NSEvent*)event {
  64. [super mouseDown:event];
  65. if ([self.window respondsToSelector:@selector(performWindowDragWithEvent)]) {
  66. // According to Google, using performWindowDragWithEvent:
  67. // does not generate a NSWindowWillMoveNotification. Hence post one.
  68. [[NSNotificationCenter defaultCenter]
  69. postNotificationName:NSWindowWillMoveNotification
  70. object:self];
  71. if (@available(macOS 10.11, *)) {
  72. [self.window performWindowDragWithEvent:event];
  73. }
  74. return;
  75. }
  76. if (self.window.styleMask & NSWindowStyleMaskFullScreen) {
  77. return;
  78. }
  79. self.initialLocation = [event locationInWindow];
  80. }
  81. - (void)mouseDragged:(NSEvent*)event {
  82. if ([self.window respondsToSelector:@selector(performWindowDragWithEvent)]) {
  83. return;
  84. }
  85. if (self.window.styleMask & NSWindowStyleMaskFullScreen) {
  86. return;
  87. }
  88. NSPoint currentLocation = [NSEvent mouseLocation];
  89. NSPoint newOrigin;
  90. NSRect screenFrame = [[NSScreen mainScreen] frame];
  91. NSSize screenSize = screenFrame.size;
  92. NSRect windowFrame = [self.window frame];
  93. NSSize windowSize = windowFrame.size;
  94. newOrigin.x = currentLocation.x - self.initialLocation.x;
  95. newOrigin.y = currentLocation.y - self.initialLocation.y;
  96. BOOL inMenuBar = (newOrigin.y + windowSize.height) >
  97. (screenFrame.origin.y + screenSize.height);
  98. BOOL screenAboveMainScreen = false;
  99. if (inMenuBar) {
  100. for (NSScreen* screen in [NSScreen screens]) {
  101. NSRect currentScreenFrame = [screen frame];
  102. BOOL isHigher = currentScreenFrame.origin.y > screenFrame.origin.y;
  103. // If there's another screen that is generally above the current screen,
  104. // we'll draw a new rectangle that is just above the current screen. If
  105. // the "higher" screen intersects with this rectangle, we'll allow drawing
  106. // above the menubar.
  107. if (isHigher) {
  108. NSRect aboveScreenRect =
  109. NSMakeRect(screenFrame.origin.x,
  110. screenFrame.origin.y + screenFrame.size.height - 10,
  111. screenFrame.size.width, 200);
  112. BOOL screenAboveIntersects =
  113. NSIntersectsRect(currentScreenFrame, aboveScreenRect);
  114. if (screenAboveIntersects) {
  115. screenAboveMainScreen = true;
  116. break;
  117. }
  118. }
  119. }
  120. }
  121. // Don't let window get dragged up under the menu bar
  122. if (inMenuBar && !screenAboveMainScreen) {
  123. newOrigin.y = screenFrame.origin.y +
  124. (screenFrame.size.height - windowFrame.size.height);
  125. }
  126. // Move the window to the new location
  127. [self.window setFrameOrigin:newOrigin];
  128. }
  129. // For debugging purposes only.
  130. - (void)drawDebugRect:(NSRect)aRect {
  131. [[[NSColor greenColor] colorWithAlphaComponent:0.5] set];
  132. NSRectFill([self bounds]);
  133. }
  134. @end
  135. @interface ExcludeDragRegionView : NSView
  136. @end
  137. @implementation ExcludeDragRegionView
  138. + (void)load {
  139. if (getenv("ELECTRON_DEBUG_DRAG_REGIONS")) {
  140. static dispatch_once_t onceToken;
  141. dispatch_once(&onceToken, ^{
  142. SEL originalSelector = @selector(drawRect:);
  143. SEL swizzledSelector = @selector(drawDebugRect:);
  144. Method originalMethod =
  145. class_getInstanceMethod([self class], originalSelector);
  146. Method swizzledMethod =
  147. class_getInstanceMethod([self class], swizzledSelector);
  148. BOOL didAddMethod =
  149. class_addMethod([self class], originalSelector,
  150. method_getImplementation(swizzledMethod),
  151. method_getTypeEncoding(swizzledMethod));
  152. if (didAddMethod) {
  153. class_replaceMethod([self class], swizzledSelector,
  154. method_getImplementation(originalMethod),
  155. method_getTypeEncoding(originalMethod));
  156. } else {
  157. method_exchangeImplementations(originalMethod, swizzledMethod);
  158. }
  159. });
  160. }
  161. }
  162. - (BOOL)mouseDownCanMoveWindow {
  163. return NO;
  164. }
  165. // For debugging purposes only.
  166. - (void)drawDebugRect:(NSRect)aRect {
  167. [[[NSColor redColor] colorWithAlphaComponent:0.5] set];
  168. NSRectFill([self bounds]);
  169. }
  170. @end
  171. namespace electron {
  172. NativeBrowserViewMac::NativeBrowserViewMac(
  173. InspectableWebContents* inspectable_web_contents)
  174. : NativeBrowserView(inspectable_web_contents) {
  175. auto* iwc_view = GetInspectableWebContentsView();
  176. if (!iwc_view)
  177. return;
  178. auto* view = iwc_view->GetNativeView().GetNativeNSView();
  179. view.autoresizingMask = kDefaultAutoResizingMask;
  180. }
  181. NativeBrowserViewMac::~NativeBrowserViewMac() {}
  182. void NativeBrowserViewMac::SetAutoResizeFlags(uint8_t flags) {
  183. NSAutoresizingMaskOptions autoresizing_mask = kDefaultAutoResizingMask;
  184. if (flags & kAutoResizeWidth) {
  185. autoresizing_mask |= NSViewWidthSizable;
  186. }
  187. if (flags & kAutoResizeHeight) {
  188. autoresizing_mask |= NSViewHeightSizable;
  189. }
  190. if (flags & kAutoResizeHorizontal) {
  191. autoresizing_mask |=
  192. NSViewMaxXMargin | NSViewMinXMargin | NSViewWidthSizable;
  193. }
  194. if (flags & kAutoResizeVertical) {
  195. autoresizing_mask |=
  196. NSViewMaxYMargin | NSViewMinYMargin | NSViewHeightSizable;
  197. }
  198. auto* iwc_view = GetInspectableWebContentsView();
  199. if (!iwc_view)
  200. return;
  201. auto* view = iwc_view->GetNativeView().GetNativeNSView();
  202. view.autoresizingMask = autoresizing_mask;
  203. }
  204. void NativeBrowserViewMac::SetBounds(const gfx::Rect& bounds) {
  205. auto* iwc_view = GetInspectableWebContentsView();
  206. if (!iwc_view)
  207. return;
  208. auto* view = iwc_view->GetNativeView().GetNativeNSView();
  209. auto* superview = view.superview;
  210. const auto superview_height = superview ? superview.frame.size.height : 0;
  211. view.frame =
  212. NSMakeRect(bounds.x(), superview_height - bounds.y() - bounds.height(),
  213. bounds.width(), bounds.height());
  214. // Ensure draggable regions are properly updated to reflect new bounds.
  215. UpdateDraggableRegions(draggable_regions_);
  216. }
  217. gfx::Rect NativeBrowserViewMac::GetBounds() {
  218. auto* iwc_view = GetInspectableWebContentsView();
  219. if (!iwc_view)
  220. return gfx::Rect();
  221. NSView* view = iwc_view->GetNativeView().GetNativeNSView();
  222. const int superview_height =
  223. (view.superview) ? view.superview.frame.size.height : 0;
  224. return gfx::Rect(
  225. view.frame.origin.x,
  226. superview_height - view.frame.origin.y - view.frame.size.height,
  227. view.frame.size.width, view.frame.size.height);
  228. }
  229. void NativeBrowserViewMac::SetBackgroundColor(SkColor color) {
  230. auto* iwc_view = GetInspectableWebContentsView();
  231. if (!iwc_view)
  232. return;
  233. auto* view = iwc_view->GetNativeView().GetNativeNSView();
  234. view.wantsLayer = YES;
  235. view.layer.backgroundColor = skia::CGColorCreateFromSkColor(color);
  236. }
  237. void NativeBrowserViewMac::UpdateDraggableRegions(
  238. const std::vector<gfx::Rect>& drag_exclude_rects) {
  239. if (!inspectable_web_contents_)
  240. return;
  241. auto* web_contents = inspectable_web_contents_->GetWebContents();
  242. auto* iwc_view = GetInspectableWebContentsView();
  243. if (!iwc_view || !web_contents)
  244. return;
  245. NSView* web_view = GetWebContents()->GetNativeView().GetNativeNSView();
  246. NSView* inspectable_view = iwc_view->GetNativeView().GetNativeNSView();
  247. NSView* window_content_view = inspectable_view.superview;
  248. // Remove all DragRegionViews that were added last time. Note that we need
  249. // to copy the `subviews` array to avoid mutation during iteration.
  250. base::scoped_nsobject<NSArray> subviews([[web_view subviews] copy]);
  251. for (NSView* subview in subviews.get()) {
  252. if ([subview isKindOfClass:[DragRegionView class]]) {
  253. [subview removeFromSuperview];
  254. }
  255. }
  256. // Create one giant NSView that is draggable.
  257. base::scoped_nsobject<NSView> drag_region_view(
  258. [[DragRegionView alloc] initWithFrame:web_view.bounds]);
  259. [web_view addSubview:drag_region_view];
  260. // Then, on top of that, add "exclusion zones".
  261. auto const offset = GetBounds().OffsetFromOrigin();
  262. const auto window_content_view_height = NSHeight(window_content_view.bounds);
  263. for (const auto& rect : drag_exclude_rects) {
  264. const auto x = rect.x() + offset.x();
  265. const auto y = window_content_view_height - (rect.bottom() + offset.y());
  266. const auto exclude_rect = NSMakeRect(x, y, rect.width(), rect.height());
  267. const auto drag_region_view_exclude_rect =
  268. [window_content_view convertRect:exclude_rect toView:drag_region_view];
  269. base::scoped_nsobject<NSView> exclude_drag_region_view(
  270. [[ExcludeDragRegionView alloc]
  271. initWithFrame:drag_region_view_exclude_rect]);
  272. [drag_region_view addSubview:exclude_drag_region_view];
  273. }
  274. }
  275. void NativeBrowserViewMac::UpdateDraggableRegions(
  276. const std::vector<mojom::DraggableRegionPtr>& regions) {
  277. if (!inspectable_web_contents_)
  278. return;
  279. auto* web_contents = inspectable_web_contents_->GetWebContents();
  280. NSView* web_view = web_contents->GetNativeView().GetNativeNSView();
  281. NSInteger webViewWidth = NSWidth([web_view bounds]);
  282. NSInteger webViewHeight = NSHeight([web_view bounds]);
  283. // Draggable regions are implemented by having the whole web view draggable
  284. // and overlaying regions that are not draggable.
  285. if (&draggable_regions_ != &regions)
  286. draggable_regions_ = mojo::Clone(regions);
  287. std::vector<gfx::Rect> drag_exclude_rects;
  288. if (draggable_regions_.empty()) {
  289. drag_exclude_rects.emplace_back(0, 0, webViewWidth, webViewHeight);
  290. } else {
  291. drag_exclude_rects = CalculateNonDraggableRegions(
  292. DraggableRegionsToSkRegion(draggable_regions_), webViewWidth,
  293. webViewHeight);
  294. }
  295. UpdateDraggableRegions(drag_exclude_rects);
  296. }
  297. // static
  298. NativeBrowserView* NativeBrowserView::Create(
  299. InspectableWebContents* inspectable_web_contents) {
  300. return new NativeBrowserViewMac(inspectable_web_contents);
  301. }
  302. } // namespace electron