From 0b9b05ed85b6576a266b5fd12df45f78d124a51a Mon Sep 17 00:00:00 2001 From: Ian Chua Date: Mon, 12 Oct 2026 16:44:24 +0800 Subject: [PATCH] fix: Isolate plugin's webview browser sessions --- src/slic3r/GUI/DockPanel.cpp | 5 +- src/slic3r/GUI/DockPanel.hpp | 5 +- src/slic3r/GUI/WebDialog.cpp | 5 +- src/slic3r/GUI/WebDialog.hpp | 3 +- src/slic3r/GUI/WebPanel.cpp | 4 +- src/slic3r/GUI/WebPanel.hpp | 2 +- src/slic3r/GUI/Widgets/WebView.cpp | 62 ++++++++++++++++++-- src/slic3r/GUI/Widgets/WebView.hpp | 6 +- src/slic3r/GUI/Widgets/WebViewHostDialog.cpp | 5 +- src/slic3r/GUI/Widgets/WebViewHostDialog.hpp | 3 +- src/slic3r/Utils/MacDarkMode.hpp | 1 + src/slic3r/Utils/MacDarkMode.mm | 9 +++ src/slic3r/plugin/host/PluginHostUi.cpp | 5 +- src/slic3r/plugin/host/PluginPages.cpp | 3 +- 14 files changed, 98 insertions(+), 20 deletions(-) diff --git a/src/slic3r/GUI/DockPanel.cpp b/src/slic3r/GUI/DockPanel.cpp index ca5e064b7a..d58ea2bd3d 100644 --- a/src/slic3r/GUI/DockPanel.cpp +++ b/src/slic3r/GUI/DockPanel.cpp @@ -25,8 +25,9 @@ DockPanel::DockPanel(wxWindow* parent, const std::string& html, MessageHandler on_message, CloseHandler on_close, - CloseHandler on_destroyed) - : WebPanel(parent, web_hosting::orca_bridge_script()) + CloseHandler on_destroyed, + const wxString& profile) + : WebPanel(parent, web_hosting::orca_bridge_script(), profile) , m_html(html) , m_on_message(std::move(on_message)) , m_on_close(std::move(on_close)) diff --git a/src/slic3r/GUI/DockPanel.hpp b/src/slic3r/GUI/DockPanel.hpp index d2370acf4e..861aebb598 100644 --- a/src/slic3r/GUI/DockPanel.hpp +++ b/src/slic3r/GUI/DockPanel.hpp @@ -6,6 +6,8 @@ #include #include +#include + class wxWindow; namespace Slic3r { namespace GUI { @@ -27,7 +29,8 @@ public: const std::string& html, MessageHandler on_message, CloseHandler on_close, - CloseHandler on_destroyed); + CloseHandler on_destroyed, + const wxString& profile = wxEmptyString); ~DockPanel() override; // Main thread only. diff --git a/src/slic3r/GUI/WebDialog.cpp b/src/slic3r/GUI/WebDialog.cpp index 97ea52677e..b9a5c74c05 100644 --- a/src/slic3r/GUI/WebDialog.cpp +++ b/src/slic3r/GUI/WebDialog.cpp @@ -27,7 +27,8 @@ WebDialog::WebDialog(wxWindow* parent, SubmitHandler on_submit, CloseHandler on_close, CloseHandler on_destroyed, - long wx_style) + long wx_style, + const wxString& profile) : WebViewHostDialog(parent, wxID_ANY, title, wxDefaultPosition, size, wx_style) , m_html(html) , m_on_message(std::move(on_message)) @@ -37,7 +38,7 @@ WebDialog::WebDialog(wxWindow* parent, { // A tiny bundled bootstrap page brings the webview up; the real plugin HTML // is swapped in via SetPage once the bootstrap finishes loading. - create_webview(web_hosting::BOOTSTRAP_PAGE, title, size, wxSize(320, 240)); + create_webview(web_hosting::BOOTSTRAP_PAGE, title, size, wxSize(320, 240), profile); // Paint the window/webview in the themed background so there is no white // flash before the (transparent) bootstrap page and plugin HTML render. diff --git a/src/slic3r/GUI/WebDialog.hpp b/src/slic3r/GUI/WebDialog.hpp index 650259c2b0..829dc265e3 100644 --- a/src/slic3r/GUI/WebDialog.hpp +++ b/src/slic3r/GUI/WebDialog.hpp @@ -49,7 +49,8 @@ public: SubmitHandler on_submit, CloseHandler on_close, CloseHandler on_destroyed, - long wx_style = wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxMAXIMIZE_BOX | wxRESIZE_BORDER); + long wx_style = wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxMAXIMIZE_BOX | wxRESIZE_BORDER, + const wxString& profile = wxEmptyString); ~WebDialog() override; static void post_message(WebDialog* dialog, const nlohmann::json& data); diff --git a/src/slic3r/GUI/WebPanel.cpp b/src/slic3r/GUI/WebPanel.cpp index 5222636471..c2243cecdf 100644 --- a/src/slic3r/GUI/WebPanel.cpp +++ b/src/slic3r/GUI/WebPanel.cpp @@ -17,7 +17,7 @@ namespace Slic3r { namespace GUI { -WebPanel::WebPanel(wxWindow* parent, const char* bridge_script) +WebPanel::WebPanel(wxWindow* parent, const char* bridge_script, const wxString& profile) : wxPanel(parent, wxID_ANY) { SetBackgroundColour(wxGetApp().get_window_default_clr()); @@ -25,7 +25,7 @@ WebPanel::WebPanel(wxWindow* parent, const char* bridge_script) SetSizer(sizer); // Never null: WebView::CreateWebView substitutes a placeholder view when no backend is available. - m_browser = WebView::CreateWebView(this, web_hosting::bootstrap_url()); + m_browser = WebView::CreateWebView(this, web_hosting::bootstrap_url(), profile); m_browser->SetBackgroundColour(GetBackgroundColour()); m_browser->AddUserScript(wxString::FromUTF8(WebViewHostDialog::theme_user_script())); m_browser->AddUserScript(wxString::FromUTF8(WebViewHostDialog::element_defaults_user_script())); diff --git a/src/slic3r/GUI/WebPanel.hpp b/src/slic3r/GUI/WebPanel.hpp index 8fb8b8d1fc..5f6c877336 100644 --- a/src/slic3r/GUI/WebPanel.hpp +++ b/src/slic3r/GUI/WebPanel.hpp @@ -22,7 +22,7 @@ namespace Slic3r { namespace GUI { class WebPanel : public wxPanel { public: - WebPanel(wxWindow* parent, const char* bridge_script); + WebPanel(wxWindow* parent, const char* bridge_script, const wxString& profile = wxEmptyString); protected: wxWebView* browser() const { return m_browser; } diff --git a/src/slic3r/GUI/Widgets/WebView.cpp b/src/slic3r/GUI/Widgets/WebView.cpp index 498fa4ec43..0e6bd2848d 100644 --- a/src/slic3r/GUI/Widgets/WebView.cpp +++ b/src/slic3r/GUI/Widgets/WebView.cpp @@ -3,6 +3,8 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/Utils/MacDarkMode.hpp" +#include + #include #include @@ -72,6 +74,26 @@ webkit_javascript_result_unref (WebKitJavascriptResult *js_result); } #endif +// Private storage root for an isolated (plugin) profile. Plugin pages must not reach the shared +// default store's cookies/session (e.g. the OrcaCloud login), so each profile gets its own dir. +// macOS isolates with an in-memory store instead (see CreateWebView), so this is unused there. +#if !defined(__WXOSX__) +static wxString isolated_profile_path(const wxString& profile) +{ + wxString safe; + safe.reserve(profile.size()); + for (wxString::const_iterator it = profile.begin(); it != profile.end(); ++it) { + const wxChar c = *it; + const bool keep = (c >= wxT('a') && c <= wxT('z')) || (c >= wxT('A') && c <= wxT('Z')) || + (c >= wxT('0') && c <= wxT('9')) || c == wxT('.') || c == wxT('-') || c == wxT('_'); + safe += keep ? c : wxT('_'); + } + if (safe.empty()) + safe = wxT("default"); + return wxString::FromUTF8((boost::filesystem::path(Slic3r::data_dir()) / "web_profiles" / safe.ToStdString()).string()); +} +#endif + #ifdef __WIN32__ // Run Download and Install in another thread so we don't block the UI thread DWORD DownloadAndInstallWV2RT() { @@ -135,6 +157,9 @@ DWORD DownloadAndInstallWV2RT() { class WebViewEdge : public wxWebViewEdge { public: + WebViewEdge() = default; + explicit WebViewEdge(const wxWebViewConfiguration& config) : wxWebViewEdge(config) {} + bool SetUserAgent(const wxString &userAgent) override { bool dark = userAgent.Contains("dark"); @@ -213,6 +238,11 @@ public: { } + explicit WebViewWebKit(const wxWebViewConfiguration& config) + : wxWebViewWebKit(config) + { + } + ~WebViewWebKit() override { RemoveScriptMessageHandler("wx"); @@ -284,7 +314,7 @@ static WebViewRef *webview_ref(wxWebView *webView) return webView ? static_cast(webView->GetRefData()) : nullptr; } -wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url) +wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url, wxString const & profile) { #if wxUSE_WEBVIEW_EDGE // Check if a fixed version of edge is present in @@ -304,12 +334,36 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url) if (!url2.empty()) { url2 = wxURI(url2).BuildURI(); } BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << ": " << url2.ToUTF8(); + const bool isolated = !profile.IsEmpty(); + #ifdef __WIN32__ - wxWebView* webView = new WebViewEdge; + wxWebView* webView = nullptr; + if (isolated) { + wxWebViewConfiguration config = wxWebView::NewConfiguration(wxWebViewBackendEdge); + config.SetDataPath(isolated_profile_path(profile)); + webView = new WebViewEdge(config); + } else { + webView = new WebViewEdge; + } #elif defined(__WXOSX__) - wxWebView* webView = new WebViewWebKit; + wxWebView* webView = nullptr; + if (isolated) { + // wx has no SetDataPath on macOS; give the view its own in-memory store instead. + wxWebViewConfiguration config = wxWebView::NewConfiguration(wxWebViewBackendWebKit); + Slic3r::GUI::WKWebViewConfiguration_setNonPersistentDataStore(config.GetNativeConfiguration()); + webView = new WebViewWebKit(config); + } else { + webView = new WebViewWebKit; + } #else - auto webView = wxWebView::New(); + wxWebView* webView = nullptr; + if (isolated) { + wxWebViewConfiguration config = wxWebView::NewConfiguration(wxWebViewBackendWebKit); + config.SetDataPath(isolated_profile_path(profile)); + webView = wxWebView::New(config); + } else { + webView = wxWebView::New(); + } #endif if (webView) { webView->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE)); diff --git a/src/slic3r/GUI/Widgets/WebView.hpp b/src/slic3r/GUI/Widgets/WebView.hpp index cdd885f9bd..ddeb2269de 100644 --- a/src/slic3r/GUI/Widgets/WebView.hpp +++ b/src/slic3r/GUI/Widgets/WebView.hpp @@ -14,7 +14,11 @@ wxDECLARE_EVENT(EVT_WEBVIEW_RECREATED, wxCommandEvent); class WebView { public: - static wxWebView *CreateWebView(wxWindow *parent, wxString const &url); + // `profile` empty -> the shared default backend store used by all trusted UI. + // `profile` non-empty -> a private store keyed by `profile`, so this view shares no + // cookies/storage/session with the shared store or any other profile. Used for + // plugin-supplied (untrusted) content. + static wxWebView *CreateWebView(wxWindow *parent, wxString const &url, wxString const &profile = wxEmptyString); #if wxUSE_WEBVIEW_EDGE static bool CheckWebViewRuntime(); static bool DownloadAndInstallWebViewRuntime(); diff --git a/src/slic3r/GUI/Widgets/WebViewHostDialog.cpp b/src/slic3r/GUI/Widgets/WebViewHostDialog.cpp index ea94825eeb..508c38af61 100644 --- a/src/slic3r/GUI/Widgets/WebViewHostDialog.cpp +++ b/src/slic3r/GUI/Widgets/WebViewHostDialog.cpp @@ -172,7 +172,8 @@ WebViewHostDialog::WebViewHostDialog(wxWindow* parent, bool WebViewHostDialog::create_webview(const std::string& resource_path, const wxString& title, const wxSize& dialog_size, - const wxSize& min_size) + const wxSize& min_size, + const wxString& profile) { SetTitle(title); SetMinSize(FromDIP(min_size)); @@ -181,7 +182,7 @@ bool WebViewHostDialog::create_webview(const std::string& resource_path, wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL); // Created empty and loaded below, after the user scripts are added, so the first load already has them. - m_browser = WebView::CreateWebView(this, wxEmptyString); + m_browser = WebView::CreateWebView(this, wxEmptyString, profile); if (m_browser == nullptr) { wxLogError("Could not init m_browser"); delete topsizer; diff --git a/src/slic3r/GUI/Widgets/WebViewHostDialog.hpp b/src/slic3r/GUI/Widgets/WebViewHostDialog.hpp index 361f7ac385..088325c097 100644 --- a/src/slic3r/GUI/Widgets/WebViewHostDialog.hpp +++ b/src/slic3r/GUI/Widgets/WebViewHostDialog.hpp @@ -38,7 +38,8 @@ public: bool create_webview(const std::string& resource_path, const wxString& title, const wxSize& dialog_size = wxSize(820, 660), - const wxSize& min_size = wxSize(640, 640)); + const wxSize& min_size = wxSize(640, 640), + const wxString& profile = wxEmptyString); void load_url(const wxString& url); bool run_script(const wxString& script); diff --git a/src/slic3r/Utils/MacDarkMode.hpp b/src/slic3r/Utils/MacDarkMode.hpp index 8c1cea4663..a5a23fed9e 100644 --- a/src/slic3r/Utils/MacDarkMode.hpp +++ b/src/slic3r/Utils/MacDarkMode.hpp @@ -12,6 +12,7 @@ extern double mac_max_scaling_factor(); extern void set_miniaturizable(void * window); void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*callback)(wxString const &)); void WKWebView_setTransparentBackground(void * web); +void WKWebViewConfiguration_setNonPersistentDataStore(void * configuration); void WKWebView_force_display(void * web); void set_tag_when_enter_full_screen(bool isfullscreen); void set_title_colour_after_set_title(void * window); diff --git a/src/slic3r/Utils/MacDarkMode.mm b/src/slic3r/Utils/MacDarkMode.mm index 7a5e8fb027..86c1777580 100644 --- a/src/slic3r/Utils/MacDarkMode.mm +++ b/src/slic3r/Utils/MacDarkMode.mm @@ -102,6 +102,15 @@ void WKWebView_setTransparentBackground(void * web) [webView registerForDraggedTypes: @[NSFilenamesPboardType]]; } +// Give a WKWebViewConfiguration its own in-memory website data store, so the view's cookies and +// storage neither come from nor leak into the shared default store. Used to isolate plugin web views. +void WKWebViewConfiguration_setNonPersistentDataStore(void * configuration) +{ + WKWebViewConfiguration * config = (WKWebViewConfiguration*)configuration; + if (config) + config.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore]; +} + // Force a WKWebView to re-lay-out and repaint. Needed for chrome-less popups: the window is // transparent, so a WKWebView whose layer has no pending frame leaves the whole window invisible // until the user generates input (scroll/arrow). setNeedsDisplay alone does not always reach the diff --git a/src/slic3r/plugin/host/PluginHostUi.cpp b/src/slic3r/plugin/host/PluginHostUi.cpp index 15fe24b2c5..eb3ec53b08 100644 --- a/src/slic3r/plugin/host/PluginHostUi.cpp +++ b/src/slic3r/plugin/host/PluginHostUi.cpp @@ -390,7 +390,8 @@ py::object ui_create_window(const std::string& html, const std::string& title, i auto* dlg = new GUI::WebDialog(ui_parent(), wxString::FromUTF8(title), html, wxSize(w, h), std::move(msg_adapter), std::move(submit_adapter), - std::move(on_close), std::move(on_destroyed), PLUGIN_WX_STYLE); + std::move(on_close), std::move(on_destroyed), PLUGIN_WX_STYLE, + wxString::FromUTF8(plugin_key)); UiRegistry::instance().bind(new_id, dlg, plugin_key); if (modal) { dlg->ShowModal(); @@ -487,7 +488,7 @@ py::object ui_create_dock_panel(const std::string& html, const std::string& titl auto on_destroyed = [new_id]() { UiRegistry::instance().remove(new_id); }; auto* panel = new GUI::DockPanel(plater, html, std::move(msg_adapter), make_close_adapter(close_holder), - std::move(on_destroyed)); + std::move(on_destroyed), wxString::FromUTF8(plugin_key)); UiRegistry::instance().bind(new_id, panel, plugin_key); plater->add_dock_pane(panel, GUI::plugin_pane_name(plugin_key, title), wxString::FromUTF8(title), dock, wxSize(w, h), [panel]() { panel->fire_close(); }); diff --git a/src/slic3r/plugin/host/PluginPages.cpp b/src/slic3r/plugin/host/PluginPages.cpp index cf4ed6a6a8..961da5c379 100644 --- a/src/slic3r/plugin/host/PluginPages.cpp +++ b/src/slic3r/plugin/host/PluginPages.cpp @@ -77,7 +77,8 @@ constexpr char PLUGIN_PAGE_BRIDGE_JS[] = R"JS( } // namespace PluginPage::PluginPage(wxWindow* parent, std::shared_ptr capability) - : GUI::WebPanel(parent, PLUGIN_PAGE_BRIDGE_JS) + : GUI::WebPanel(parent, PLUGIN_PAGE_BRIDGE_JS, + wxString::FromUTF8(capability ? capability->identity().plugin_key : std::string())) , m_cap(std::move(capability)) , m_lifetime(std::make_shared>(this)) {