325 lines
10 KiB
C++
325 lines
10 KiB
C++
#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 <boost/filesystem/path.hpp>
|
|
#include <wx/sizer.h>
|
|
#include <wx/string.h>
|
|
#include <wx/toolbar.h>
|
|
|
|
#include <slic3r/GUI/Widgets/WebView.hpp>
|
|
#include <wx/webview.h>
|
|
|
|
#ifdef __linux__
|
|
#include <webkit2/webkit2.h>
|
|
#endif
|
|
|
|
namespace Slic3r {
|
|
namespace GUI {
|
|
|
|
#ifdef __linux__
|
|
// Workaround for #7210: WebKitGTK crashes on vue-resize's hidden <object> probe used by
|
|
// older Fluidd/Mainsail pages. Swap that <object> for a <div> 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<PrinterWebViewHandler>(*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<WebKitWebView*>(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 _WIN32
|
|
// RemoveAllUserScripts causes WebView2 to forget about our script message handler,
|
|
// so re-add it here.
|
|
m_browser->RemoveScriptMessageHandler("wx");
|
|
m_browser->AddScriptMessageHandler("wx");
|
|
#endif
|
|
|
|
#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
|