diff --git a/filenames.gni b/filenames.gni index 97b348cd7b..b8daa9a919 100644 --- a/filenames.gni +++ b/filenames.gni @@ -159,8 +159,12 @@ filenames = { "shell/browser/osr/osr_web_contents_view_mac.mm", "shell/browser/relauncher_mac.cc", "shell/browser/ui/certificate_trust_mac.mm", + "shell/browser/ui/cocoa/delayed_native_view_host.h", + "shell/browser/ui/cocoa/delayed_native_view_host.mm", "shell/browser/ui/cocoa/electron_bundle_mover.h", "shell/browser/ui/cocoa/electron_bundle_mover.mm", + "shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h", + "shell/browser/ui/cocoa/electron_inspectable_web_contents_view.mm", "shell/browser/ui/cocoa/electron_menu_controller.h", "shell/browser/ui/cocoa/electron_menu_controller.mm", "shell/browser/ui/cocoa/electron_native_widget_mac.h", @@ -187,6 +191,8 @@ filenames = { "shell/browser/ui/cocoa/window_buttons_proxy.mm", "shell/browser/ui/drag_util_mac.mm", "shell/browser/ui/file_dialog_mac.mm", + "shell/browser/ui/inspectable_web_contents_view_mac.h", + "shell/browser/ui/inspectable_web_contents_view_mac.mm", "shell/browser/ui/message_box_mac.mm", "shell/browser/ui/tray_icon_cocoa.h", "shell/browser/ui/tray_icon_cocoa.mm", @@ -202,7 +208,6 @@ filenames = { "shell/common/node_bindings_mac.cc", "shell/common/node_bindings_mac.h", "shell/common/platform_util_mac.mm", - "shell/browser/ui/views/inspectable_web_contents_view_mac.mm", ] lib_sources_views = [ @@ -217,6 +222,8 @@ filenames = { "shell/browser/ui/views/electron_views_delegate.h", "shell/browser/ui/views/frameless_view.cc", "shell/browser/ui/views/frameless_view.h", + "shell/browser/ui/views/inspectable_web_contents_view_views.cc", + "shell/browser/ui/views/inspectable_web_contents_view_views.h", "shell/browser/ui/views/menu_bar.cc", "shell/browser/ui/views/menu_bar.h", "shell/browser/ui/views/menu_delegate.cc", diff --git a/shell/browser/api/electron_api_base_window.cc b/shell/browser/api/electron_api_base_window.cc index 337c928889..c47fc24af1 100644 --- a/shell/browser/api/electron_api_base_window.cc +++ b/shell/browser/api/electron_api_base_window.cc @@ -121,6 +121,11 @@ BaseWindow::BaseWindow(gin_helper::Arguments* args, BaseWindow::~BaseWindow() { CloseImmediately(); + // Destroy the native window in next tick because the native code might be + // iterating all windows. + base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon( + FROM_HERE, window_.release()); + // Remove global reference so the JS object can be garbage collected. self_ref_.Reset(); } diff --git a/shell/browser/api/electron_api_web_contents_view.cc b/shell/browser/api/electron_api_web_contents_view.cc index 092b60a7e9..a46f97d85d 100644 --- a/shell/browser/api/electron_api_web_contents_view.cc +++ b/shell/browser/api/electron_api_web_contents_view.cc @@ -26,14 +26,29 @@ #include "ui/views/view_class_properties.h" #include "ui/views/widget/widget.h" +#if BUILDFLAG(IS_MAC) +#include "shell/browser/ui/cocoa/delayed_native_view_host.h" +#endif + namespace electron::api { WebContentsView::WebContentsView(v8::Isolate* isolate, gin::Handle web_contents) - : View(web_contents->inspectable_web_contents()->GetView()), +#if BUILDFLAG(IS_MAC) + : View(new DelayedNativeViewHost(web_contents->inspectable_web_contents() + ->GetView() + ->GetNativeView())), +#else + : View(web_contents->inspectable_web_contents()->GetView()->GetView()), +#endif web_contents_(isolate, web_contents.ToV8()), api_web_contents_(web_contents.get()) { +#if !BUILDFLAG(IS_MAC) + // On macOS the View is a newly-created |DelayedNativeViewHost| and it is our + // responsibility to delete it. On other platforms the View is created and + // managed by InspectableWebContents. set_delete_view(false); +#endif view()->SetProperty( views::kFlexBehaviorKey, views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToMinimum, @@ -74,15 +89,8 @@ void WebContentsView::SetBorderRadius(int radius) { void WebContentsView::ApplyBorderRadius() { if (border_radius().has_value() && api_web_contents_ && view()->GetWidget()) { - auto* web_view = api_web_contents_->inspectable_web_contents() - ->GetView() - ->contents_web_view(); - - // WebView won't exist for offscreen rendering. - if (web_view) { - web_view->holder()->SetCornerRadii( - gfx::RoundedCornersF(border_radius().value())); - } + auto* view = api_web_contents_->inspectable_web_contents()->GetView(); + view->SetCornerRadii(gfx::RoundedCornersF(border_radius().value())); } } diff --git a/shell/browser/native_window_mac.mm b/shell/browser/native_window_mac.mm index bdc02d2e1d..843f66c1ec 100644 --- a/shell/browser/native_window_mac.mm +++ b/shell/browser/native_window_mac.mm @@ -236,7 +236,7 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options, views::Widget::InitParams::TYPE_WINDOW); params.bounds = bounds; params.delegate = this; - params.headless_mode = true; + params.type = views::Widget::InitParams::TYPE_WINDOW; params.native_widget = new ElectronNativeWidgetMac(this, windowType, styleMask, widget()); widget()->Init(std::move(params)); diff --git a/shell/browser/native_window_views.cc b/shell/browser/native_window_views.cc index a5364f80e0..c5439cd0a9 100644 --- a/shell/browser/native_window_views.cc +++ b/shell/browser/native_window_views.cc @@ -27,6 +27,7 @@ #include "content/public/common/color_parser.h" #include "shell/browser/api/electron_api_web_contents.h" #include "shell/browser/ui/inspectable_web_contents.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" diff --git a/shell/browser/ui/cocoa/delayed_native_view_host.h b/shell/browser/ui/cocoa/delayed_native_view_host.h new file mode 100644 index 0000000000..eb02826c2b --- /dev/null +++ b/shell/browser/ui/cocoa/delayed_native_view_host.h @@ -0,0 +1,34 @@ +// Copyright (c) 2018 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ELECTRON_SHELL_BROWSER_UI_COCOA_DELAYED_NATIVE_VIEW_HOST_H_ +#define ELECTRON_SHELL_BROWSER_UI_COCOA_DELAYED_NATIVE_VIEW_HOST_H_ + +#include "ui/views/controls/native/native_view_host.h" + +namespace electron { + +// Automatically attach the native view after the NativeViewHost is attached to +// a widget. (Attaching it directly would cause crash.) +class DelayedNativeViewHost : public views::NativeViewHost { + public: + explicit DelayedNativeViewHost(gfx::NativeView native_view); + ~DelayedNativeViewHost() override; + + // disable copy + DelayedNativeViewHost(const DelayedNativeViewHost&) = delete; + DelayedNativeViewHost& operator=(const DelayedNativeViewHost&) = delete; + + // views::View: + void ViewHierarchyChanged( + const views::ViewHierarchyChangedDetails& details) override; + bool OnMousePressed(const ui::MouseEvent& event) override; + + private: + gfx::NativeView native_view_; +}; + +} // namespace electron + +#endif // ELECTRON_SHELL_BROWSER_UI_COCOA_DELAYED_NATIVE_VIEW_HOST_H_ diff --git a/shell/browser/ui/cocoa/delayed_native_view_host.mm b/shell/browser/ui/cocoa/delayed_native_view_host.mm new file mode 100644 index 0000000000..e8f4f0305d --- /dev/null +++ b/shell/browser/ui/cocoa/delayed_native_view_host.mm @@ -0,0 +1,44 @@ +// Copyright (c) 2018 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "shell/browser/ui/cocoa/delayed_native_view_host.h" +#include "base/apple/owned_objc.h" +#include "shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h" + +namespace electron { + +DelayedNativeViewHost::DelayedNativeViewHost(gfx::NativeView native_view) + : native_view_(native_view) {} + +DelayedNativeViewHost::~DelayedNativeViewHost() = default; + +void DelayedNativeViewHost::ViewHierarchyChanged( + const views::ViewHierarchyChangedDetails& details) { + // NativeViewHost doesn't expect to have children, so filter the + // ViewHierarchyChanged events before passing them on. + if (details.child == this) { + NativeViewHost::ViewHierarchyChanged(details); + if (details.is_add && GetWidget() && !native_view()) + Attach(native_view_); + } +} + +bool DelayedNativeViewHost::OnMousePressed(const ui::MouseEvent& ui_event) { + // NativeViewHost::OnMousePressed normally isn't called, but + // NativeWidgetMacNSWindow specifically carves out an event here for + // right-mouse-button clicks. We want to forward them to the web content, so + // handle them here. + // See: + // https://source.chromium.org/chromium/chromium/src/+/main:components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm;l=415-421;drc=a5af91924bafb85426e091c6035801990a6dc697 + + ElectronInspectableWebContentsView* inspectable_web_contents_view = + (ElectronInspectableWebContentsView*)native_view_.GetNativeNSView(); + [inspectable_web_contents_view + redispatchContextMenuEvent:base::apple::OwnedNSEvent( + ui_event.native_event())]; + + return true; +} + +} // namespace electron diff --git a/shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h b/shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h new file mode 100644 index 0000000000..677e76db2a --- /dev/null +++ b/shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h @@ -0,0 +1,56 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE-CHROMIUM file. + +#ifndef ELECTRON_SHELL_BROWSER_UI_COCOA_ELECTRON_INSPECTABLE_WEB_CONTENTS_VIEW_H_ +#define ELECTRON_SHELL_BROWSER_UI_COCOA_ELECTRON_INSPECTABLE_WEB_CONTENTS_VIEW_H_ + +#import + +#include "base/apple/owned_objc.h" +#include "base/memory/raw_ptr.h" +#include "chrome/browser/devtools/devtools_contents_resizing_strategy.h" +#include "ui/base/cocoa/base_view.h" + +namespace electron { +class InspectableWebContentsViewMac; +} + +using electron::InspectableWebContentsViewMac; + +@interface NSView (WebContentsView) +- (void)setMouseDownCanMoveWindow:(BOOL)can_move; +@end + +@interface ElectronInspectableWebContentsView : BaseView { + @private + raw_ptr inspectableWebContentsView_; + + NSView* __strong fake_view_; + NSWindow* __strong devtools_window_; + BOOL devtools_visible_; + BOOL devtools_docked_; + BOOL devtools_is_first_responder_; + BOOL attached_to_window_; + + DevToolsContentsResizingStrategy strategy_; +} + +- (instancetype)initWithInspectableWebContentsViewMac: + (InspectableWebContentsViewMac*)view; +- (void)notifyDevToolsFocused; +- (void)setCornerRadii:(CGFloat)cornerRadius; +- (void)setDevToolsVisible:(BOOL)visible activate:(BOOL)activate; +- (BOOL)isDevToolsVisible; +- (BOOL)isDevToolsFocused; +- (void)setIsDocked:(BOOL)docked activate:(BOOL)activate; +- (void)setContentsResizingStrategy: + (const DevToolsContentsResizingStrategy&)strategy; +- (void)setTitle:(NSString*)title; +- (NSString*)getTitle; + +- (void)redispatchContextMenuEvent:(base::apple::OwnedNSEvent)theEvent; + +@end + +#endif // ELECTRON_SHELL_BROWSER_UI_COCOA_ELECTRON_INSPECTABLE_WEB_CONTENTS_VIEW_H_ diff --git a/shell/browser/ui/cocoa/electron_inspectable_web_contents_view.mm b/shell/browser/ui/cocoa/electron_inspectable_web_contents_view.mm new file mode 100644 index 0000000000..9ac5e737b5 --- /dev/null +++ b/shell/browser/ui/cocoa/electron_inspectable_web_contents_view.mm @@ -0,0 +1,352 @@ +// Copyright (c) 2014 The Chromium Authors. 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/cocoa/electron_inspectable_web_contents_view.h" + +#include "content/public/browser/render_widget_host_view.h" +#include "shell/browser/api/electron_api_web_contents.h" +#include "shell/browser/ui/cocoa/event_dispatching_window.h" +#include "shell/browser/ui/inspectable_web_contents.h" +#include "shell/browser/ui/inspectable_web_contents_view_delegate.h" +#include "shell/browser/ui/inspectable_web_contents_view_mac.h" +#include "ui/base/cocoa/base_view.h" +#include "ui/gfx/mac/scoped_cocoa_disable_screen_updates.h" + +@implementation ElectronInspectableWebContentsView + +- (instancetype)initWithInspectableWebContentsViewMac: + (InspectableWebContentsViewMac*)view { + self = [super init]; + if (!self) + return nil; + + inspectableWebContentsView_ = view; + devtools_visible_ = NO; + devtools_docked_ = NO; + devtools_is_first_responder_ = NO; + attached_to_window_ = NO; + + if (inspectableWebContentsView_->inspectable_web_contents()->is_guest()) { + fake_view_ = [[NSView alloc] init]; + [fake_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [self addSubview:fake_view_]; + } else { + auto* contents = inspectableWebContentsView_->inspectable_web_contents() + ->GetWebContents(); + auto* contentsView = contents->GetNativeView().GetNativeNSView(); + [contentsView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [self addSubview:contentsView]; + } + + // See https://code.google.com/p/chromium/issues/detail?id=348490. + [self setWantsLayer:YES]; + + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize { + [self adjustSubviews]; +} + +- (void)viewDidMoveToWindow { + if (attached_to_window_ && !self.window) { + attached_to_window_ = NO; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + } else if (!attached_to_window_ && self.window) { + attached_to_window_ = YES; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(viewDidBecomeFirstResponder:) + name:kViewDidBecomeFirstResponder + object:nil]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(parentWindowBecameMain:) + name:NSWindowDidBecomeMainNotification + object:nil]; + } +} + +- (IBAction)showDevTools:(id)sender { + inspectableWebContentsView_->inspectable_web_contents()->ShowDevTools(true); +} + +- (void)notifyDevToolsFocused { + if (inspectableWebContentsView_->GetDelegate()) + inspectableWebContentsView_->GetDelegate()->DevToolsFocused(); +} + +- (void)setCornerRadii:(CGFloat)cornerRadius { + auto* inspectable_web_contents = + inspectableWebContentsView_->inspectable_web_contents(); + DCHECK(inspectable_web_contents); + auto* webContents = inspectable_web_contents->GetWebContents(); + if (!webContents) + return; + auto* webContentsView = webContents->GetNativeView().GetNativeNSView(); + webContentsView.wantsLayer = YES; + webContentsView.layer.cornerRadius = cornerRadius; +} + +- (void)notifyDevToolsResized { + // When devtools is opened, resizing devtools would not trigger + // UpdateDraggableRegions for WebContents, so we have to notify the window + // to do an update of draggable regions. + if (inspectableWebContentsView_->GetDelegate()) + inspectableWebContentsView_->GetDelegate()->DevToolsResized(); +} + +- (void)setDevToolsVisible:(BOOL)visible activate:(BOOL)activate { + if (visible == devtools_visible_) + return; + + auto* inspectable_web_contents = + inspectableWebContentsView_->inspectable_web_contents(); + auto* devToolsWebContents = + inspectable_web_contents->GetDevToolsWebContents(); + auto* devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView(); + + devtools_visible_ = visible; + if (devtools_docked_) { + if (visible) { + // Place the devToolsView under contentsView, notice that we didn't set + // sizes for them until the setContentsResizingStrategy message. + [self addSubview:devToolsView positioned:NSWindowBelow relativeTo:nil]; + [self adjustSubviews]; + + // Focus on web view. + devToolsWebContents->RestoreFocus(); + } else { + gfx::ScopedCocoaDisableScreenUpdates disabler; + [devToolsView removeFromSuperview]; + [self adjustSubviews]; + [self notifyDevToolsResized]; + } + } else { + if (visible) { + if (activate) { + [devtools_window_ makeKeyAndOrderFront:nil]; + } else { + [devtools_window_ orderBack:nil]; + } + } else { + [devtools_window_ setDelegate:nil]; + [devtools_window_ close]; + devtools_window_ = nil; + } + } +} + +- (BOOL)isDevToolsVisible { + return devtools_visible_; +} + +- (BOOL)isDevToolsFocused { + if (devtools_docked_) { + return [[self window] isKeyWindow] && devtools_is_first_responder_; + } else { + return [devtools_window_ isKeyWindow]; + } +} + +- (void)setIsDocked:(BOOL)docked activate:(BOOL)activate { + // Revert to no-devtools state. + [self setDevToolsVisible:NO activate:NO]; + + // Switch to new state. + devtools_docked_ = docked; + auto* inspectable_web_contents = + inspectableWebContentsView_->inspectable_web_contents(); + auto* devToolsWebContents = + inspectable_web_contents->GetDevToolsWebContents(); + auto devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView(); + if (!docked) { + auto styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | + NSWindowStyleMaskMiniaturizable | + NSWindowStyleMaskResizable | + NSWindowStyleMaskTexturedBackground | + NSWindowStyleMaskUnifiedTitleAndToolbar; + devtools_window_ = [[EventDispatchingWindow alloc] + initWithContentRect:NSMakeRect(0, 0, 800, 600) + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:YES]; + [devtools_window_ setDelegate:self]; + [devtools_window_ setFrameAutosaveName:@"electron.devtools"]; + [devtools_window_ setTitle:@"Developer Tools"]; + [devtools_window_ setReleasedWhenClosed:NO]; + [devtools_window_ setAutorecalculatesContentBorderThickness:NO + forEdge:NSMaxYEdge]; + [devtools_window_ setContentBorderThickness:24 forEdge:NSMaxYEdge]; + + NSView* contentView = [devtools_window_ contentView]; + devToolsView.frame = contentView.bounds; + devToolsView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + + [contentView addSubview:devToolsView]; + [devToolsView setMouseDownCanMoveWindow:NO]; + } else { + [devToolsView setMouseDownCanMoveWindow:YES]; + } + [self setDevToolsVisible:YES activate:activate]; +} + +- (void)setContentsResizingStrategy: + (const DevToolsContentsResizingStrategy&)strategy { + strategy_.CopyFrom(strategy); + [self adjustSubviews]; +} + +- (void)adjustSubviews { + if (![[self subviews] count]) + return; + + if (![self isDevToolsVisible] || devtools_window_) { + DCHECK_EQ(1u, [[self subviews] count]); + NSView* contents = [[self subviews] objectAtIndex:0]; + [contents setFrame:[self bounds]]; + return; + } + + NSView* devToolsView = [[self subviews] objectAtIndex:0]; + NSView* contentsView = [[self subviews] objectAtIndex:1]; + + DCHECK_EQ(2u, [[self subviews] count]); + + gfx::Rect new_devtools_bounds; + gfx::Rect new_contents_bounds; + ApplyDevToolsContentsResizingStrategy( + strategy_, gfx::Size(NSSizeToCGSize([self bounds].size)), + &new_devtools_bounds, &new_contents_bounds); + [devToolsView setFrame:[self flipRectToNSRect:new_devtools_bounds]]; + [contentsView setFrame:[self flipRectToNSRect:new_contents_bounds]]; + + // Move mask to the devtools area to exclude it from dragging. + NSRect cf = contentsView.frame; + NSRect sb = [self bounds]; + NSRect devtools_frame; + if (cf.size.height < sb.size.height) { // bottom docked + devtools_frame.origin.x = 0; + devtools_frame.origin.y = 0; + devtools_frame.size.width = sb.size.width; + devtools_frame.size.height = sb.size.height - cf.size.height; + } else { // left or right docked + if (cf.origin.x > 0) // left docked + devtools_frame.origin.x = 0; + else // right docked. + devtools_frame.origin.x = cf.size.width; + devtools_frame.origin.y = 0; + devtools_frame.size.width = sb.size.width - cf.size.width; + devtools_frame.size.height = sb.size.height; + } + + [self notifyDevToolsResized]; +} + +- (void)setTitle:(NSString*)title { + [devtools_window_ setTitle:title]; +} + +- (NSString*)getTitle { + return [devtools_window_ title]; +} + +- (void)viewDidBecomeFirstResponder:(NSNotification*)notification { + auto* inspectable_web_contents = + inspectableWebContentsView_->inspectable_web_contents(); + DCHECK(inspectable_web_contents); + auto* webContents = inspectable_web_contents->GetWebContents(); + if (!webContents) + return; + auto* webContentsView = webContents->GetNativeView().GetNativeNSView(); + + NSView* view = [notification object]; + if ([[webContentsView subviews] containsObject:view]) { + devtools_is_first_responder_ = NO; + return; + } + + auto* devToolsWebContents = + inspectable_web_contents->GetDevToolsWebContents(); + if (!devToolsWebContents) + return; + auto devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView(); + + if ([[devToolsView subviews] containsObject:view]) { + devtools_is_first_responder_ = YES; + [self notifyDevToolsFocused]; + } +} + +- (void)parentWindowBecameMain:(NSNotification*)notification { + NSWindow* parentWindow = [notification object]; + if ([self window] == parentWindow && devtools_docked_ && + devtools_is_first_responder_) + [self notifyDevToolsFocused]; +} + +- (void)redispatchContextMenuEvent:(base::apple::OwnedNSEvent)event { + DCHECK(event.Get().type == NSEventTypeRightMouseDown || + (event.Get().type == NSEventTypeLeftMouseDown && + (event.Get().modifierFlags & NSEventModifierFlagControl))); + content::WebContents* contents = + inspectableWebContentsView_->inspectable_web_contents()->GetWebContents(); + electron::api::WebContents* api_contents = + electron::api::WebContents::From(contents); + if (api_contents) { + // Temporarily pretend that the WebContents is fully non-draggable while we + // re-send the mouse event. This allows the re-dispatched event to "land" + // on the WebContents, instead of "falling through" back to the window. + auto* rwhv = contents->GetRenderWidgetHostView(); + if (rwhv) { + api_contents->SetForceNonDraggable(true); + BaseView* contentsView = + (BaseView*)rwhv->GetNativeView().GetNativeNSView(); + [contentsView mouseEvent:event.Get()]; + api_contents->SetForceNonDraggable(false); + } + } +} + +#pragma mark - NSWindowDelegate + +- (void)windowWillClose:(NSNotification*)notification { + inspectableWebContentsView_->inspectable_web_contents()->CloseDevTools(); +} + +- (void)windowDidBecomeMain:(NSNotification*)notification { + content::WebContents* web_contents = + inspectableWebContentsView_->inspectable_web_contents() + ->GetDevToolsWebContents(); + if (!web_contents) + return; + + web_contents->RestoreFocus(); + + content::RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView(); + if (rwhv) + rwhv->SetActive(true); + + [self notifyDevToolsFocused]; +} + +- (void)windowDidResignMain:(NSNotification*)notification { + content::WebContents* web_contents = + inspectableWebContentsView_->inspectable_web_contents() + ->GetDevToolsWebContents(); + if (!web_contents) + return; + + web_contents->StoreFocus(); + + content::RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView(); + if (rwhv) + rwhv->SetActive(false); +} + +@end diff --git a/shell/browser/ui/cocoa/electron_ns_window.mm b/shell/browser/ui/cocoa/electron_ns_window.mm index 96d29893e0..17adef63de 100644 --- a/shell/browser/ui/cocoa/electron_ns_window.mm +++ b/shell/browser/ui/cocoa/electron_ns_window.mm @@ -200,11 +200,6 @@ void SwizzleSwipeWithEvent(NSView* view, SEL swiz_selector) { } - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)screen { - // We initialize the window in headless mode to allow painting before it is - // shown, but we don't want the headless behavior of allowing the window to be - // placed unconstrained. - self.isHeadless = false; - // Resizing is disabled. if (electron::ScopedDisableResize::IsResizeDisabled()) return [self frame]; diff --git a/shell/browser/ui/inspectable_web_contents.cc b/shell/browser/ui/inspectable_web_contents.cc index 2acff1b4c0..29a37d0d55 100644 --- a/shell/browser/ui/inspectable_web_contents.cc +++ b/shell/browser/ui/inspectable_web_contents.cc @@ -297,6 +297,11 @@ class InspectableWebContents::NetworkResourceLoader base::TimeDelta retry_delay_; }; +// Implemented separately on each platform. +InspectableWebContentsView* CreateInspectableContentsView( + InspectableWebContents* inspectable_web_contents); + +// static // static void InspectableWebContents::RegisterPrefs(PrefRegistrySimple* registry) { registry->RegisterDictionaryPref(kDevToolsBoundsPref, @@ -312,7 +317,7 @@ InspectableWebContents::InspectableWebContents( : pref_service_(pref_service), web_contents_(std::move(web_contents)), is_guest_(is_guest), - view_(new InspectableWebContentsView(this)) { + view_(CreateInspectableContentsView(this)) { const base::Value* bounds_dict = &pref_service_->GetValue(kDevToolsBoundsPref); if (bounds_dict->is_dict()) { diff --git a/shell/browser/ui/inspectable_web_contents_view.cc b/shell/browser/ui/inspectable_web_contents_view.cc index 2d21a8beb5..946b5071bc 100644 --- a/shell/browser/ui/inspectable_web_contents_view.cc +++ b/shell/browser/ui/inspectable_web_contents_view.cc @@ -5,234 +5,12 @@ #include "shell/browser/ui/inspectable_web_contents_view.h" -#include -#include - -#include "base/memory/raw_ptr.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" -#include "ui/base/models/image_model.h" -#include "ui/views/controls/label.h" -#include "ui/views/controls/webview/webview.h" -#include "ui/views/widget/widget.h" -#include "ui/views/widget/widget_delegate.h" -#include "ui/views/window/client_view.h" - namespace electron { -namespace { - -class DevToolsWindowDelegate : public views::ClientView, - public views::WidgetDelegate { - public: - DevToolsWindowDelegate(InspectableWebContentsView* shell, - views::View* view, - views::Widget* widget) - : views::ClientView(widget, view), - shell_(shell), - view_(view), - widget_(widget) { - SetOwnedByWidget(true); - set_owned_by_client(); - - if (shell->GetDelegate()) - icon_ = shell->GetDelegate()->GetDevToolsWindowIcon(); - } - ~DevToolsWindowDelegate() override = default; - - // disable copy - DevToolsWindowDelegate(const DevToolsWindowDelegate&) = delete; - DevToolsWindowDelegate& operator=(const DevToolsWindowDelegate&) = delete; - - // views::WidgetDelegate: - views::View* GetInitiallyFocusedView() override { return view_; } - std::u16string GetWindowTitle() const override { return shell_->GetTitle(); } - ui::ImageModel GetWindowAppIcon() override { return GetWindowIcon(); } - ui::ImageModel GetWindowIcon() override { return icon_; } - views::Widget* GetWidget() override { return widget_; } - const views::Widget* GetWidget() const override { return widget_; } - views::View* GetContentsView() override { return view_; } - views::ClientView* CreateClientView(views::Widget* widget) override { - return this; - } - - // views::ClientView: - views::CloseRequestResult OnWindowCloseRequested() override { - shell_->inspectable_web_contents()->CloseDevTools(); - return views::CloseRequestResult::kCannotClose; - } - - private: - raw_ptr shell_; - raw_ptr view_; - raw_ptr widget_; - ui::ImageModel icon_; -}; - -} // namespace - InspectableWebContentsView::InspectableWebContentsView( InspectableWebContents* inspectable_web_contents) - : inspectable_web_contents_(inspectable_web_contents), - devtools_web_view_(new views::WebView(nullptr)), - title_(u"Developer Tools") { - if (!inspectable_web_contents_->is_guest() && - inspectable_web_contents_->GetWebContents()->GetNativeView()) { - auto* contents_web_view = new views::WebView(nullptr); - contents_web_view->SetWebContents( - inspectable_web_contents_->GetWebContents()); - contents_view_ = contents_web_view_ = contents_web_view; - } else { - contents_view_ = new views::Label(u"No content under offscreen mode"); - } + : inspectable_web_contents_(inspectable_web_contents) {} - devtools_web_view_->SetVisible(false); - AddChildView(devtools_web_view_.get()); - AddChildView(contents_view_.get()); -} - -InspectableWebContentsView::~InspectableWebContentsView() { - if (devtools_window_) - inspectable_web_contents()->SaveDevToolsBounds( - devtools_window_->GetWindowBoundsInScreen()); -} - -void InspectableWebContentsView::ShowDevTools(bool activate) { - if (devtools_visible_) - return; - - devtools_visible_ = true; - if (devtools_window_) { - devtools_window_web_view_->SetWebContents( - inspectable_web_contents_->GetDevToolsWebContents()); - devtools_window_->SetBounds(inspectable_web_contents()->dev_tools_bounds()); - if (activate) { - devtools_window_->Show(); - } else { - devtools_window_->ShowInactive(); - } - - // Update draggable regions to account for the new dock position. - if (GetDelegate()) - GetDelegate()->DevToolsResized(); - } else { - devtools_web_view_->SetVisible(true); - devtools_web_view_->SetWebContents( - inspectable_web_contents_->GetDevToolsWebContents()); - devtools_web_view_->RequestFocus(); - DeprecatedLayoutImmediately(); - } -} - -void InspectableWebContentsView::CloseDevTools() { - if (!devtools_visible_) - return; - - devtools_visible_ = false; - if (devtools_window_) { - auto save_bounds = devtools_window_->IsMinimized() - ? devtools_window_->GetRestoredBounds() - : devtools_window_->GetWindowBoundsInScreen(); - inspectable_web_contents()->SaveDevToolsBounds(save_bounds); - - devtools_window_.reset(); - devtools_window_web_view_ = nullptr; - devtools_window_delegate_ = nullptr; - } else { - devtools_web_view_->SetVisible(false); - devtools_web_view_->SetWebContents(nullptr); - DeprecatedLayoutImmediately(); - } -} - -bool InspectableWebContentsView::IsDevToolsViewShowing() { - return devtools_visible_; -} - -bool InspectableWebContentsView::IsDevToolsViewFocused() { - if (devtools_window_web_view_) - return devtools_window_web_view_->HasFocus(); - else if (devtools_web_view_) - return devtools_web_view_->HasFocus(); - else - return false; -} - -void InspectableWebContentsView::SetIsDocked(bool docked, bool activate) { - CloseDevTools(); - - if (!docked) { - devtools_window_ = std::make_unique(); - devtools_window_web_view_ = new views::WebView(nullptr); - devtools_window_delegate_ = new DevToolsWindowDelegate( - this, devtools_window_web_view_, devtools_window_.get()); - - views::Widget::InitParams params{ - views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET}; - params.delegate = devtools_window_delegate_; - params.bounds = inspectable_web_contents()->dev_tools_bounds(); - -#if BUILDFLAG(IS_LINUX) - params.wm_role_name = "devtools"; - if (GetDelegate()) - GetDelegate()->GetDevToolsWindowWMClass(¶ms.wm_class_name, - ¶ms.wm_class_class); -#endif - - devtools_window_->Init(std::move(params)); - devtools_window_->UpdateWindowIcon(); - devtools_window_->widget_delegate()->SetHasWindowSizeControls(true); - } - - ShowDevTools(activate); -} - -void InspectableWebContentsView::SetContentsResizingStrategy( - const DevToolsContentsResizingStrategy& strategy) { - strategy_.CopyFrom(strategy); - DeprecatedLayoutImmediately(); -} - -void InspectableWebContentsView::SetTitle(const std::u16string& title) { - if (devtools_window_) { - title_ = title; - devtools_window_->UpdateWindowTitle(); - } -} - -const std::u16string InspectableWebContentsView::GetTitle() { - return title_; -} - -void InspectableWebContentsView::Layout(PassKey) { - if (!devtools_web_view_->GetVisible()) { - contents_view_->SetBoundsRect(GetContentsBounds()); - // Propagate layout call to all children, for example browser views. - LayoutSuperclass(this); - return; - } - - gfx::Size container_size(width(), height()); - gfx::Rect new_devtools_bounds; - gfx::Rect new_contents_bounds; - ApplyDevToolsContentsResizingStrategy( - strategy_, container_size, &new_devtools_bounds, &new_contents_bounds); - - // DevTools cares about the specific position, so we have to compensate RTL - // layout here. - new_devtools_bounds.set_x(GetMirroredXForRect(new_devtools_bounds)); - new_contents_bounds.set_x(GetMirroredXForRect(new_contents_bounds)); - - devtools_web_view_->SetBoundsRect(new_devtools_bounds); - contents_view_->SetBoundsRect(new_contents_bounds); - - // Propagate layout call to all children, for example browser views. - LayoutSuperclass(this); - - if (GetDelegate()) - GetDelegate()->DevToolsResized(); -} +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 bd279b4ae0..f7bb542743 100644 --- a/shell/browser/ui/inspectable_web_contents_view.h +++ b/shell/browser/ui/inspectable_web_contents_view.h @@ -9,74 +9,66 @@ #include #include "base/memory/raw_ptr.h" -#include "chrome/browser/devtools/devtools_contents_resizing_strategy.h" #include "electron/shell/common/api/api.mojom.h" +#include "ui/gfx/geometry/rounded_corners_f.h" #include "ui/gfx/native_widget_types.h" -#include "ui/views/view.h" class DevToolsContentsResizingStrategy; + +#if defined(TOOLKIT_VIEWS) namespace views { +class View; class WebView; -class Widget; -class WidgetDelegate; } // namespace views +#endif namespace electron { class InspectableWebContents; class InspectableWebContentsViewDelegate; -class InspectableWebContentsView : public views::View { +class InspectableWebContentsView { public: explicit InspectableWebContentsView( InspectableWebContents* inspectable_web_contents); - ~InspectableWebContentsView() override; + virtual ~InspectableWebContentsView(); InspectableWebContents* inspectable_web_contents() { return inspectable_web_contents_; } - views::WebView* contents_web_view() const { return contents_web_view_; } - // The delegate manages its own life. void SetDelegate(InspectableWebContentsViewDelegate* delegate) { delegate_ = delegate; } InspectableWebContentsViewDelegate* GetDelegate() const { return delegate_; } - void ShowDevTools(bool activate); - void CloseDevTools(); - bool IsDevToolsViewShowing(); - bool IsDevToolsViewFocused(); - void SetIsDocked(bool docked, bool activate); - void SetContentsResizingStrategy( - const DevToolsContentsResizingStrategy& strategy); - void SetTitle(const std::u16string& title); - const std::u16string GetTitle(); - - // views::View: - void Layout(PassKey) override; -#if BUILDFLAG(IS_MAC) - bool OnMousePressed(const ui::MouseEvent& event) override; +#if defined(TOOLKIT_VIEWS) && !BUILDFLAG(IS_MAC) + // Returns the container control, which has devtools view attached. + virtual views::View* GetView() = 0; +#else + virtual gfx::NativeView GetNativeView() const = 0; #endif - private: + virtual void ShowDevTools(bool activate) = 0; + virtual void SetCornerRadii(const gfx::RoundedCornersF& corner_radii) = 0; + // Hide the DevTools view. + virtual void CloseDevTools() = 0; + virtual bool IsDevToolsViewShowing() = 0; + virtual bool IsDevToolsViewFocused() = 0; + virtual void SetIsDocked(bool docked, bool activate) = 0; + virtual void SetContentsResizingStrategy( + const DevToolsContentsResizingStrategy& strategy) = 0; + virtual void SetTitle(const std::u16string& title) = 0; + virtual const std::u16string GetTitle() = 0; + + protected: // Owns us. raw_ptr inspectable_web_contents_; + private: raw_ptr delegate_ = nullptr; // weak references. - - std::unique_ptr devtools_window_; - raw_ptr devtools_window_web_view_ = nullptr; - raw_ptr devtools_web_view_ = nullptr; - raw_ptr contents_web_view_ = nullptr; - raw_ptr contents_view_ = nullptr; - - DevToolsContentsResizingStrategy strategy_; - bool devtools_visible_ = false; - raw_ptr devtools_window_delegate_ = nullptr; - std::u16string title_; }; } // namespace electron diff --git a/shell/browser/ui/inspectable_web_contents_view_mac.h b/shell/browser/ui/inspectable_web_contents_view_mac.h new file mode 100644 index 0000000000..88b4078614 --- /dev/null +++ b/shell/browser/ui/inspectable_web_contents_view_mac.h @@ -0,0 +1,42 @@ +// 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. + +#ifndef ELECTRON_SHELL_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_MAC_H_ +#define ELECTRON_SHELL_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_MAC_H_ + +#include "shell/browser/ui/inspectable_web_contents_view.h" + +@class ElectronInspectableWebContentsView; + +namespace electron { + +class InspectableWebContentsViewMac : public InspectableWebContentsView { + public: + explicit InspectableWebContentsViewMac( + InspectableWebContents* inspectable_web_contents); + InspectableWebContentsViewMac(const InspectableWebContentsViewMac&) = delete; + InspectableWebContentsViewMac& operator=( + const InspectableWebContentsViewMac&) = delete; + ~InspectableWebContentsViewMac() override; + + gfx::NativeView GetNativeView() const override; + void SetCornerRadii(const gfx::RoundedCornersF& corner_radii) override; + void ShowDevTools(bool activate) override; + void CloseDevTools() override; + bool IsDevToolsViewShowing() override; + bool IsDevToolsViewFocused() override; + void SetIsDocked(bool docked, bool activate) override; + void SetContentsResizingStrategy( + const DevToolsContentsResizingStrategy& strategy) override; + void SetTitle(const std::u16string& title) override; + const std::u16string GetTitle() override; + + private: + ElectronInspectableWebContentsView* __strong view_; +}; + +} // namespace electron + +#endif // ELECTRON_SHELL_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_MAC_H_ diff --git a/shell/browser/ui/inspectable_web_contents_view_mac.mm b/shell/browser/ui/inspectable_web_contents_view_mac.mm new file mode 100644 index 0000000000..091dc01731 --- /dev/null +++ b/shell/browser/ui/inspectable_web_contents_view_mac.mm @@ -0,0 +1,74 @@ +// 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_mac.h" + +#include "base/strings/sys_string_conversions.h" +#import "shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h" +#include "shell/browser/ui/inspectable_web_contents.h" +#include "shell/browser/ui/inspectable_web_contents_view_delegate.h" + +namespace electron { + +InspectableWebContentsView* CreateInspectableContentsView( + InspectableWebContents* inspectable_web_contents) { + return new InspectableWebContentsViewMac(inspectable_web_contents); +} + +InspectableWebContentsViewMac::InspectableWebContentsViewMac( + InspectableWebContents* inspectable_web_contents) + : InspectableWebContentsView(inspectable_web_contents), + view_([[ElectronInspectableWebContentsView alloc] + initWithInspectableWebContentsViewMac:this]) {} + +InspectableWebContentsViewMac::~InspectableWebContentsViewMac() { + [[NSNotificationCenter defaultCenter] removeObserver:view_]; + CloseDevTools(); +} + +gfx::NativeView InspectableWebContentsViewMac::GetNativeView() const { + return view_; +} + +void InspectableWebContentsViewMac::SetCornerRadii( + const gfx::RoundedCornersF& corner_radii) { + // We can assume all four values are identical. + [view_ setCornerRadii:corner_radii.upper_left()]; +} + +void InspectableWebContentsViewMac::ShowDevTools(bool activate) { + [view_ setDevToolsVisible:YES activate:activate]; +} + +void InspectableWebContentsViewMac::CloseDevTools() { + [view_ setDevToolsVisible:NO activate:NO]; +} + +bool InspectableWebContentsViewMac::IsDevToolsViewShowing() { + return [view_ isDevToolsVisible]; +} + +bool InspectableWebContentsViewMac::IsDevToolsViewFocused() { + return [view_ isDevToolsFocused]; +} + +void InspectableWebContentsViewMac::SetIsDocked(bool docked, bool activate) { + [view_ setIsDocked:docked activate:activate]; +} + +void InspectableWebContentsViewMac::SetContentsResizingStrategy( + const DevToolsContentsResizingStrategy& strategy) { + [view_ setContentsResizingStrategy:strategy]; +} + +void InspectableWebContentsViewMac::SetTitle(const std::u16string& title) { + [view_ setTitle:base::SysUTF16ToNSString(title)]; +} + +const std::u16string InspectableWebContentsViewMac::GetTitle() { + return base::SysNSStringToUTF16([view_ getTitle]); +} + +} // namespace electron diff --git a/shell/browser/ui/views/inspectable_web_contents_view_mac.mm b/shell/browser/ui/views/inspectable_web_contents_view_mac.mm deleted file mode 100644 index a98af7cf18..0000000000 --- a/shell/browser/ui/views/inspectable_web_contents_view_mac.mm +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2022 Salesforce, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "shell/browser/ui/inspectable_web_contents_view.h" - -#include "content/public/browser/render_widget_host_view.h" -#include "content/public/browser/web_contents.h" -#include "shell/browser/api/electron_api_web_contents.h" -#include "ui/base/cocoa/base_view.h" - -namespace electron { - -bool InspectableWebContentsView::OnMousePressed(const ui::MouseEvent& event) { - DCHECK(event.IsRightMouseButton() || - (event.IsLeftMouseButton() && event.IsControlDown())); - content::WebContents* contents = inspectable_web_contents()->GetWebContents(); - electron::api::WebContents* api_contents = - electron::api::WebContents::From(contents); - if (api_contents) { - // Temporarily pretend that the WebContents is fully non-draggable while we - // re-send the mouse event. This allows the re-dispatched event to "land" - // on the WebContents, instead of "falling through" back to the window. - api_contents->SetForceNonDraggable(true); - BaseView* contentsView = (BaseView*)contents->GetRenderWidgetHostView() - ->GetNativeView() - .GetNativeNSView(); - [contentsView mouseEvent:event.native_event().Get()]; - api_contents->SetForceNonDraggable(false); - } - return true; -} - -} // namespace electron diff --git a/shell/browser/ui/views/inspectable_web_contents_view_views.cc b/shell/browser/ui/views/inspectable_web_contents_view_views.cc new file mode 100644 index 0000000000..8180401283 --- /dev/null +++ b/shell/browser/ui/views/inspectable_web_contents_view_views.cc @@ -0,0 +1,257 @@ +// Copyright (c) 2014 The Chromium Authors. 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/views/inspectable_web_contents_view_views.h" + +#include +#include + +#include "base/memory/raw_ptr.h" +#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" +#include "ui/base/models/image_model.h" +#include "ui/views/controls/label.h" +#include "ui/views/controls/webview/webview.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" +#include "ui/views/window/client_view.h" + +namespace electron { + +namespace { + +class DevToolsWindowDelegate : public views::ClientView, + public views::WidgetDelegate { + public: + DevToolsWindowDelegate(InspectableWebContentsViewViews* shell, + views::View* view, + views::Widget* widget) + : views::ClientView(widget, view), + shell_(shell), + view_(view), + widget_(widget) { + SetOwnedByWidget(true); + set_owned_by_client(); + + if (shell->GetDelegate()) + icon_ = shell->GetDelegate()->GetDevToolsWindowIcon(); + } + ~DevToolsWindowDelegate() override = default; + + // disable copy + DevToolsWindowDelegate(const DevToolsWindowDelegate&) = delete; + DevToolsWindowDelegate& operator=(const DevToolsWindowDelegate&) = delete; + + // views::WidgetDelegate: + views::View* GetInitiallyFocusedView() override { return view_; } + std::u16string GetWindowTitle() const override { return shell_->GetTitle(); } + ui::ImageModel GetWindowAppIcon() override { return GetWindowIcon(); } + ui::ImageModel GetWindowIcon() override { return icon_; } + views::Widget* GetWidget() override { return widget_; } + const views::Widget* GetWidget() const override { return widget_; } + views::View* GetContentsView() override { return view_; } + views::ClientView* CreateClientView(views::Widget* widget) override { + return this; + } + + // views::ClientView: + views::CloseRequestResult OnWindowCloseRequested() override { + shell_->inspectable_web_contents()->CloseDevTools(); + return views::CloseRequestResult::kCannotClose; + } + + private: + raw_ptr shell_; + raw_ptr view_; + raw_ptr widget_; + ui::ImageModel icon_; +}; + +} // namespace + +InspectableWebContentsView* CreateInspectableContentsView( + InspectableWebContents* inspectable_web_contents) { + return new InspectableWebContentsViewViews(inspectable_web_contents); +} + +InspectableWebContentsViewViews::InspectableWebContentsViewViews( + InspectableWebContents* inspectable_web_contents) + : InspectableWebContentsView(inspectable_web_contents), + devtools_web_view_(new views::WebView(nullptr)), + title_(u"Developer Tools") { + if (!inspectable_web_contents_->is_guest() && + inspectable_web_contents_->GetWebContents()->GetNativeView()) { + auto* contents_web_view = new views::WebView(nullptr); + contents_web_view->SetWebContents( + inspectable_web_contents_->GetWebContents()); + contents_view_ = contents_web_view_ = contents_web_view; + } else { + contents_view_ = new views::Label(u"No content under offscreen mode"); + } + + devtools_web_view_->SetVisible(false); + AddChildView(devtools_web_view_.get()); + AddChildView(contents_view_.get()); +} + +InspectableWebContentsViewViews::~InspectableWebContentsViewViews() { + if (devtools_window_) + inspectable_web_contents()->SaveDevToolsBounds( + devtools_window_->GetWindowBoundsInScreen()); +} + +views::View* InspectableWebContentsViewViews::GetView() { + return this; +} + +void InspectableWebContentsViewViews::SetCornerRadii( + const gfx::RoundedCornersF& corner_radii) { + // WebView won't exist for offscreen rendering. + if (contents_web_view_) { + contents_web_view_->holder()->SetCornerRadii( + gfx::RoundedCornersF(corner_radii)); + } +} + +void InspectableWebContentsViewViews::ShowDevTools(bool activate) { + if (devtools_visible_) + return; + + devtools_visible_ = true; + if (devtools_window_) { + devtools_window_web_view_->SetWebContents( + inspectable_web_contents_->GetDevToolsWebContents()); + devtools_window_->SetBounds(inspectable_web_contents()->dev_tools_bounds()); + if (activate) { + devtools_window_->Show(); + } else { + devtools_window_->ShowInactive(); + } + + // Update draggable regions to account for the new dock position. + if (GetDelegate()) + GetDelegate()->DevToolsResized(); + } else { + devtools_web_view_->SetVisible(true); + devtools_web_view_->SetWebContents( + inspectable_web_contents_->GetDevToolsWebContents()); + devtools_web_view_->RequestFocus(); + DeprecatedLayoutImmediately(); + } +} + +void InspectableWebContentsViewViews::CloseDevTools() { + if (!devtools_visible_) + return; + + devtools_visible_ = false; + if (devtools_window_) { + auto save_bounds = devtools_window_->IsMinimized() + ? devtools_window_->GetRestoredBounds() + : devtools_window_->GetWindowBoundsInScreen(); + inspectable_web_contents()->SaveDevToolsBounds(save_bounds); + + devtools_window_.reset(); + devtools_window_web_view_ = nullptr; + devtools_window_delegate_ = nullptr; + } else { + devtools_web_view_->SetVisible(false); + devtools_web_view_->SetWebContents(nullptr); + DeprecatedLayoutImmediately(); + } +} + +bool InspectableWebContentsViewViews::IsDevToolsViewShowing() { + return devtools_visible_; +} + +bool InspectableWebContentsViewViews::IsDevToolsViewFocused() { + if (devtools_window_web_view_) + return devtools_window_web_view_->HasFocus(); + else if (devtools_web_view_) + return devtools_web_view_->HasFocus(); + else + return false; +} + +void InspectableWebContentsViewViews::SetIsDocked(bool docked, bool activate) { + CloseDevTools(); + + if (!docked) { + devtools_window_ = std::make_unique(); + devtools_window_web_view_ = new views::WebView(nullptr); + devtools_window_delegate_ = new DevToolsWindowDelegate( + this, devtools_window_web_view_, devtools_window_.get()); + + views::Widget::InitParams params{ + views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET}; + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.delegate = devtools_window_delegate_; + params.bounds = inspectable_web_contents()->dev_tools_bounds(); + +#if BUILDFLAG(IS_LINUX) + params.wm_role_name = "devtools"; + if (GetDelegate()) + GetDelegate()->GetDevToolsWindowWMClass(¶ms.wm_class_name, + ¶ms.wm_class_class); +#endif + + devtools_window_->Init(std::move(params)); + devtools_window_->UpdateWindowIcon(); + devtools_window_->widget_delegate()->SetHasWindowSizeControls(true); + } + + ShowDevTools(activate); +} + +void InspectableWebContentsViewViews::SetContentsResizingStrategy( + const DevToolsContentsResizingStrategy& strategy) { + strategy_.CopyFrom(strategy); + DeprecatedLayoutImmediately(); +} + +void InspectableWebContentsViewViews::SetTitle(const std::u16string& title) { + if (devtools_window_) { + title_ = title; + devtools_window_->UpdateWindowTitle(); + } +} + +const std::u16string InspectableWebContentsViewViews::GetTitle() { + return title_; +} + +void InspectableWebContentsViewViews::Layout(PassKey) { + if (!devtools_web_view_->GetVisible()) { + contents_view_->SetBoundsRect(GetContentsBounds()); + // Propagate layout call to all children, for example browser views. + LayoutSuperclass(this); + return; + } + + gfx::Size container_size(width(), height()); + gfx::Rect new_devtools_bounds; + gfx::Rect new_contents_bounds; + ApplyDevToolsContentsResizingStrategy( + strategy_, container_size, &new_devtools_bounds, &new_contents_bounds); + + // DevTools cares about the specific position, so we have to compensate RTL + // layout here. + new_devtools_bounds.set_x(GetMirroredXForRect(new_devtools_bounds)); + new_contents_bounds.set_x(GetMirroredXForRect(new_contents_bounds)); + + devtools_web_view_->SetBoundsRect(new_devtools_bounds); + contents_view_->SetBoundsRect(new_contents_bounds); + + // Propagate layout call to all children, for example browser views. + LayoutSuperclass(this); + + if (GetDelegate()) + GetDelegate()->DevToolsResized(); +} + +} // namespace electron diff --git a/shell/browser/ui/views/inspectable_web_contents_view_views.h b/shell/browser/ui/views/inspectable_web_contents_view_views.h new file mode 100644 index 0000000000..36554a497d --- /dev/null +++ b/shell/browser/ui/views/inspectable_web_contents_view_views.h @@ -0,0 +1,63 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE-CHROMIUM file. + +#ifndef ELECTRON_SHELL_BROWSER_UI_VIEWS_INSPECTABLE_WEB_CONTENTS_VIEW_VIEWS_H_ +#define ELECTRON_SHELL_BROWSER_UI_VIEWS_INSPECTABLE_WEB_CONTENTS_VIEW_VIEWS_H_ + +#include + +#include "base/compiler_specific.h" +#include "base/memory/raw_ptr.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 { +class WebView; +class Widget; +class WidgetDelegate; +} // namespace views + +namespace electron { + +class InspectableWebContentsViewViews : public InspectableWebContentsView, + public views::View { + public: + explicit InspectableWebContentsViewViews( + InspectableWebContents* inspectable_web_contents); + ~InspectableWebContentsViewViews() override; + + // InspectableWebContentsView: + views::View* GetView() override; + void ShowDevTools(bool activate) override; + void SetCornerRadii(const gfx::RoundedCornersF& corner_radii) override; + void CloseDevTools() override; + bool IsDevToolsViewShowing() override; + bool IsDevToolsViewFocused() override; + void SetIsDocked(bool docked, bool activate) override; + void SetContentsResizingStrategy( + const DevToolsContentsResizingStrategy& strategy) override; + void SetTitle(const std::u16string& title) override; + const std::u16string GetTitle() override; + + // views::View: + void Layout(PassKey) override; + + private: + std::unique_ptr devtools_window_; + raw_ptr devtools_window_web_view_ = nullptr; + raw_ptr contents_web_view_ = nullptr; + raw_ptr contents_view_ = nullptr; + raw_ptr devtools_web_view_ = nullptr; + + DevToolsContentsResizingStrategy strategy_; + bool devtools_visible_ = false; + raw_ptr devtools_window_delegate_ = nullptr; + std::u16string title_; +}; + +} // namespace electron + +#endif // ELECTRON_SHELL_BROWSER_UI_VIEWS_INSPECTABLE_WEB_CONTENTS_VIEW_VIEWS_H_