Files
kicad-source-mirror/common/widgets/webview_panel.cpp
T
Alex Shvartzkop 1082ce08da Modernize CSS in template selector dialog.
Hope this works on GTK.
2026-02-03 10:13:47 +03:00

291 lines
10 KiB
C++

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <build_version.h>
#include <tool/tool_manager.h>
#include <tool/tool_base.h>
#include <widgets/webview_panel.h>
#include <wx/sizer.h>
#include <wx/webviewarchivehandler.h>
#include <wx/webviewfshandler.h>
#include <wx/utils.h>
#include <wx/log.h>
#if wxUSE_WEBVIEW_EDGE
// note webview2 is available on Windows, but not on MINGW (only webview available)
#include <webview2EnvironmentOptions.h>
#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<wxWebViewHandler>( new wxWebViewArchiveHandler( "wxfs" ) ) );
m_browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( 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<wxWebViewHandler>( new wxWebViewArchiveHandler( "wxfs" ) ) );
m_browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( 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<int>(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() );
}