Browse Source

chore: Move draggable regions implementation from NativeBrowserView into InspectableWebContentsView (#35007)

* hore: Move draggable regions implementation from NativeBrowserView into InspectableWebContentsView

The draggable regions implementation is related to WebView, so
InspectableWebContentsView is a more appropriate place to put it there.
Also, this refactoring will allow the subsequent extension of the
WebContentsView API, which will eventually replace BrowserView API.

* fix: Lint error

* fix: Adjusted owner-window
Daniel Kocielinski 2 years ago
parent
commit
23d4a252c6

+ 1 - 0
filenames.gni

@@ -494,6 +494,7 @@ filenames = {
     "shell/browser/ui/inspectable_web_contents.cc",
     "shell/browser/ui/inspectable_web_contents.h",
     "shell/browser/ui/inspectable_web_contents_delegate.h",
+    "shell/browser/ui/inspectable_web_contents_view.cc",
     "shell/browser/ui/inspectable_web_contents_view.h",
     "shell/browser/ui/inspectable_web_contents_view_delegate.cc",
     "shell/browser/ui/inspectable_web_contents_view_delegate.h",

+ 13 - 1
shell/browser/api/electron_api_browser_view.cc

@@ -105,7 +105,16 @@ void BrowserView::SetOwnerWindow(BaseWindow* window) {
   if (web_contents())
     web_contents()->SetOwnerWindow(window ? window->window() : nullptr);
 
+  if (owner_window_.get()) {
+    owner_window_->window()->remove_inspectable_view(
+        view_->GetInspectableWebContentsView());
+  }
+
   owner_window_ = window ? window->GetWeakPtr() : nullptr;
+
+  if (owner_window_.get() && view_->GetInspectableWebContentsView())
+    owner_window_->window()->add_inspectable_view(
+        view_->GetInspectableWebContentsView());
 }
 
 BrowserView::~BrowserView() {
@@ -123,7 +132,10 @@ void BrowserView::WebContentsDestroyed() {
 
 void BrowserView::OnDraggableRegionsUpdated(
     const std::vector<mojom::DraggableRegionPtr>& regions) {
-  view_->UpdateDraggableRegions(regions);
+  InspectableWebContentsView* iwc_view = view_->GetInspectableWebContentsView();
+  if (!iwc_view)
+    return;
+  iwc_view->UpdateDraggableRegions(regions);
 }
 
 // static

+ 2 - 2
shell/browser/api/electron_api_browser_window.cc

@@ -215,8 +215,8 @@ void BrowserWindow::OnCloseButtonClicked(bool* prevent_default) {
   api_web_contents_->NotifyUserActivation();
 
   // Trigger beforeunload events for associated BrowserViews.
-  for (NativeBrowserView* view : window_->browser_views()) {
-    auto* vwc = view->web_contents();
+  for (InspectableWebContentsView* view : window_->inspectable_views()) {
+    auto* vwc = view->inspectable_web_contents()->GetWebContents();
     auto* api_web_contents = api::WebContents::From(vwc);
 
     // Required to make beforeunload handler work.

+ 0 - 13
shell/browser/native_browser_view.h

@@ -9,7 +9,6 @@
 
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
-#include "shell/common/api/api.mojom.h"
 #include "third_party/skia/include/core/SkColor.h"
 
 namespace gfx {
@@ -43,10 +42,6 @@ class NativeBrowserView : public content::WebContentsObserver {
     return inspectable_web_contents_;
   }
 
-  const std::vector<mojom::DraggableRegionPtr>& GetDraggableRegions() const {
-    return draggable_regions_;
-  }
-
   InspectableWebContentsView* GetInspectableWebContentsView();
 
   virtual void SetAutoResizeFlags(uint8_t flags) = 0;
@@ -54,20 +49,12 @@ class NativeBrowserView : public content::WebContentsObserver {
   virtual gfx::Rect GetBounds() = 0;
   virtual void SetBackgroundColor(SkColor color) = 0;
 
-  virtual void UpdateDraggableRegions(
-      const std::vector<gfx::Rect>& drag_exclude_rects) {}
-
-  // Called when the window needs to update its draggable region.
-  virtual void UpdateDraggableRegions(
-      const std::vector<mojom::DraggableRegionPtr>& regions) {}
-
  protected:
   explicit NativeBrowserView(InspectableWebContents* inspectable_web_contents);
   // content::WebContentsObserver:
   void WebContentsDestroyed() override;
 
   InspectableWebContents* inspectable_web_contents_;
-  std::vector<mojom::DraggableRegionPtr> draggable_regions_;
 };
 
 }  // namespace electron

+ 0 - 6
shell/browser/native_browser_view_mac.h

@@ -23,12 +23,6 @@ class NativeBrowserViewMac : public NativeBrowserView {
   void SetBounds(const gfx::Rect& bounds) override;
   gfx::Rect GetBounds() override;
   void SetBackgroundColor(SkColor color) override;
-
-  void UpdateDraggableRegions(
-      const std::vector<mojom::DraggableRegionPtr>& regions) override;
-
-  void UpdateDraggableRegions(
-      const std::vector<gfx::Rect>& drag_exclude_rects) override;
 };
 
 }  // namespace electron

+ 1 - 274
shell/browser/native_browser_view_mac.mm

@@ -4,10 +4,6 @@
 
 #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"
@@ -17,206 +13,6 @@
 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(
@@ -279,7 +75,7 @@ void NativeBrowserViewMac::SetBounds(const gfx::Rect& bounds) {
       NSMakeRect(bounds.x(), new_height, bounds.width(), bounds.height());
 
   // Ensure draggable regions are properly updated to reflect new bounds.
-  UpdateDraggableRegions(draggable_regions_);
+  iwc_view->UpdateDraggableRegions(iwc_view->GetDraggableRegions());
 }
 
 gfx::Rect NativeBrowserViewMac::GetBounds() {
@@ -315,75 +111,6 @@ void NativeBrowserViewMac::SetBackgroundColor(SkColor color) {
   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_ != &regions)
-    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) {

+ 0 - 23
shell/browser/native_browser_view_views.cc

@@ -6,7 +6,6 @@
 
 #include <vector>
 
-#include "shell/browser/ui/drag_util.h"
 #include "shell/browser/ui/views/inspectable_web_contents_view_views.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/views/background.h"
@@ -25,25 +24,6 @@ void NativeBrowserViewViews::SetAutoResizeFlags(uint8_t flags) {
   ResetAutoResizeProportions();
 }
 
-void NativeBrowserViewViews::UpdateDraggableRegions(
-    const std::vector<mojom::DraggableRegionPtr>& regions) {
-  if (&draggable_regions_ != &regions)
-    draggable_regions_ = mojo::Clone(regions);
-
-  // We need to snap the regions to the bounds of the current BrowserView.
-  // For example, if an attached BrowserView is draggable but its bounds are
-  // { x: 200,  y: 100, width: 300, height: 300 }
-  // then we need to add 200 to the x-value and 100 to the
-  // y-value of each of the passed regions or it will be incorrectly
-  // assumed that the regions begin in the top left corner as they
-  // would for the main client window.
-  auto const offset = GetBounds().OffsetFromOrigin();
-  for (auto& snapped_region : draggable_regions_) {
-    snapped_region->bounds.Offset(offset);
-  }
-  draggable_region_ = DraggableRegionsToSkRegion(draggable_regions_);
-}
-
 void NativeBrowserViewViews::SetAutoResizeProportions(
     const gfx::Size& window_size) {
   if ((auto_resize_flags_ & AutoResizeFlags::kAutoResizeHorizontal) &&
@@ -132,9 +112,6 @@ void NativeBrowserViewViews::SetBounds(const gfx::Rect& bounds) {
 
   view->InvalidateLayout();
   view->SchedulePaint();
-
-  // Ensure draggable regions are properly updated to reflect new bounds.
-  UpdateDraggableRegions(draggable_regions_);
 }
 
 gfx::Rect NativeBrowserViewViews::GetBounds() {

+ 0 - 7
shell/browser/native_browser_view_views.h

@@ -9,7 +9,6 @@
 #include <vector>
 
 #include "shell/browser/native_browser_view.h"
-#include "third_party/skia/include/core/SkRegion.h"
 
 namespace electron {
 
@@ -30,14 +29,10 @@ class NativeBrowserViewViews : public NativeBrowserView {
   void SetBounds(const gfx::Rect& bounds) override;
   gfx::Rect GetBounds() override;
   void SetBackgroundColor(SkColor color) override;
-  void UpdateDraggableRegions(
-      const std::vector<mojom::DraggableRegionPtr>& regions) override;
 
   // WebContentsObserver:
   void RenderViewReady() override;
 
-  SkRegion* draggable_region() const { return draggable_region_.get(); }
-
  private:
   void ResetAutoResizeProportions();
 
@@ -50,8 +45,6 @@ class NativeBrowserViewViews : public NativeBrowserView {
   bool auto_vertical_proportion_set_ = false;
   float auto_vertical_proportion_height_ = 0.;
   float auto_vertical_proportion_top_ = 0.;
-
-  std::unique_ptr<SkRegion> draggable_region_;
 };
 
 }  // namespace electron

+ 24 - 0
shell/browser/native_window.h

@@ -18,6 +18,7 @@
 #include "content/public/browser/web_contents_user_data.h"
 #include "extensions/browser/app_window/size_constraints.h"
 #include "shell/browser/native_window_observer.h"
+#include "shell/browser/ui/inspectable_web_contents_view.h"
 #include "shell/common/api/api.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/views/widget/widget_delegate.h"
@@ -50,6 +51,10 @@ namespace electron {
 class ElectronMenuModel;
 class NativeBrowserView;
 
+namespace api {
+class BrowserView;
+}
+
 #if BUILDFLAG(IS_MAC)
 typedef NSView* NativeWindowHandle;
 #else
@@ -373,9 +378,15 @@ class NativeWindow : public base::SupportsUserData,
 
   std::list<NativeBrowserView*> browser_views() const { return browser_views_; }
 
+  std::list<InspectableWebContentsView*> inspectable_views() const {
+    return inspectable_views_;
+  }
+
   int32_t window_id() const { return next_id_; }
 
  protected:
+  friend class api::BrowserView;
+
   NativeWindow(const gin_helper::Dictionary& options, NativeWindow* parent);
 
   // views::WidgetDelegate:
@@ -393,6 +404,16 @@ class NativeWindow : public base::SupportsUserData,
         [&browser_view](NativeBrowserView* n) { return (n == browser_view); });
   }
 
+  void add_inspectable_view(InspectableWebContentsView* inspectable_view) {
+    inspectable_views_.push_back(inspectable_view);
+  }
+  void remove_inspectable_view(InspectableWebContentsView* inspectable_view) {
+    inspectable_views_.remove_if(
+        [&inspectable_view](InspectableWebContentsView* n) {
+          return (n == inspectable_view);
+        });
+  }
+
   // The boolean parsing of the "titleBarOverlay" option
   bool titlebar_overlay_ = false;
 
@@ -456,6 +477,9 @@ class NativeWindow : public base::SupportsUserData,
   // The browser view layer.
   std::list<NativeBrowserView*> browser_views_;
 
+  // The inspectable webContents views.
+  std::list<InspectableWebContentsView*> inspectable_views_;
+
   // Observers of this window.
   base::ObserverList<NativeWindowObserver> observers_;
 

+ 0 - 1
shell/browser/native_window_mac.mm

@@ -34,7 +34,6 @@
 #include "shell/browser/ui/cocoa/window_buttons_proxy.h"
 #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 "shell/browser/window_list.h"
 #include "shell/common/gin_converters/gfx_converter.h"
 #include "shell/common/gin_helper/dictionary.h"

+ 6 - 6
shell/browser/native_window_views.cc

@@ -20,7 +20,7 @@
 #include "shell/browser/api/electron_api_web_contents.h"
 #include "shell/browser/native_browser_view_views.h"
 #include "shell/browser/ui/inspectable_web_contents.h"
-#include "shell/browser/ui/inspectable_web_contents_view.h"
+#include "shell/browser/ui/views/inspectable_web_contents_view_views.h"
 #include "shell/browser/ui/views/root_view.h"
 #include "shell/browser/web_contents_preferences.h"
 #include "shell/browser/web_view_manager.h"
@@ -1594,11 +1594,11 @@ bool NativeWindowViews::ShouldDescendIntoChildForEventHandling(
     const gfx::Point& location) {
   // App window should claim mouse events that fall within any BrowserViews'
   // draggable region.
-  for (auto* view : browser_views()) {
-    auto* native_view = static_cast<NativeBrowserViewViews*>(view);
-    auto* view_draggable_region = native_view->draggable_region();
-    if (view_draggable_region &&
-        view_draggable_region->contains(location.x(), location.y()))
+  for (auto* view : inspectable_views()) {
+    auto* inspectable_view =
+        static_cast<InspectableWebContentsViewViews*>(view);
+    if (inspectable_view->IsContainedInDraggableRegion(content_view(),
+                                                       location))
       return false;
   }
 

+ 16 - 0
shell/browser/ui/inspectable_web_contents_view.cc

@@ -0,0 +1,16 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright (c) 2013 Adam Roben <[email protected]>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE-CHROMIUM file.
+
+#include "shell/browser/ui/inspectable_web_contents_view.h"
+
+namespace electron {
+
+InspectableWebContentsView::InspectableWebContentsView(
+    InspectableWebContents* inspectable_web_contents)
+    : inspectable_web_contents_(inspectable_web_contents) {}
+
+InspectableWebContentsView::~InspectableWebContentsView() = default;
+
+}  // namespace electron

+ 24 - 2
shell/browser/ui/inspectable_web_contents_view.h

@@ -7,7 +7,9 @@
 #define ELECTRON_SHELL_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_H_
 
 #include <string>
+#include <vector>
 
+#include "shell/common/api/api.mojom.h"
 #include "ui/gfx/native_widget_types.h"
 
 class DevToolsContentsResizingStrategy;
@@ -20,12 +22,18 @@ class View;
 
 namespace electron {
 
+class InspectableWebContents;
 class InspectableWebContentsViewDelegate;
 
 class InspectableWebContentsView {
  public:
-  InspectableWebContentsView() {}
-  virtual ~InspectableWebContentsView() {}
+  explicit InspectableWebContentsView(
+      InspectableWebContents* inspectable_web_contents);
+  virtual ~InspectableWebContentsView();
+
+  InspectableWebContents* inspectable_web_contents() {
+    return inspectable_web_contents_;
+  }
 
   // The delegate manages its own life.
   void SetDelegate(InspectableWebContentsViewDelegate* delegate) {
@@ -33,6 +41,10 @@ class InspectableWebContentsView {
   }
   InspectableWebContentsViewDelegate* GetDelegate() const { return delegate_; }
 
+  const std::vector<mojom::DraggableRegionPtr>& GetDraggableRegions() const {
+    return draggable_regions_;
+  }
+
 #if defined(TOOLKIT_VIEWS) && !BUILDFLAG(IS_MAC)
   // Returns the container control, which has devtools view attached.
   virtual views::View* GetView() = 0;
@@ -54,6 +66,16 @@ class InspectableWebContentsView {
       const DevToolsContentsResizingStrategy& strategy) = 0;
   virtual void SetTitle(const std::u16string& title) = 0;
 
+  // Called when the window needs to update its draggable region.
+  virtual void UpdateDraggableRegions(
+      const std::vector<mojom::DraggableRegionPtr>& regions) = 0;
+
+ protected:
+  // Owns us.
+  InspectableWebContents* inspectable_web_contents_;
+
+  std::vector<mojom::DraggableRegionPtr> draggable_regions_;
+
  private:
   InspectableWebContentsViewDelegate* delegate_ = nullptr;  // weak references.
 };

+ 4 - 9
shell/browser/ui/inspectable_web_contents_view_mac.h

@@ -8,14 +8,14 @@
 
 #include "shell/browser/ui/inspectable_web_contents_view.h"
 
+#include <vector>
+
 #include "base/mac/scoped_nsobject.h"
 
 @class ElectronInspectableWebContentsView;
 
 namespace electron {
 
-class InspectableWebContents;
-
 class InspectableWebContentsViewMac : public InspectableWebContentsView {
  public:
   explicit InspectableWebContentsViewMac(
@@ -34,15 +34,10 @@ class InspectableWebContentsViewMac : public InspectableWebContentsView {
   void SetContentsResizingStrategy(
       const DevToolsContentsResizingStrategy& strategy) override;
   void SetTitle(const std::u16string& title) override;
-
-  InspectableWebContents* inspectable_web_contents() {
-    return inspectable_web_contents_;
-  }
+  void UpdateDraggableRegions(
+      const std::vector<mojom::DraggableRegionPtr>& regions) override;
 
  private:
-  // Owns us.
-  InspectableWebContents* inspectable_web_contents_;
-
   base::scoped_nsobject<ElectronInspectableWebContentsView> view_;
 };
 

+ 267 - 1
shell/browser/ui/inspectable_web_contents_view_mac.mm

@@ -6,11 +6,216 @@
 #include "shell/browser/ui/inspectable_web_contents_view_mac.h"
 
 #import <AppKit/AppKit.h>
+#import <objc/runtime.h>
+
+#include <vector>
 
 #include "base/strings/sys_string_conversions.h"
 #import "shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h"
+#include "shell/browser/ui/drag_util.h"
 #include "shell/browser/ui/inspectable_web_contents.h"
 #include "shell/browser/ui/inspectable_web_contents_view_delegate.h"
+#include "ui/gfx/geometry/rect.h"
+
+@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 {
 
@@ -21,7 +226,7 @@ InspectableWebContentsView* CreateInspectableContentsView(
 
 InspectableWebContentsViewMac::InspectableWebContentsViewMac(
     InspectableWebContents* inspectable_web_contents)
-    : inspectable_web_contents_(inspectable_web_contents),
+    : InspectableWebContentsView(inspectable_web_contents),
       view_([[ElectronInspectableWebContentsView alloc]
           initWithInspectableWebContentsViewMac:this]) {}
 
@@ -62,4 +267,65 @@ void InspectableWebContentsViewMac::SetTitle(const std::u16string& title) {
   [view_ setTitle:base::SysUTF16ToNSString(title)];
 }
 
+void InspectableWebContentsViewMac::UpdateDraggableRegions(
+    const std::vector<mojom::DraggableRegionPtr>& regions) {
+  auto* web_contents = inspectable_web_contents()->GetWebContents();
+  NSView* web_view = web_contents->GetNativeView().GetNativeNSView();
+  NSView* inspectable_view = GetNativeView().GetNativeNSView();
+  NSView* inspectable_superview = inspectable_view.superview;
+
+  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_ != &regions)
+    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);
+  }
+
+  // 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".
+  const int superview_height =
+      (inspectable_superview) ? inspectable_superview.frame.size.height : 0;
+  if (!inspectable_superview)
+    inspectable_superview = inspectable_view;
+  const int offset_x = inspectable_view.frame.origin.x;
+  const int offset_y = superview_height - inspectable_view.frame.origin.y -
+                       inspectable_view.frame.size.height;
+  for (const auto& rect : drag_exclude_rects) {
+    const auto x = rect.x() + offset_x;
+    const auto y = superview_height - (rect.bottom() + offset_y);
+    const auto exclude_rect = NSMakeRect(x, y, rect.width(), rect.height());
+
+    const auto drag_region_view_exclude_rect =
+        [inspectable_superview 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];
+  }
+}
+
 }  // namespace electron

+ 6 - 5
shell/browser/ui/views/frameless_view.cc

@@ -6,6 +6,7 @@
 
 #include "shell/browser/native_browser_view_views.h"
 #include "shell/browser/native_window_views.h"
+#include "shell/browser/ui/views/inspectable_web_contents_view_views.h"
 #include "ui/aura/window.h"
 #include "ui/base/hit_test.h"
 #include "ui/views/widget/widget.h"
@@ -80,11 +81,11 @@ int FramelessView::NonClientHitTest(const gfx::Point& cursor) {
     return HTCLIENT;
 
   // Check attached BrowserViews for potential draggable areas.
-  for (auto* view : window_->browser_views()) {
-    auto* native_view = static_cast<NativeBrowserViewViews*>(view);
-    auto* view_draggable_region = native_view->draggable_region();
-    if (view_draggable_region &&
-        view_draggable_region->contains(cursor.x(), cursor.y()))
+  for (auto* view : window_->inspectable_views()) {
+    auto* inspectable_view =
+        static_cast<InspectableWebContentsViewViews*>(view);
+    if (inspectable_view->IsContainedInDraggableRegion(window_->content_view(),
+                                                       cursor))
       return HTCAPTION;
   }
 

+ 24 - 1
shell/browser/ui/views/inspectable_web_contents_view_views.cc

@@ -7,8 +7,10 @@
 #include <memory>
 
 #include <utility>
+#include <vector>
 
 #include "base/strings/utf_string_conversions.h"
+#include "shell/browser/ui/drag_util.h"
 #include "shell/browser/ui/inspectable_web_contents.h"
 #include "shell/browser/ui/inspectable_web_contents_delegate.h"
 #include "shell/browser/ui/inspectable_web_contents_view_delegate.h"
@@ -79,7 +81,7 @@ InspectableWebContentsView* CreateInspectableContentsView(
 
 InspectableWebContentsViewViews::InspectableWebContentsViewViews(
     InspectableWebContents* inspectable_web_contents)
-    : inspectable_web_contents_(inspectable_web_contents),
+    : InspectableWebContentsView(inspectable_web_contents),
       devtools_web_view_(new views::WebView(nullptr)),
       title_(u"Developer Tools") {
   if (!inspectable_web_contents_->IsGuest() &&
@@ -103,6 +105,19 @@ InspectableWebContentsViewViews::~InspectableWebContentsViewViews() {
         devtools_window_->GetWindowBoundsInScreen());
 }
 
+bool InspectableWebContentsViewViews::IsContainedInDraggableRegion(
+    views::View* root_view,
+    const gfx::Point& location) {
+  if (!draggable_region_.get())
+    return false;
+  // Draggable regions are defined relative to the web contents.
+  gfx::Point point_in_contents_web_view_coords(location);
+  views::View::ConvertPointToTarget(root_view, this,
+                                    &point_in_contents_web_view_coords);
+  return draggable_region_->contains(point_in_contents_web_view_coords.x(),
+                                     point_in_contents_web_view_coords.y());
+}
+
 views::View* InspectableWebContentsViewViews::GetView() {
   return this;
 }
@@ -212,6 +227,14 @@ void InspectableWebContentsViewViews::SetTitle(const std::u16string& title) {
   }
 }
 
+void InspectableWebContentsViewViews::UpdateDraggableRegions(
+    const std::vector<mojom::DraggableRegionPtr>& regions) {
+  if (&draggable_regions_ == &regions)
+    return;
+  draggable_regions_ = mojo::Clone(regions);
+  draggable_region_ = DraggableRegionsToSkRegion(draggable_regions_);
+}
+
 void InspectableWebContentsViewViews::Layout() {
   if (!devtools_web_view_->GetVisible()) {
     contents_web_view_->SetBoundsRect(GetContentsBounds());

+ 9 - 9
shell/browser/ui/views/inspectable_web_contents_view_views.h

@@ -6,10 +6,12 @@
 #define ELECTRON_SHELL_BROWSER_UI_VIEWS_INSPECTABLE_WEB_CONTENTS_VIEW_VIEWS_H_
 
 #include <memory>
+#include <vector>
 
 #include "base/compiler_specific.h"
 #include "chrome/browser/devtools/devtools_contents_resizing_strategy.h"
 #include "shell/browser/ui/inspectable_web_contents_view.h"
+#include "third_party/skia/include/core/SkRegion.h"
 #include "ui/views/view.h"
 
 namespace views {
@@ -20,8 +22,6 @@ class WidgetDelegate;
 
 namespace electron {
 
-class InspectableWebContents;
-
 class InspectableWebContentsViewViews : public InspectableWebContentsView,
                                         public views::View {
  public:
@@ -29,6 +29,9 @@ class InspectableWebContentsViewViews : public InspectableWebContentsView,
       InspectableWebContents* inspectable_web_contents);
   ~InspectableWebContentsViewViews() override;
 
+  bool IsContainedInDraggableRegion(views::View* root_view,
+                                    const gfx::Point& location);
+
   // InspectableWebContentsView:
   views::View* GetView() override;
   views::View* GetWebView() override;
@@ -40,20 +43,15 @@ class InspectableWebContentsViewViews : public InspectableWebContentsView,
   void SetContentsResizingStrategy(
       const DevToolsContentsResizingStrategy& strategy) override;
   void SetTitle(const std::u16string& title) override;
+  void UpdateDraggableRegions(
+      const std::vector<mojom::DraggableRegionPtr>& regions) override;
 
   // views::View:
   void Layout() override;
 
-  InspectableWebContents* inspectable_web_contents() {
-    return inspectable_web_contents_;
-  }
-
   const std::u16string& GetTitle() const { return title_; }
 
  private:
-  // Owns us.
-  InspectableWebContents* inspectable_web_contents_;
-
   std::unique_ptr<views::Widget> devtools_window_;
   views::WebView* devtools_window_web_view_ = nullptr;
   views::View* contents_web_view_ = nullptr;
@@ -63,6 +61,8 @@ class InspectableWebContentsViewViews : public InspectableWebContentsView,
   bool devtools_visible_ = false;
   views::WidgetDelegate* devtools_window_delegate_ = nullptr;
   std::u16string title_;
+
+  std::unique_ptr<SkRegion> draggable_region_;
 };
 
 }  // namespace electron