feat: add multi BrowserView support to BrowserWindow (#16148)

* feat: add multi BrowserView support to BrowserWindow

Add functions addBrowserView, removeBroserView, getBrowserViews to
BrowserWindow class. Existing API as setBrowserView and
getBrowserView code replaced to use new api inside.

* fix: for lint and osx compile errors

* fix: lint error in test code

* feat: add multi BrowserView support to BrowserWindow

Add functions addBrowserView, removeBroserView, getBrowserViews to
BrowserWindow class. Existing API as setBrowserView and
getBrowserView code replaced to use new api inside.

* fix: for lint and osx compile errors

* fix: lint error in test code

* fix: method to be accessible on mac api impl

* fix: missed function declarations for mac impl

* fix: use base class reset function
This commit is contained in:
Vladimir 2018-12-22 04:49:26 +03:00 committed by Cheng Zhao
parent 18ca4b6a3a
commit 5ae3d1a1b2
12 changed files with 244 additions and 74 deletions

View file

@ -304,7 +304,29 @@ void BrowserWindow::SetBackgroundColor(const std::string& color_name) {
}
void BrowserWindow::SetBrowserView(v8::Local<v8::Value> value) {
TopLevelWindow::SetBrowserView(value);
TopLevelWindow::ResetBrowserViews();
TopLevelWindow::AddBrowserView(value);
#if defined(OS_MACOSX)
UpdateDraggableRegions(nullptr, draggable_regions_);
#endif
}
void BrowserWindow::AddBrowserView(v8::Local<v8::Value> value) {
TopLevelWindow::AddBrowserView(value);
#if defined(OS_MACOSX)
UpdateDraggableRegions(nullptr, draggable_regions_);
#endif
}
void BrowserWindow::RemoveBrowserView(v8::Local<v8::Value> 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

View file

@ -72,6 +72,9 @@ class BrowserWindow : public TopLevelWindow,
void Blur() override;
void SetBackgroundColor(const std::string& color_name) override;
void SetBrowserView(v8::Local<v8::Value> value) override;
void AddBrowserView(v8::Local<v8::Value> value) override;
void RemoveBrowserView(v8::Local<v8::Value> value) override;
void ResetBrowserViews() override;
void SetVibrancy(v8::Isolate* isolate, v8::Local<v8::Value> value) override;
// BrowserWindow APIs.

View file

@ -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.

View file

@ -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> view) {
ResetBrowserViews();
content_view_.Reset(isolate(), view.ToV8());
window_->SetContentView(view->view());
}
@ -681,18 +682,37 @@ void TopLevelWindow::SetParentWindow(v8::Local<v8::Value> value,
}
void TopLevelWindow::SetBrowserView(v8::Local<v8::Value> value) {
ResetBrowserView();
ResetBrowserViews();
AddBrowserView(value);
}
void TopLevelWindow::AddBrowserView(v8::Local<v8::Value> value) {
mate::Handle<BrowserView> 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<v8::Value> value) {
mate::Handle<BrowserView> 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<v8::Value> TopLevelWindow::GetNativeWindowHandle() {
// TODO(MarshallOfSound): Replace once
// https://chromium-review.googlesource.com/c/chromium/src/+/1253094/ has
@ -847,12 +867,29 @@ std::vector<v8::Local<v8::Object>> TopLevelWindow::GetChildWindows() const {
return child_windows_.Values(isolate());
}
v8::Local<v8::Value> TopLevelWindow::GetBrowserView() const {
if (browser_view_.IsEmpty()) {
v8::Local<v8::Value> 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<v8::Value>::New(isolate(), (*first_view).second);
} else {
args->ThrowError(
"BrowserWindow have multiple BrowserViews, "
"Use getBrowserViews() instead");
return v8::Null(isolate());
}
}
return v8::Local<v8::Value>::New(isolate(), browser_view_);
std::vector<v8::Local<v8::Value>> TopLevelWindow::GetBrowserViews() const {
std::vector<v8::Local<v8::Value>> ret;
for (auto const& views_iter : browser_views_) {
ret.push_back(v8::Local<v8::Value>::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<BrowserView> browser_view;
if (mate::ConvertFromV8(isolate(),
v8::Local<v8::Value>::New(isolate(), item.second),
&browser_view) &&
!browser_view.IsEmpty()) {
window_->RemoveBrowserView(browser_view->view());
browser_view->web_contents()->SetOwnerWindow(nullptr);
}
mate::Handle<BrowserView> 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)

View file

@ -165,6 +165,10 @@ class TopLevelWindow : public mate::TrackableObject<TopLevelWindow>,
void SetMenu(v8::Isolate* isolate, v8::Local<v8::Value> menu);
void SetParentWindow(v8::Local<v8::Value> value, mate::Arguments* args);
virtual void SetBrowserView(v8::Local<v8::Value> value);
virtual void AddBrowserView(v8::Local<v8::Value> value);
virtual void RemoveBrowserView(v8::Local<v8::Value> value);
virtual std::vector<v8::Local<v8::Value>> GetBrowserViews() const;
virtual void ResetBrowserViews();
v8::Local<v8::Value> GetNativeWindowHandle();
void SetProgressBar(double progress, mate::Arguments* args);
void SetOverlayIcon(const gfx::Image& overlay,
@ -195,7 +199,7 @@ class TopLevelWindow : public mate::TrackableObject<TopLevelWindow>,
v8::Local<v8::Value> GetContentView() const;
v8::Local<v8::Value> GetParentWindow() const;
std::vector<v8::Local<v8::Object>> GetChildWindows() const;
v8::Local<v8::Value> GetBrowserView() const;
v8::Local<v8::Value> GetBrowserView(mate::Arguments* args) const;
bool IsModal() const;
// Extra APIs added in JS.
@ -238,7 +242,7 @@ class TopLevelWindow : public mate::TrackableObject<TopLevelWindow>,
#endif
v8::Global<v8::Value> content_view_;
v8::Global<v8::Value> browser_view_;
std::map<int32_t, v8::Global<v8::Value>> browser_views_;
v8::Global<v8::Value> menu_;
v8::Global<v8::Value> parent_window_;
KeyWeakMap<int> child_windows_;

View file

@ -5,6 +5,7 @@
#ifndef ATOM_BROWSER_NATIVE_WINDOW_H_
#define ATOM_BROWSER_NATIVE_WINDOW_H_
#include <list>
#include <map>
#include <memory>
#include <string>
@ -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<NativeBrowserView*> 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<NativeBrowserView*> browser_views_;
// Observers of this window.
base::ObserverList<NativeWindowObserver> observers_;

View file

@ -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;

View file

@ -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());
}

View file

@ -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<NativeBrowserViewViews*>(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<NativeBrowserViewViews*>(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();

View file

@ -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;

View file

@ -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.

View file

@ -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()