/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright The KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #if wxUSE_WEBVIEW_EDGE // note webview2 is available on Windows, but not on MINGW (only webview available) #include #endif WEBVIEW_PANEL::WEBVIEW_PANEL( wxWindow* aParent, wxWindowID aId, const wxPoint& aPos, const wxSize& aSize, const int aStyle, TOOL_MANAGER* aToolManager, TOOL_BASE* aTool ) : wxPanel( aParent, aId, aPos, aSize, aStyle ), m_initialized( false ), m_handleExternalLinks( false ), m_loadError( false ), m_loadedEventBound( false ), m_browser( nullptr ), m_toolManager( aToolManager ), m_tool( aTool ) { wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL ); if( !wxGetEnv( wxT( "WEBKIT_DISABLE_COMPOSITING_MODE" ), nullptr ) ) { wxSetEnv( wxT( "WEBKIT_DISABLE_COMPOSITING_MODE" ), wxT( "1" ) ); } #if wxCHECK_VERSION( 3, 3, 0 ) wxWebViewConfiguration config = wxWebView::NewConfiguration(); m_backend = config.GetBackend(); #if wxUSE_WEBVIEW_EDGE if( m_backend == wxWebViewBackendEdge ) { ICoreWebView2EnvironmentOptions* webViewOptions = (ICoreWebView2EnvironmentOptions*) config.GetNativeConfiguration(); // Disable gesture navigation features webViewOptions->put_AdditionalBrowserArguments( L"--disable-features=msEdgeMouseGestureSupported," L"msEdgeMouseGestureDefaultEnabled,OverscrollHistoryNavigation " L"--enable-features=kEdgeMouseGestureDisabledInCN" ); } #endif m_browser = wxWebView::New( config ); #else m_browser = wxWebView::New(); m_backend = wxWebViewBackendDefault; #endif #ifdef __WXMAC__ m_browser->RegisterHandler( wxSharedPtr( new wxWebViewArchiveHandler( "wxfs" ) ) ); m_browser->RegisterHandler( wxSharedPtr( new wxWebViewFSHandler( "memory" ) ) ); #endif m_browser->SetUserAgent( wxString::Format( "KiCad/%s WebView/%s", GetMajorMinorPatchVersion(), wxGetOsDescription() ) ); m_browser->Create( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize ); sizer->Add( m_browser, 1, wxEXPAND ); SetSizer( sizer ); #ifndef __WXMAC__ m_browser->RegisterHandler( wxSharedPtr( new wxWebViewArchiveHandler( "wxfs" ) ) ); m_browser->RegisterHandler( wxSharedPtr( new wxWebViewFSHandler( "memory" ) ) ); #endif Bind( wxEVT_WEBVIEW_NAVIGATING, &WEBVIEW_PANEL::OnNavigationRequest, this, m_browser->GetId() ); Bind( wxEVT_WEBVIEW_NEWWINDOW, &WEBVIEW_PANEL::OnNewWindow, this, m_browser->GetId() ); Bind( wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WEBVIEW_PANEL::OnScriptMessage, this, m_browser->GetId() ); Bind( wxEVT_WEBVIEW_SCRIPT_RESULT, &WEBVIEW_PANEL::OnScriptResult, this, m_browser->GetId() ); Bind( wxEVT_WEBVIEW_ERROR, &WEBVIEW_PANEL::OnError, this, m_browser->GetId() ); } WEBVIEW_PANEL::~WEBVIEW_PANEL() { } void WEBVIEW_PANEL::BindLoadedEvent() { if( m_loadedEventBound ) return; Bind( wxEVT_WEBVIEW_LOADED, &WEBVIEW_PANEL::OnWebViewLoaded, this, m_browser->GetId() ); m_loadedEventBound = true; } void WEBVIEW_PANEL::LoadURL( const wxString& aURL ) { wxLogTrace( "webview", "Loading URL: %s", aURL ); if( aURL.starts_with( "file:/" ) && !aURL.starts_with( "file:///" ) ) { wxString new_url = wxString( "file:///" ) + aURL.AfterFirst( '/' ); m_browser->LoadURL( new_url ); return; } if( !aURL.StartsWith( "http://" ) && !aURL.StartsWith( "https://" ) && !aURL.StartsWith( "file://" ) ) { wxLogError( "Invalid URL: %s", aURL ); return; } m_browser->LoadURL( aURL ); } void WEBVIEW_PANEL::SetPage( const wxString& aHtmlContent ) { wxLogTrace( "webview", "Setting page content" ); m_browser->SetPage( aHtmlContent, "file://" ); } bool WEBVIEW_PANEL::AddMessageHandler( const wxString& aName, MESSAGE_HANDLER aHandler ) { wxLogTrace( "webview", "Adding message handler for: %s", aName ); auto it = m_msgHandlers.find( aName ); if( it != m_msgHandlers.end() ) { it->second = std::move( aHandler ); return true; } m_msgHandlers.emplace( aName, std::move( aHandler ) ); if( m_initialized ) { if( !m_browser->AddScriptMessageHandler( aName ) ) wxLogTrace( "webview", "Could not add script message handler %s", aName ); } return true; } void WEBVIEW_PANEL::ClearMessageHandlers() { wxLogTrace( "webview", "Clearing all message handlers" ); for( const auto& handler : m_msgHandlers ) m_browser->RemoveScriptMessageHandler( handler.first ); m_msgHandlers.clear(); } void WEBVIEW_PANEL::OnNavigationRequest( wxWebViewEvent& aEvt ) { m_loadError = false; wxLogTrace( "webview", "Navigation request to URL: %s", aEvt.GetURL() ); // Default behavior: open external links in the system browser bool isExternal = aEvt.GetURL().StartsWith( "http://" ) || aEvt.GetURL().StartsWith( "https://" ); if( isExternal && !m_handleExternalLinks ) { wxLogTrace( "webview", "Opening external URL in system browser: %s", aEvt.GetURL() ); wxLaunchDefaultBrowser( aEvt.GetURL() ); aEvt.Veto(); } } void WEBVIEW_PANEL::OnWebViewLoaded( wxWebViewEvent& aEvt ) { if( !m_initialized ) { // Defer handler registration to avoid running during modal dialog/yield auto initFunc = [this]() { for( const auto& handler : m_msgHandlers ) { if( !m_browser->AddScriptMessageHandler( handler.first ) ) { wxLogTrace( "webview", "Could not add script message handler %s", handler.first ); } } // Inject navigation hook for SPA/JS navigation to prevent webkit crashing without new window m_browser->AddUserScript( R"( (function() { // Change window.open to navigate in the same window window.open = function(url) { if (url) window.location.href = url; return null; }; window.showModalDialog = function() { return null; }; if (window.external && window.external.invoke) { function notifyHost() { window.external.invoke('navigation:' + window.location.href); } window.addEventListener('popstate', notifyHost); window.addEventListener('pushstate', notifyHost); window.addEventListener('replacestate', notifyHost); ['pushState', 'replaceState'].forEach(function(type) { var orig = history[type]; history[type] = function() { var rv = orig.apply(this, arguments); window.dispatchEvent(new Event(type.toLowerCase())); return rv; }; }); } })(); )" ); }; if( m_toolManager && m_tool ) m_toolManager->RunMainStack( m_tool, initFunc ); else CallAfter( initFunc ); m_initialized = true; } aEvt.Skip(); } void WEBVIEW_PANEL::OnNewWindow( wxWebViewEvent& aEvt ) { m_browser->LoadURL( aEvt.GetURL() ); aEvt.Veto(); // Prevent default behavior of opening a new window wxLogTrace( "webview", "New window requested for URL: %s", aEvt.GetURL() ); wxLogTrace( "webview", "Target: %s", aEvt.GetTarget() ); wxLogTrace( "webview", "Action flags: %d", static_cast(aEvt.GetNavigationAction()) ); wxLogTrace( "webview", "Message handler: %s", aEvt.GetMessageHandler() ); } void WEBVIEW_PANEL::OnScriptMessage( wxWebViewEvent& aEvt ) { wxLogTrace( "webview", "Script message received: %s for handler %s", aEvt.GetString(), aEvt.GetMessageHandler() ); wxString handler = aEvt.GetMessageHandler(); handler.Trim(true).Trim(false); if( handler.IsEmpty() ) { for( auto handlerPair : m_msgHandlers ) { wxLogTrace( "webview", "No handler specified, trying: %s", handlerPair.first ); handlerPair.second( aEvt.GetString() ); } return; } auto it = m_msgHandlers.find( handler ); if( it == m_msgHandlers.end() ) { wxLogTrace( "webview", "No handler registered for message: %s", handler ); return; } // Call the registered handler with the message wxLogTrace( "webview", "Calling handler for message: %s", handler ); it->second( aEvt.GetString() ); } void WEBVIEW_PANEL::OnScriptResult( wxWebViewEvent& aEvt ) { if( aEvt.IsError() ) wxLogTrace( "webview", "Async script execution failed: %s", aEvt.GetString() ); } void WEBVIEW_PANEL::OnError( wxWebViewEvent& aEvt ) { m_loadError = true; wxLogTrace( "webview", "WebView error: %s", aEvt.GetString() ); }