diff --git a/atom/browser/api/atom_api_browser_window.cc b/atom/browser/api/atom_api_browser_window.cc index 4e79e452485f..59842687bc52 100644 --- a/atom/browser/api/atom_api_browser_window.cc +++ b/atom/browser/api/atom_api_browser_window.cc @@ -304,7 +304,29 @@ void BrowserWindow::SetBackgroundColor(const std::string& color_name) { } void BrowserWindow::SetBrowserView(v8::Local value) { - TopLevelWindow::SetBrowserView(value); + TopLevelWindow::ResetBrowserViews(); + TopLevelWindow::AddBrowserView(value); +#if defined(OS_MACOSX) + UpdateDraggableRegions(nullptr, draggable_regions_); +#endif +} + +void BrowserWindow::AddBrowserView(v8::Local value) { + TopLevelWindow::AddBrowserView(value); +#if defined(OS_MACOSX) + UpdateDraggableRegions(nullptr, draggable_regions_); +#endif +} + +void BrowserWindow::RemoveBrowserView(v8::Local value) { + TopLevelWindow::RemoveBrowserView(value); +#if defined(OS_MACOSX) + UpdateDraggableRegions(nullptr, draggable_regions_); +#endif +} + +void BrowserWindow::ResetBrowserViews() { + TopLevelWindow::ResetBrowserViews(); #if defined(OS_MACOSX) UpdateDraggableRegions(nullptr, draggable_regions_); #endif diff --git a/atom/browser/api/atom_api_browser_window.h b/atom/browser/api/atom_api_browser_window.h index 074fcab7c06b..6cf33384f60e 100644 --- a/atom/browser/api/atom_api_browser_window.h +++ b/atom/browser/api/atom_api_browser_window.h @@ -72,6 +72,9 @@ class BrowserWindow : public TopLevelWindow, void Blur() override; void SetBackgroundColor(const std::string& color_name) override; void SetBrowserView(v8::Local value) override; + void AddBrowserView(v8::Local value) override; + void RemoveBrowserView(v8::Local value) override; + void ResetBrowserViews() override; void SetVibrancy(v8::Isolate* isolate, v8::Local value) override; // BrowserWindow APIs. diff --git a/atom/browser/api/atom_api_browser_window_mac.mm b/atom/browser/api/atom_api_browser_window_mac.mm index e35a8842781e..47f6488f4298 100644 --- a/atom/browser/api/atom_api_browser_window_mac.mm +++ b/atom/browser/api/atom_api_browser_window_mac.mm @@ -108,8 +108,10 @@ void BrowserWindow::UpdateDraggableRegions( DraggableRegionsToSkRegion(regions), webViewWidth, webViewHeight); } - if (window_->browser_view()) - window_->browser_view()->UpdateDraggableRegions(drag_exclude_rects); + auto browser_views = window_->browser_views(); + for (NativeBrowserView* view : browser_views) { + (view)->UpdateDraggableRegions(drag_exclude_rects); + } // Create and add a ControlRegionView for each region that needs to be // excluded from the dragging. diff --git a/atom/browser/api/atom_api_top_level_window.cc b/atom/browser/api/atom_api_top_level_window.cc index 49753ab34970..cd6ef374218b 100644 --- a/atom/browser/api/atom_api_top_level_window.cc +++ b/atom/browser/api/atom_api_top_level_window.cc @@ -159,7 +159,7 @@ void TopLevelWindow::OnWindowClosed() { Emit("closed"); RemoveFromParentChildWindows(); - ResetBrowserView(); + TopLevelWindow::ResetBrowserViews(); // Destroy the native class when window is closed. base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, GetDestroyClosure()); @@ -294,6 +294,7 @@ void TopLevelWindow::OnWindowMessage(UINT message, #endif void TopLevelWindow::SetContentView(mate::Handle view) { + ResetBrowserViews(); content_view_.Reset(isolate(), view.ToV8()); window_->SetContentView(view->view()); } @@ -681,18 +682,37 @@ void TopLevelWindow::SetParentWindow(v8::Local value, } void TopLevelWindow::SetBrowserView(v8::Local value) { - ResetBrowserView(); + ResetBrowserViews(); + AddBrowserView(value); +} +void TopLevelWindow::AddBrowserView(v8::Local value) { mate::Handle browser_view; - if (value->IsNull() || value->IsUndefined()) { - window_->SetBrowserView(nullptr); - } else if (mate::ConvertFromV8(isolate(), value, &browser_view)) { - window_->SetBrowserView(browser_view->view()); - browser_view->web_contents()->SetOwnerWindow(window_.get()); - browser_view_.Reset(isolate(), value); + if (value->IsObject() && + mate::ConvertFromV8(isolate(), value, &browser_view)) { + auto get_that_view = browser_views_.find(browser_view->weak_map_id()); + if (get_that_view == browser_views_.end()) { + window_->AddBrowserView(browser_view->view()); + browser_view->web_contents()->SetOwnerWindow(window_.get()); + browser_views_[browser_view->weak_map_id()].Reset(isolate(), value); + } } } +void TopLevelWindow::RemoveBrowserView(v8::Local value) { + mate::Handle browser_view; + if (value->IsObject() && + mate::ConvertFromV8(isolate(), value, &browser_view)) { + auto get_that_view = browser_views_.find(browser_view->weak_map_id()); + if (get_that_view != browser_views_.end()) { + window_->RemoveBrowserView(browser_view->view()); + browser_view->web_contents()->SetOwnerWindow(nullptr); + + (*get_that_view).second.Reset(isolate(), value); + browser_views_.erase(get_that_view); + } + } +} v8::Local TopLevelWindow::GetNativeWindowHandle() { // TODO(MarshallOfSound): Replace once // https://chromium-review.googlesource.com/c/chromium/src/+/1253094/ has @@ -847,12 +867,29 @@ std::vector> TopLevelWindow::GetChildWindows() const { return child_windows_.Values(isolate()); } -v8::Local TopLevelWindow::GetBrowserView() const { - if (browser_view_.IsEmpty()) { +v8::Local TopLevelWindow::GetBrowserView( + mate::Arguments* args) const { + if (browser_views_.size() == 0) { + return v8::Null(isolate()); + } else if (browser_views_.size() == 1) { + auto first_view = browser_views_.begin(); + return v8::Local::New(isolate(), (*first_view).second); + } else { + args->ThrowError( + "BrowserWindow have multiple BrowserViews, " + "Use getBrowserViews() instead"); return v8::Null(isolate()); } +} - return v8::Local::New(isolate(), browser_view_); +std::vector> TopLevelWindow::GetBrowserViews() const { + std::vector> ret; + + for (auto const& views_iter : browser_views_) { + ret.push_back(v8::Local::New(isolate(), views_iter.second)); + } + + return ret; } bool TopLevelWindow::IsModal() const { @@ -944,17 +981,21 @@ int32_t TopLevelWindow::GetID() const { return weak_map_id(); } -void TopLevelWindow::ResetBrowserView() { - if (browser_view_.IsEmpty()) - return; +void TopLevelWindow::ResetBrowserViews() { + for (auto& item : browser_views_) { + mate::Handle browser_view; + if (mate::ConvertFromV8(isolate(), + v8::Local::New(isolate(), item.second), + &browser_view) && + !browser_view.IsEmpty()) { + window_->RemoveBrowserView(browser_view->view()); + browser_view->web_contents()->SetOwnerWindow(nullptr); + } - mate::Handle browser_view; - if (mate::ConvertFromV8(isolate(), GetBrowserView(), &browser_view) && - !browser_view.IsEmpty()) { - browser_view->web_contents()->SetOwnerWindow(nullptr); + item.second.Reset(); } - browser_view_.Reset(); + browser_views_.clear(); } void TopLevelWindow::RemoveFromParentChildWindows() { @@ -1064,6 +1105,8 @@ void TopLevelWindow::BuildPrototype(v8::Isolate* isolate, .SetMethod("setMenu", &TopLevelWindow::SetMenu) .SetMethod("setParentWindow", &TopLevelWindow::SetParentWindow) .SetMethod("setBrowserView", &TopLevelWindow::SetBrowserView) + .SetMethod("addBrowserView", &TopLevelWindow::AddBrowserView) + .SetMethod("removeBrowserView", &TopLevelWindow::RemoveBrowserView) .SetMethod("getNativeWindowHandle", &TopLevelWindow::GetNativeWindowHandle) .SetMethod("setProgressBar", &TopLevelWindow::SetProgressBar) @@ -1101,6 +1144,7 @@ void TopLevelWindow::BuildPrototype(v8::Isolate* isolate, .SetMethod("getParentWindow", &TopLevelWindow::GetParentWindow) .SetMethod("getChildWindows", &TopLevelWindow::GetChildWindows) .SetMethod("getBrowserView", &TopLevelWindow::GetBrowserView) + .SetMethod("getBrowserViews", &TopLevelWindow::GetBrowserViews) .SetMethod("isModal", &TopLevelWindow::IsModal) .SetMethod("setThumbarButtons", &TopLevelWindow::SetThumbarButtons) #if defined(TOOLKIT_VIEWS) diff --git a/atom/browser/api/atom_api_top_level_window.h b/atom/browser/api/atom_api_top_level_window.h index 7c71690cc951..61ba4eeeac0d 100644 --- a/atom/browser/api/atom_api_top_level_window.h +++ b/atom/browser/api/atom_api_top_level_window.h @@ -165,6 +165,10 @@ class TopLevelWindow : public mate::TrackableObject, void SetMenu(v8::Isolate* isolate, v8::Local menu); void SetParentWindow(v8::Local value, mate::Arguments* args); virtual void SetBrowserView(v8::Local value); + virtual void AddBrowserView(v8::Local value); + virtual void RemoveBrowserView(v8::Local value); + virtual std::vector> GetBrowserViews() const; + virtual void ResetBrowserViews(); v8::Local GetNativeWindowHandle(); void SetProgressBar(double progress, mate::Arguments* args); void SetOverlayIcon(const gfx::Image& overlay, @@ -195,7 +199,7 @@ class TopLevelWindow : public mate::TrackableObject, v8::Local GetContentView() const; v8::Local GetParentWindow() const; std::vector> GetChildWindows() const; - v8::Local GetBrowserView() const; + v8::Local GetBrowserView(mate::Arguments* args) const; bool IsModal() const; // Extra APIs added in JS. @@ -238,7 +242,7 @@ class TopLevelWindow : public mate::TrackableObject, #endif v8::Global content_view_; - v8::Global browser_view_; + std::map> browser_views_; v8::Global menu_; v8::Global parent_window_; KeyWeakMap child_windows_; diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 85dd3590991f..1fa2cac35c9b 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -5,6 +5,7 @@ #ifndef ATOM_BROWSER_NATIVE_WINDOW_H_ #define ATOM_BROWSER_NATIVE_WINDOW_H_ +#include #include #include #include @@ -155,7 +156,8 @@ class NativeWindow : public base::SupportsUserData, virtual void SetFocusable(bool focusable); virtual void SetMenu(AtomMenuModel* menu); virtual void SetParentWindow(NativeWindow* parent); - virtual void SetBrowserView(NativeBrowserView* browser_view) = 0; + virtual void AddBrowserView(NativeBrowserView* browser_view) = 0; + virtual void RemoveBrowserView(NativeBrowserView* browser_view) = 0; virtual gfx::NativeView GetNativeView() const = 0; virtual gfx::NativeWindow GetNativeWindow() const = 0; virtual gfx::AcceleratedWidget GetAcceleratedWidget() const = 0; @@ -286,10 +288,11 @@ class NativeWindow : public base::SupportsUserData, bool transparent() const { return transparent_; } bool enable_larger_than_screen() const { return enable_larger_than_screen_; } - NativeBrowserView* browser_view() const { return browser_view_; } NativeWindow* parent() const { return parent_; } bool is_modal() const { return is_modal_; } + std::list browser_views() const { return browser_views_; } + protected: NativeWindow(const mate::Dictionary& options, NativeWindow* parent); @@ -298,8 +301,13 @@ class NativeWindow : public base::SupportsUserData, const views::Widget* GetWidget() const override; void set_content_view(views::View* view) { content_view_ = view; } - void set_browser_view(NativeBrowserView* browser_view) { - browser_view_ = browser_view; + + void add_browser_view(NativeBrowserView* browser_view) { + browser_views_.push_back(browser_view); + } + void remove_browser_view(NativeBrowserView* browser_view) { + browser_views_.remove_if( + [&browser_view](NativeBrowserView* n) { return (n == browser_view); }); } private: @@ -340,7 +348,7 @@ class NativeWindow : public base::SupportsUserData, bool is_modal_ = false; // The browser view layer. - NativeBrowserView* browser_view_ = nullptr; + std::list browser_views_; // Observers of this window. base::ObserverList observers_; diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index f948c7a3acec..73ac3a01dcb8 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -102,7 +102,8 @@ class NativeWindowMac : public NativeWindow { bool IsDocumentEdited() override; void SetIgnoreMouseEvents(bool ignore, bool forward) override; void SetContentProtection(bool enable) override; - void SetBrowserView(NativeBrowserView* browser_view) override; + void AddBrowserView(NativeBrowserView* browser_view) override; + void RemoveBrowserView(NativeBrowserView* browser_view) override; void SetParentWindow(NativeWindow* parent) override; gfx::NativeView GetNativeView() const override; gfx::NativeWindow GetNativeWindow() const override; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 47c3b748ea92..3dd2dca16ef0 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -1069,22 +1069,16 @@ void NativeWindowMac::SetContentProtection(bool enable) { setSharingType:enable ? NSWindowSharingNone : NSWindowSharingReadOnly]; } -void NativeWindowMac::SetBrowserView(NativeBrowserView* view) { +void NativeWindowMac::AddBrowserView(NativeBrowserView* view) { [CATransaction begin]; [CATransaction setDisableActions:YES]; - if (browser_view()) { - [browser_view()->GetInspectableWebContentsView()->GetNativeView() - removeFromSuperview]; - set_browser_view(nullptr); - } - if (!view) { [CATransaction commit]; return; } - set_browser_view(view); + add_browser_view(view); auto* native_view = view->GetInspectableWebContentsView()->GetNativeView(); [[window_ contentView] addSubview:native_view positioned:NSWindowAbove @@ -1094,6 +1088,21 @@ void NativeWindowMac::SetBrowserView(NativeBrowserView* view) { [CATransaction commit]; } +void NativeWindowMac::RemoveBrowserView(NativeBrowserView* view) { + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + + if (!view) { + [CATransaction commit]; + return; + } + + [view->GetInspectableWebContentsView()->GetNativeView() removeFromSuperview]; + remove_browser_view(view); + + [CATransaction commit]; +} + void NativeWindowMac::SetParentWindow(NativeWindow* parent) { InternalSetParentWindow(parent, IsVisible()); } diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 27d901a85e68..c774b9b459eb 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -315,11 +315,6 @@ NativeWindowViews::~NativeWindowViews() { void NativeWindowViews::SetContentView(views::View* view) { if (content_view()) { root_view_->RemoveChildView(content_view()); - if (browser_view()) { - content_view()->RemoveChildView( - browser_view()->GetInspectableWebContentsView()->GetView()); - set_browser_view(nullptr); - } } set_content_view(view); focused_view_ = view; @@ -960,27 +955,33 @@ void NativeWindowViews::SetMenu(AtomMenuModel* menu_model) { } } -void NativeWindowViews::SetBrowserView(NativeBrowserView* view) { +void NativeWindowViews::AddBrowserView(NativeBrowserView* view) { if (!content_view()) return; - if (browser_view()) { - content_view()->RemoveChildView( - browser_view()->GetInspectableWebContentsView()->GetView()); - set_browser_view(nullptr); - } - if (!view) { return; } - // Add as child of the main web view to avoid (0, 0) origin from overlapping - // with menu bar. - set_browser_view(view); + add_browser_view(view); + content_view()->AddChildView( view->GetInspectableWebContentsView()->GetView()); } +void NativeWindowViews::RemoveBrowserView(NativeBrowserView* view) { + if (!content_view()) + return; + + if (!view) { + return; + } + + content_view()->RemoveChildView( + view->GetInspectableWebContentsView()->GetView()); + remove_browser_view(view); +} + void NativeWindowViews::SetParentWindow(NativeWindow* parent) { NativeWindow::SetParentWindow(parent); @@ -1175,6 +1176,26 @@ void NativeWindowViews::OnWidgetActivationChanged(views::Widget* changed_widget, root_view_->ResetAltState(); } +void NativeWindowViews::AutoresizeBrowserView(int width_delta, + int height_delta, + NativeBrowserView* browser_view) { + const auto flags = + static_cast(browser_view)->GetAutoResizeFlags(); + if (!(flags & kAutoResizeWidth)) { + width_delta = 0; + } + if (!(flags & kAutoResizeHeight)) { + height_delta = 0; + } + if (height_delta || width_delta) { + auto* view = browser_view->GetInspectableWebContentsView()->GetView(); + auto new_view_size = view->size(); + new_view_size.set_width(new_view_size.width() + width_delta); + new_view_size.set_height(new_view_size.height() + height_delta); + view->SetSize(new_view_size); + } +} + void NativeWindowViews::OnWidgetBoundsChanged(views::Widget* changed_widget, const gfx::Rect& bounds) { if (changed_widget != widget()) @@ -1184,23 +1205,10 @@ void NativeWindowViews::OnWidgetBoundsChanged(views::Widget* changed_widget, // handle minimized windows on Windows. const auto new_bounds = GetBounds(); if (widget_size_ != new_bounds.size()) { - if (browser_view()) { - const auto flags = static_cast(browser_view()) - ->GetAutoResizeFlags(); - int width_delta = 0; - int height_delta = 0; - if (flags & kAutoResizeWidth) { - width_delta = new_bounds.width() - widget_size_.width(); - } - if (flags & kAutoResizeHeight) { - height_delta = new_bounds.height() - widget_size_.height(); - } - - auto* view = browser_view()->GetInspectableWebContentsView()->GetView(); - auto new_view_size = view->size(); - new_view_size.set_width(new_view_size.width() + width_delta); - new_view_size.set_height(new_view_size.height() + height_delta); - view->SetSize(new_view_size); + int width_delta = new_bounds.width() - widget_size_.width(); + int height_delta = new_bounds.height() - widget_size_.height(); + for (NativeBrowserView* item : browser_views()) { + AutoresizeBrowserView(width_delta, height_delta, item); } NotifyWindowResize(); diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 1ab042a3dd09..2d4ab3ec1921 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -112,7 +112,8 @@ class NativeWindowViews : public NativeWindow, void SetContentProtection(bool enable) override; void SetFocusable(bool focusable) override; void SetMenu(AtomMenuModel* menu_model) override; - void SetBrowserView(NativeBrowserView* browser_view) override; + void AddBrowserView(NativeBrowserView* browser_view) override; + void RemoveBrowserView(NativeBrowserView* browser_view) override; void SetParentWindow(NativeWindow* parent) override; gfx::NativeView GetNativeView() const override; gfx::NativeWindow GetNativeWindow() const override; @@ -157,7 +158,9 @@ class NativeWindowViews : public NativeWindow, void OnWidgetActivationChanged(views::Widget* widget, bool active) override; void OnWidgetBoundsChanged(views::Widget* widget, const gfx::Rect& bounds) override; - + void AutoresizeBrowserView(int width_delta, + int height_delta, + NativeBrowserView* browser_view); // views::WidgetDelegate: void DeleteDelegate() override; views::View* GetInitiallyFocusedView() override; diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 9593daf15a95..e48d646aa11e 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1601,11 +1601,29 @@ removed in future Electron releases. #### `win.setBrowserView(browserView)` _Experimental_ -* `browserView` [BrowserView](browser-view.md) +* `browserView` [BrowserView](browser-view.md). Attach browserView to win. +If there is some other browserViews was attached they will be removed from +this window. #### `win.getBrowserView()` _Experimental_ -Returns `BrowserView | null` - an attached BrowserView. Returns `null` if none is attached. +Returns `BrowserView | null` - an BrowserView what is attached. Returns `null` +if none is attached. Throw error if multiple BrowserViews is attached. + +#### `win.addBrowserView(browserView)` _Experimental_ + +* `browserView` [BrowserView](browser-view.md) + +Replacement API for setBrowserView supporting work with multi browser views. + +#### `win.removeBrowserView(browserView)` _Experimental_ + +* `browserView` [BrowserView](browser-view.md) + +#### `win.getBrowserViews()` _Experimental_ + +Returns array of `BrowserView` what was an attached with addBrowserView +or setBrowserView. **Note:** The BrowserView API is currently experimental and may change or be removed in future Electron releases. diff --git a/spec/api-browser-view-spec.js b/spec/api-browser-view-spec.js index 515d9f926d2c..fbbb4ebe0180 100644 --- a/spec/api-browser-view-spec.js +++ b/spec/api-browser-view-spec.js @@ -132,6 +132,54 @@ describe('BrowserView module', () => { }) }) + describe('BrowserWindow.addBrowserView()', () => { + it('does not throw for valid args', () => { + let view1 = new BrowserView() + w.addBrowserView(view1) + let view2 = new BrowserView() + w.addBrowserView(view2) + view1.destroy() + view1 = null + view2.destroy() + view2 = null + }) + it('does not throw if called multiple times with same view', () => { + view = new BrowserView() + w.addBrowserView(view) + w.addBrowserView(view) + w.addBrowserView(view) + }) + }) + + describe('BrowserWindow.removeBrowserView()', () => { + it('does not throw if called multiple times with same view', () => { + view = new BrowserView() + w.addBrowserView(view) + w.removeBrowserView(view) + w.removeBrowserView(view) + }) + }) + + describe('BrowserWindow.getBrowserViews()', () => { + it('returns same views as was added', () => { + let view1 = new BrowserView() + w.addBrowserView(view1) + let view2 = new BrowserView() + w.addBrowserView(view2) + + expect(view1.id).to.be.not.null() + const views = w.getBrowserViews() + expect(views.length).to.equal(2) + expect(views[0].webContents.id).to.equal(view1.webContents.id) + expect(views[1].webContents.id).to.equal(view2.webContents.id) + + view1.destroy() + view1 = null + view2.destroy() + view2 = null + }) + }) + describe('BrowserView.webContents.getOwnerBrowserWindow()', () => { it('points to owning window', () => { view = new BrowserView()