#include "PrinterWebView.hpp" #include "I18N.hpp" #include "PrinterWebViewHandler.hpp" #include "slic3r/GUI/PrinterWebView.hpp" #include "slic3r/GUI/wxExtensions.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/MainFrame.hpp" #include "libslic3r_version.h" #include #include #include #include #include #include #ifdef __linux__ #include #endif namespace Slic3r { namespace GUI { #ifdef __linux__ // Workaround for #7210: WebKitGTK crashes on vue-resize's hidden probe used by // older Fluidd/Mainsail pages. Swap that for a
shim at appendChild time // and bridge resize events through a fake contentDocument.defaultView so vue-resize keeps // working. Workaround proposed by @VittC. static void inject_vue_resize_workaround(wxWebView *webView) { webView->AddUserScript( "(function() {" " 'use strict';" " function isVueResizeObject(el) {" " return el && el.tagName === 'OBJECT'" " && el.type === 'text/html'" " && el.getAttribute('aria-hidden') === 'true'" " && el.getAttribute('tabindex') === '-1';" " }" " function isResizeObserverParent(p) {" " return p && p.classList && p.classList.contains('resize-observer');" " }" " function makeShim(orig, parentForRO) {" " var shim = document.createElement('div');" " shim.setAttribute('aria-hidden', 'true');" " shim.setAttribute('tabindex', '-1');" " shim.style.display = 'none';" " var fakeWin = document.createElement('div');" " var ro = null;" " var origRemoveEL = fakeWin.removeEventListener.bind(fakeWin);" " fakeWin.removeEventListener = function(type, fn, opts) {" " origRemoveEL(type, fn, opts);" " if (type === 'resize' && ro) { ro.disconnect(); ro = null; }" " };" " Object.defineProperty(shim, 'contentDocument', {" " configurable: true," " get: function() { return { defaultView: fakeWin }; }" " });" " Object.defineProperty(shim, 'contentWindow', {" " configurable: true," " get: function() { return fakeWin; }" " });" " if (typeof orig.onload === 'function') { shim.onload = orig.onload; }" " queueMicrotask(function() {" " if (parentForRO && typeof ResizeObserver !== 'undefined') {" " ro = new ResizeObserver(function() {" " fakeWin.dispatchEvent(new Event('resize'));" " });" " ro.observe(parentForRO);" " }" " if (typeof shim.onload === 'function') {" " try { shim.onload(new Event('load')); } catch (e) {}" " }" " shim.dispatchEvent(new Event('load'));" " });" " return shim;" " }" " var origAppend = Node.prototype.appendChild;" " Node.prototype.appendChild = function(child) {" " if (isResizeObserverParent(this) && isVueResizeObject(child)) {" " return origAppend.call(this, makeShim(child, this));" " }" " return origAppend.call(this, child);" " };" " var origInsertBefore = Node.prototype.insertBefore;" " Node.prototype.insertBefore = function(child, ref) {" " if (isResizeObserverParent(this) && isVueResizeObject(child)) {" " return origInsertBefore.call(this, makeShim(child, this), ref);" " }" " return origInsertBefore.call(this, child, ref);" " };" " console.log('[vr-fix] vue-resize WebKitGTK patch active');" "})();", wxWEBVIEW_INJECT_AT_DOCUMENT_START ); } #endif PrinterWebView::PrinterWebView(wxWindow *parent) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) , m_browser(nullptr) , m_zoomFactor(100) , m_apikey() , m_apikey_sent(false) , m_url_deferred() , m_handler(std::make_unique(*this)) { wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL); // Create the webview m_browser = WebView::CreateWebView(this, ""); if (m_browser == nullptr) { wxLogError("Could not init m_browser"); return; } #ifdef __linux__ inject_vue_resize_workaround(m_browser); auto cookiesPath = boost::filesystem::path(data_dir() + "/cache/cookies.db"); auto wv = static_cast(m_browser->GetNativeBackend()); auto wv_ctx = webkit_web_view_get_context(wv); auto cookieManager = webkit_web_context_get_cookie_manager(wv_ctx); webkit_cookie_manager_set_persistent_storage(cookieManager, cookiesPath.c_str(), WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE); #endif m_browser->Bind(wxEVT_WEBVIEW_ERROR, &PrinterWebView::OnError, this); m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrinterWebView::OnLoaded, this); m_browser->Bind(wxEVT_WEBVIEW_NEWWINDOW, &PrinterWebView::OnNewWindow, this); m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PrinterWebView::OnScriptMessage, this); SetSizer(topsizer); topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); update_mode(); // Log backend information /* m_browser->GetUserAgent() may lead crash if (wxGetApp().get_mode() == comDevelop) { wxLogMessage(wxWebView::GetBackendVersionInfo().ToString()); wxLogMessage("Backend: %s Version: %s", m_browser->GetClassInfo()->GetClassName(), wxWebView::GetBackendVersionInfo().ToString()); wxLogMessage("User Agent: %s", m_browser->GetUserAgent()); } */ //Connect the idle events Bind(wxEVT_CLOSE_WINDOW, &PrinterWebView::OnClose, this); } PrinterWebView::~PrinterWebView() { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Start"; SetEvtHandlerEnabled(false); m_handler.reset(); // Destroy the webview if(m_browser){ m_browser->Destroy(); m_browser = nullptr; } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " End"; } void PrinterWebView::load_url(wxString& url, wxString apikey) { // this->Show(); // this->Raise(); if (m_browser == nullptr) return; m_apikey = apikey; m_apikey_sent = false; m_handler = create_printer_webview_handler(*this); if (this->IsShown()) { //ORCA: m_url_deferred will be cleared on load success //m_url_deferred.clear(); m_browser->LoadURL(url); } else { m_url_deferred = url; } //m_browser->SetFocus(); UpdateState(); } bool PrinterWebView::Show(bool show) { if (show && !m_url_deferred.empty()) { m_browser->LoadURL(m_url_deferred); //ORCA: m_url_deferred will be cleared on load success //m_url_deferred.clear(); } return wxPanel::Show(show); } void PrinterWebView::reload() { m_browser->Reload(); } void PrinterWebView::update_mode() { m_browser->EnableAccessToDevTools(wxGetApp().app_config->get_bool("developer_mode")); } /** * Method that retrieves the current state from the web control and updates the * GUI the reflect this current state. */ void PrinterWebView::UpdateState() { // SetTitle(m_browser->GetCurrentTitle()); } void PrinterWebView::OnClose(wxCloseEvent& evt) { this->Hide(); } void PrinterWebView::SendAPIKey() { if (m_apikey_sent || m_apikey.IsEmpty()) return; m_apikey_sent = true; wxString script = wxString::Format(R"( // Check if window.fetch exists before overriding if (window.fetch) { const originalFetch = window.fetch; window.fetch = function(input, init = {}) { init.headers = init.headers || {}; init.headers['X-API-Key'] = '%s'; return originalFetch(input, init); }; } )", m_apikey); m_browser->RemoveAllUserScripts(); #ifdef __linux__ // Re-inject the vue-resize/WebKitGTK workaround that RemoveAllUserScripts just cleared. inject_vue_resize_workaround(m_browser); #endif m_browser->AddUserScript(script); m_browser->Reload(); } void PrinterWebView::OnError(wxWebViewEvent &evt) { auto e = "unknown error"; switch (evt.GetInt()) { case wxWEBVIEW_NAV_ERR_CONNECTION: e = "wxWEBVIEW_NAV_ERR_CONNECTION"; break; case wxWEBVIEW_NAV_ERR_CERTIFICATE: e = "wxWEBVIEW_NAV_ERR_CERTIFICATE"; break; case wxWEBVIEW_NAV_ERR_AUTH: e = "wxWEBVIEW_NAV_ERR_AUTH"; break; case wxWEBVIEW_NAV_ERR_SECURITY: e = "wxWEBVIEW_NAV_ERR_SECURITY"; break; case wxWEBVIEW_NAV_ERR_NOT_FOUND: e = "wxWEBVIEW_NAV_ERR_NOT_FOUND"; break; case wxWEBVIEW_NAV_ERR_REQUEST: e = "wxWEBVIEW_NAV_ERR_REQUEST"; break; case wxWEBVIEW_NAV_ERR_USER_CANCELLED: e = "wxWEBVIEW_NAV_ERR_USER_CANCELLED"; break; case wxWEBVIEW_NAV_ERR_OTHER: e = "wxWEBVIEW_NAV_ERR_OTHER"; break; } BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": error loading page %1% %2% %3% %4%") %evt.GetURL() %evt.GetTarget() %e %evt.GetString(); } void PrinterWebView::OnLoaded(wxWebViewEvent& evt) { if (evt.GetURL().IsEmpty()) return; //ORCA: url loaded successfully, safe to clear m_url_deferred.clear(); SendAPIKey(); if (m_handler != nullptr) { m_handler->on_loaded(evt); return; } } void PrinterWebView::OnNewWindow(wxWebViewEvent& evt) { const wxString url = evt.GetURL(); if (!url.empty()) wxLaunchDefaultBrowser(url); evt.Veto(); } void PrinterWebView::OnScriptMessage(wxWebViewEvent& evt) { if (m_handler != nullptr) m_handler->on_script_message(evt); } } // GUI } // Slic3r