native_browser_view_mac.mm 13 KB

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