KiCad PCB EDA Suite
Loading...
Searching...
No Matches
webview_panel.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <build_version.h>
22#include <wx/sizer.h>
23#include <wx/webviewarchivehandler.h>
24#include <wx/webviewfshandler.h>
25#include <wx/utils.h>
26#include <wx/log.h>
27
28WEBVIEW_PANEL::WEBVIEW_PANEL( wxWindow* aParent, wxWindowID aId, const wxPoint& aPos,
29 const wxSize& aSize, const int aStyle )
30 : wxPanel( aParent, aId, aPos, aSize, aStyle ),
31 m_initialized( false ),
32 m_browser( wxWebView::New() )
33{
34 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
35
36 if( !wxGetEnv( wxT( "WEBKIT_DISABLE_COMPOSITING_MODE" ), nullptr ) )
37 {
38 wxSetEnv( wxT( "WEBKIT_DISABLE_COMPOSITING_MODE" ), wxT( "1" ) );
39 }
40
41#ifdef __WXMAC__
42 m_browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewArchiveHandler( "wxfs" ) ) );
43 m_browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewFSHandler( "memory" ) ) );
44#endif
45 m_browser->SetUserAgent( wxString::Format( "KiCad/%s WebView/%s", GetMajorMinorPatchVersion(), wxGetOsDescription() ) );
46 m_browser->Create( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize );
47 sizer->Add( m_browser, 1, wxEXPAND );
48 SetSizer( sizer );
49
50#ifndef __WXMAC__
51 m_browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewArchiveHandler( "wxfs" ) ) );
52 m_browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewFSHandler( "memory" ) ) );
53#endif
54
55 Bind( wxEVT_WEBVIEW_NAVIGATING, &WEBVIEW_PANEL::OnNavigationRequest, this, m_browser->GetId() );
56 Bind( wxEVT_WEBVIEW_NEWWINDOW, &WEBVIEW_PANEL::OnNewWindow, this, m_browser->GetId() );
57 Bind( wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WEBVIEW_PANEL::OnScriptMessage, this, m_browser->GetId() );
58 Bind( wxEVT_WEBVIEW_SCRIPT_RESULT, &WEBVIEW_PANEL::OnScriptResult, this, m_browser->GetId() );
59 Bind( wxEVT_WEBVIEW_ERROR, &WEBVIEW_PANEL::OnError, this, m_browser->GetId() );
60 Bind( wxEVT_WEBVIEW_LOADED, &WEBVIEW_PANEL::OnWebViewLoaded, this, m_browser->GetId() );
61}
62
66
67void WEBVIEW_PANEL::LoadURL( const wxString& aURL )
68{
69 if( aURL.starts_with( "file:/" ) && !aURL.starts_with( "file:///" ) )
70 {
71 wxString new_url = wxString( "file:///" ) + aURL.AfterFirst( '/' );
72 m_browser->LoadURL( new_url );
73 return;
74 }
75
76 if( !aURL.StartsWith( "http://" ) && !aURL.StartsWith( "https://" ) && !aURL.StartsWith( "file://" ) )
77 {
78 wxLogError( "Invalid URL: %s", aURL );
79 return;
80 }
81
82 m_browser->LoadURL( aURL );
83}
84
85void WEBVIEW_PANEL::SetPage( const wxString& aHtmlContent )
86{
87 m_browser->SetPage( aHtmlContent, "file://" );
88}
89
90bool WEBVIEW_PANEL::AddMessageHandler( const wxString& aName, MESSAGE_HANDLER aHandler )
91{
92 m_msgHandlers.emplace( aName, std::move(aHandler) );
93 return true;
94}
95
100
101void WEBVIEW_PANEL::OnNavigationRequest( wxWebViewEvent& aEvt )
102{
103 // Default behavior: open external links in the system browser
104 bool isExternal = aEvt.GetURL().StartsWith( "http://" ) || aEvt.GetURL().StartsWith( "https://" );
105 if( isExternal )
106 {
107 wxLaunchDefaultBrowser( aEvt.GetURL() );
108 aEvt.Veto();
109 }
110}
111
112void WEBVIEW_PANEL::OnWebViewLoaded( wxWebViewEvent& aEvt )
113{
114 if( !m_initialized )
115 {
116 // Defer handler registration to avoid running during modal dialog/yield
117 CallAfter([this]() {
118 static bool handler_added_inner = false;
119 if (!handler_added_inner) {
120
121 for( const auto& handler : m_msgHandlers )
122 {
123 if( !m_browser->AddScriptMessageHandler( handler.first ) )
124 {
125 wxLogDebug( "Could not add script message handler %s", handler.first );
126 }
127 }
128
129 handler_added_inner = true;
130 }
131
132 // Inject navigation hook for SPA/JS navigation to prevent webkit crashing without new window
133 m_browser->AddUserScript(R"(
134 (function() {
135 // Change window.open to navigate in the same window
136 window.open = function(url) { if (url) window.location.href = url; return null; };
137 window.showModalDialog = function() { return null; };
138
139 if (window.external && window.external.invoke) {
140 function notifyHost() {
141 window.external.invoke('navigation:' + window.location.href);
142 }
143 window.addEventListener('popstate', notifyHost);
144 window.addEventListener('pushstate', notifyHost);
145 window.addEventListener('replacestate', notifyHost);
146 ['pushState', 'replaceState'].forEach(function(type) {
147 var orig = history[type];
148 history[type] = function() {
149 var rv = orig.apply(this, arguments);
150 window.dispatchEvent(new Event(type.toLowerCase()));
151 return rv;
152 };
153 });
154 }
155 })();
156 )");
157 });
158
159 m_initialized = true;
160 }
161}
162
163void WEBVIEW_PANEL::OnNewWindow( wxWebViewEvent& aEvt )
164{
165 m_browser->LoadURL( aEvt.GetURL() );
166 aEvt.Veto(); // Prevent default behavior of opening a new window
167 wxLogTrace( "webview", "New window requested for URL: %s", aEvt.GetURL() );
168 wxLogTrace( "webview", "Target: %s", aEvt.GetTarget() );
169 wxLogTrace( "webview", "Action flags: %d", static_cast<int>(aEvt.GetNavigationAction()) );
170 wxLogTrace( "webview", "Message handler: %s", aEvt.GetMessageHandler() );
171}
172
173void WEBVIEW_PANEL::OnScriptMessage( wxWebViewEvent& aEvt )
174{
175 wxLogTrace( "webview", "Script message received: %s for handler %s", aEvt.GetString(), aEvt.GetMessageHandler() );
176
177 if( aEvt.GetMessageHandler().IsEmpty() )
178 {
179 wxLogDebug( "No message handler specified for script message: %s", aEvt.GetString() );
180 return;
181 }
182
183 auto it = m_msgHandlers.find( aEvt.GetMessageHandler() );
184 if( it == m_msgHandlers.end() )
185 {
186 wxLogDebug( "No handler registered for message: %s", aEvt.GetMessageHandler() );
187 return;
188 }
189
190 // Call the registered handler with the message
191 wxLogTrace( "webview", "Calling handler for message: %s", aEvt.GetMessageHandler() );
192 it->second( aEvt.GetString() );
193}
194
195void WEBVIEW_PANEL::OnScriptResult( wxWebViewEvent& aEvt )
196{
197 if( aEvt.IsError() )
198 wxLogDebug( "Async script execution failed: %s", aEvt.GetString() );
199}
200
201void WEBVIEW_PANEL::OnError( wxWebViewEvent& aEvt )
202{
203 wxLogDebug( "WebView error: %s", aEvt.GetString() );
204}
wxString GetMajorMinorPatchVersion()
Get the major, minor and patch version in a string major.minor.patch This is extracted by CMake from ...
WEBVIEW_PANEL(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, const int style=0)
void OnScriptMessage(wxWebViewEvent &evt)
void OnNewWindow(wxWebViewEvent &evt)
std::map< wxString, MESSAGE_HANDLER > m_msgHandlers
void SetPage(const wxString &htmlContent)
void OnWebViewLoaded(wxWebViewEvent &evt)
void OnNavigationRequest(wxWebViewEvent &evt)
void ClearMessageHandlers()
bool AddMessageHandler(const wxString &name, MESSAGE_HANDLER handler)
std::function< void(const wxString &)> MESSAGE_HANDLER
void OnScriptResult(wxWebViewEvent &evt)
void OnError(wxWebViewEvent &evt)
~WEBVIEW_PANEL() override
void LoadURL(const wxString &url)
wxWebView * m_browser