From 2f20dbea5500a0842d9b95d2477c8b717c2e30f3 Mon Sep 17 00:00:00 2001 From: "trop[bot]" <37223003+trop[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 22:09:29 -0500 Subject: [PATCH] fix: segfault when moving WebContentsView between BrowserWindows (#44613) * fix: segfault when moving WebContentsView between BrowserWindows Co-authored-by: John Kleinschmidt * chore: actually enable fix Co-authored-by: John Kleinschmidt * fixup segfault when moving WebContentsView between BrowserWindows Co-authored-by: John Kleinschmidt --------- Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: John Kleinschmidt --- shell/browser/api/electron_api_view.cc | 26 +++++++++++++++- shell/browser/api/electron_api_view.h | 2 ++ .../webview-move-between-windows/index.js | 31 +++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/crash-cases/webview-move-between-windows/index.js diff --git a/shell/browser/api/electron_api_view.cc b/shell/browser/api/electron_api_view.cc index b01a08be88cb..c65f80b164fe 100644 --- a/shell/browser/api/electron_api_view.cc +++ b/shell/browser/api/electron_api_view.cc @@ -124,6 +124,15 @@ struct Converter { } }; +template <> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + electron::api::View* out) { + return gin::ConvertFromV8(isolate, val, &out); + } +}; + template <> struct Converter { static v8::Local ToV8(v8::Isolate* isolate, @@ -257,11 +266,13 @@ void View::RemoveChildView(gin::Handle child) { #if BUILDFLAG(IS_MAC) ScopedCAActionDisabler disable_animations; #endif + // Remove from child_views first so that OnChildViewRemoved doesn't try to + // remove it again + child_views_.erase(it); // It's possible for the child's view to be invalid here // if the child's webContents was closed or destroyed. if (child->view()) view_->RemoveChildView(child->view()); - child_views_.erase(it); } } @@ -388,6 +399,19 @@ void View::OnViewIsDeleting(views::View* observed_view) { view_ = nullptr; } +void View::OnChildViewRemoved(views::View* observed_view, views::View* child) { + v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); + auto it = std::ranges::find_if( + child_views_, [&](const v8::Global& child_view) { + View current_view; + gin::ConvertFromV8(isolate, child_view.Get(isolate), ¤t_view); + return current_view.view()->GetID() == child->GetID(); + }); + if (it != child_views_.end()) { + child_views_.erase(it); + } +} + // static gin_helper::WrappableBase* View::New(gin::Arguments* args) { View* view = new View(); diff --git a/shell/browser/api/electron_api_view.h b/shell/browser/api/electron_api_view.h index 6eeb16521906..1b0a3f815a6f 100644 --- a/shell/browser/api/electron_api_view.h +++ b/shell/browser/api/electron_api_view.h @@ -47,6 +47,8 @@ class View : public gin_helper::EventEmitter, // views::ViewObserver void OnViewBoundsChanged(views::View* observed_view) override; void OnViewIsDeleting(views::View* observed_view) override; + void OnChildViewRemoved(views::View* observed_view, + views::View* child) override; views::View* view() const { return view_; } std::optional border_radius() const { return border_radius_; } diff --git a/spec/fixtures/crash-cases/webview-move-between-windows/index.js b/spec/fixtures/crash-cases/webview-move-between-windows/index.js new file mode 100644 index 000000000000..de9053ec65ca --- /dev/null +++ b/spec/fixtures/crash-cases/webview-move-between-windows/index.js @@ -0,0 +1,31 @@ +const { app, BrowserWindow, WebContentsView } = require('electron'); + +function createWindow () { + // Create the browser window. + const mainWindow = new BrowserWindow(); + const secondaryWindow = new BrowserWindow(); + + const contentsView = new WebContentsView(); + mainWindow.contentView.addChildView(contentsView); + mainWindow.webContents.setDevToolsWebContents(contentsView.webContents); + mainWindow.openDevTools(); + + contentsView.setBounds({ + x: 400, + y: 0, + width: 400, + height: 600 + }); + + setTimeout(() => { + secondaryWindow.contentView.addChildView(contentsView); + setTimeout(() => { + mainWindow.contentView.addChildView(contentsView); + app.quit(); + }, 1000); + }, 1000); +} + +app.whenReady().then(() => { + createWindow(); +});