diff --git a/filenames.gni b/filenames.gni index 4d72254086cb..a7dda9edcac5 100644 --- a/filenames.gni +++ b/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", diff --git a/shell/browser/api/electron_api_browser_view.cc b/shell/browser/api/electron_api_browser_view.cc index 732d5ee45b18..80971de6c479 100644 --- a/shell/browser/api/electron_api_browser_view.cc +++ b/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& regions) { - view_->UpdateDraggableRegions(regions); + InspectableWebContentsView* iwc_view = view_->GetInspectableWebContentsView(); + if (!iwc_view) + return; + iwc_view->UpdateDraggableRegions(regions); } // static diff --git a/shell/browser/api/electron_api_browser_window.cc b/shell/browser/api/electron_api_browser_window.cc index d1b950818fbc..fdf191eaaead 100644 --- a/shell/browser/api/electron_api_browser_window.cc +++ b/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. diff --git a/shell/browser/native_browser_view.h b/shell/browser/native_browser_view.h index 05a47ffa3d17..620e414ec25f 100644 --- a/shell/browser/native_browser_view.h +++ b/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& 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& drag_exclude_rects) {} - - // Called when the window needs to update its draggable region. - virtual void UpdateDraggableRegions( - const std::vector& regions) {} - protected: explicit NativeBrowserView(InspectableWebContents* inspectable_web_contents); // content::WebContentsObserver: void WebContentsDestroyed() override; InspectableWebContents* inspectable_web_contents_; - std::vector draggable_regions_; }; } // namespace electron diff --git a/shell/browser/native_browser_view_mac.h b/shell/browser/native_browser_view_mac.h index 313d51616f34..c30253fbcfc3 100644 --- a/shell/browser/native_browser_view_mac.h +++ b/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& regions) override; - - void UpdateDraggableRegions( - const std::vector& drag_exclude_rects) override; }; } // namespace electron diff --git a/shell/browser/native_browser_view_mac.mm b/shell/browser/native_browser_view_mac.mm index 685b7194b528..4ed2ebc3f660 100644 --- a/shell/browser/native_browser_view_mac.mm +++ b/shell/browser/native_browser_view_mac.mm @@ -4,10 +4,6 @@ #include "shell/browser/native_browser_view_mac.h" -#import -#include - -#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& 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 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 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 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& 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 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) { diff --git a/shell/browser/native_browser_view_views.cc b/shell/browser/native_browser_view_views.cc index 651a9ba449f0..222674b0f725 100644 --- a/shell/browser/native_browser_view_views.cc +++ b/shell/browser/native_browser_view_views.cc @@ -6,7 +6,6 @@ #include -#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& regions) { - if (&draggable_regions_ != ®ions) - 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() { diff --git a/shell/browser/native_browser_view_views.h b/shell/browser/native_browser_view_views.h index 77bb0f73260f..ccda379bdefa 100644 --- a/shell/browser/native_browser_view_views.h +++ b/shell/browser/native_browser_view_views.h @@ -9,7 +9,6 @@ #include #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& 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 draggable_region_; }; } // namespace electron diff --git a/shell/browser/native_window.h b/shell/browser/native_window.h index 14d3d30ed8ec..fcf1d2d6bd28 100644 --- a/shell/browser/native_window.h +++ b/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 browser_views() const { return browser_views_; } + std::list 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 browser_views_; + // The inspectable webContents views. + std::list inspectable_views_; + // Observers of this window. base::ObserverList observers_; diff --git a/shell/browser/native_window_mac.mm b/shell/browser/native_window_mac.mm index 8de1f61c635f..e2252b50eb88 100644 --- a/shell/browser/native_window_mac.mm +++ b/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" diff --git a/shell/browser/native_window_views.cc b/shell/browser/native_window_views.cc index 4e80fad3dfbd..de0d63c58781 100644 --- a/shell/browser/native_window_views.cc +++ b/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(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(view); + if (inspectable_view->IsContainedInDraggableRegion(content_view(), + location)) return false; } diff --git a/shell/browser/ui/inspectable_web_contents_view.cc b/shell/browser/ui/inspectable_web_contents_view.cc new file mode 100644 index 000000000000..946b5071bcc8 --- /dev/null +++ b/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 . 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 diff --git a/shell/browser/ui/inspectable_web_contents_view.h b/shell/browser/ui/inspectable_web_contents_view.h index 11e9d8447470..c46e5234d01b 100644 --- a/shell/browser/ui/inspectable_web_contents_view.h +++ b/shell/browser/ui/inspectable_web_contents_view.h @@ -7,7 +7,9 @@ #define ELECTRON_SHELL_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_H_ #include +#include +#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& 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& regions) = 0; + + protected: + // Owns us. + InspectableWebContents* inspectable_web_contents_; + + std::vector draggable_regions_; + private: InspectableWebContentsViewDelegate* delegate_ = nullptr; // weak references. }; diff --git a/shell/browser/ui/inspectable_web_contents_view_mac.h b/shell/browser/ui/inspectable_web_contents_view_mac.h index 32b8563137a3..f65fe7d75c35 100644 --- a/shell/browser/ui/inspectable_web_contents_view_mac.h +++ b/shell/browser/ui/inspectable_web_contents_view_mac.h @@ -8,14 +8,14 @@ #include "shell/browser/ui/inspectable_web_contents_view.h" +#include + #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& regions) override; private: - // Owns us. - InspectableWebContents* inspectable_web_contents_; - base::scoped_nsobject view_; }; diff --git a/shell/browser/ui/inspectable_web_contents_view_mac.mm b/shell/browser/ui/inspectable_web_contents_view_mac.mm index f9e9011669d0..428f09e232a7 100644 --- a/shell/browser/ui/inspectable_web_contents_view_mac.mm +++ b/shell/browser/ui/inspectable_web_contents_view_mac.mm @@ -6,11 +6,216 @@ #include "shell/browser/ui/inspectable_web_contents_view_mac.h" #import +#import + +#include #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& 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_ != ®ions) + draggable_regions_ = mojo::Clone(regions); + + std::vector 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 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 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 exclude_drag_region_view( + [[ExcludeDragRegionView alloc] + initWithFrame:drag_region_view_exclude_rect]); + [drag_region_view addSubview:exclude_drag_region_view]; + } +} + } // namespace electron diff --git a/shell/browser/ui/views/frameless_view.cc b/shell/browser/ui/views/frameless_view.cc index 8f13a1f0eadd..8398b9e5a53a 100644 --- a/shell/browser/ui/views/frameless_view.cc +++ b/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(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(view); + if (inspectable_view->IsContainedInDraggableRegion(window_->content_view(), + cursor)) return HTCAPTION; } diff --git a/shell/browser/ui/views/inspectable_web_contents_view_views.cc b/shell/browser/ui/views/inspectable_web_contents_view_views.cc index 7ab8ac51a211..4bf598bdce16 100644 --- a/shell/browser/ui/views/inspectable_web_contents_view_views.cc +++ b/shell/browser/ui/views/inspectable_web_contents_view_views.cc @@ -7,8 +7,10 @@ #include #include +#include #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& regions) { + if (&draggable_regions_ == ®ions) + return; + draggable_regions_ = mojo::Clone(regions); + draggable_region_ = DraggableRegionsToSkRegion(draggable_regions_); +} + void InspectableWebContentsViewViews::Layout() { if (!devtools_web_view_->GetVisible()) { contents_web_view_->SetBoundsRect(GetContentsBounds()); diff --git a/shell/browser/ui/views/inspectable_web_contents_view_views.h b/shell/browser/ui/views/inspectable_web_contents_view_views.h index beac94c013c2..d77a97766c87 100644 --- a/shell/browser/ui/views/inspectable_web_contents_view_views.h +++ b/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 +#include #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& 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 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 draggable_region_; }; } // namespace electron