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>
21
22#include <tool/tool_manager.h>
23#include <tool/tool_base.h>
24
26#include <wx/sizer.h>
27#include <wx/webviewarchivehandler.h>
28#include <wx/webviewfshandler.h>
29#include <wx/utils.h>
30#include <wx/log.h>
31
32#if wxUSE_WEBVIEW_EDGE
33 // note webview2 is available on Windows, but not on MINGW (only webview available)
34 #include <webview2EnvironmentOptions.h>
35#endif
36
37
38WEBVIEW_PANEL::WEBVIEW_PANEL( wxWindow* aParent, wxWindowID aId, const wxPoint& aPos, const wxSize& aSize,
39 const int aStyle, TOOL_MANAGER* aToolManager, TOOL_BASE* aTool ) :
40 wxPanel( aParent, aId, aPos, aSize, aStyle ),
41 m_initialized( false ),
42 m_handleExternalLinks( false ),
43 m_loadError( false ),
44 m_loadedEventBound( false ),
45 m_browser( nullptr ),
46 m_toolManager( aToolManager ),
47 m_tool( aTool )
48{
49 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
50
51 if( !wxGetEnv( wxT( "WEBKIT_DISABLE_COMPOSITING_MODE" ), nullptr ) )
52 {
53 wxSetEnv( wxT( "WEBKIT_DISABLE_COMPOSITING_MODE" ), wxT( "1" ) );
54 }
55
56#if wxCHECK_VERSION( 3, 3, 0 )
57 wxWebViewConfiguration config = wxWebView::NewConfiguration();
58 m_backend = config.GetBackend();
59
60#if wxUSE_WEBVIEW_EDGE
61 if( m_backend == wxWebViewBackendEdge )
62 {
63 ICoreWebView2EnvironmentOptions* webViewOptions =
64 (ICoreWebView2EnvironmentOptions*) config.GetNativeConfiguration();
65
66 // Disable gesture navigation features
67 webViewOptions->put_AdditionalBrowserArguments( L"--disable-features=msEdgeMouseGestureSupported,"
68 L"msEdgeMouseGestureDefaultEnabled,OverscrollHistoryNavigation "
69 L"--enable-features=kEdgeMouseGestureDisabledInCN" );
70 }
71#endif
72
73 m_browser = wxWebView::New( config );
74#else
75 m_browser = wxWebView::New();
76 m_backend = wxWebViewBackendDefault;
77#endif
78
79#ifdef __WXMAC__
80 m_browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewArchiveHandler( "wxfs" ) ) );
81 m_browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewFSHandler( "memory" ) ) );
82#endif
83 m_browser->SetUserAgent( wxString::Format( "KiCad/%s WebView/%s", GetMajorMinorPatchVersion(), wxGetOsDescription() ) );
84 m_browser->Create( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize );
85 sizer->Add( m_browser, 1, wxEXPAND );
86 SetSizer( sizer );
87
88#ifndef __WXMAC__
89 m_browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewArchiveHandler( "wxfs" ) ) );
90 m_browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewFSHandler( "memory" ) ) );
91#endif
92
93 Bind( wxEVT_WEBVIEW_NAVIGATING, &WEBVIEW_PANEL::OnNavigationRequest, this, m_browser->GetId() );
94 Bind( wxEVT_WEBVIEW_NEWWINDOW, &WEBVIEW_PANEL::OnNewWindow, this, m_browser->GetId() );
95 Bind( wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WEBVIEW_PANEL::OnScriptMessage, this, m_browser->GetId() );
96 Bind( wxEVT_WEBVIEW_SCRIPT_RESULT, &WEBVIEW_PANEL::OnScriptResult, this, m_browser->GetId() );
97 Bind( wxEVT_WEBVIEW_ERROR, &WEBVIEW_PANEL::OnError, this, m_browser->GetId() );
98}
99
103
105{
107 return;
108
109 Bind( wxEVT_WEBVIEW_LOADED, &WEBVIEW_PANEL::OnWebViewLoaded, this, m_browser->GetId() );
110 m_loadedEventBound = true;
111}
112
113void WEBVIEW_PANEL::LoadURL( const wxString& aURL )
114{
115 wxLogTrace( "webview", "Loading URL: %s", aURL );
116
117 if( aURL.starts_with( "file:/" ) && !aURL.starts_with( "file:///" ) )
118 {
119 wxString new_url = wxString( "file:///" ) + aURL.AfterFirst( '/' );
120 m_browser->LoadURL( new_url );
121 return;
122 }
123
124 if( !aURL.StartsWith( "http://" ) && !aURL.StartsWith( "https://" ) && !aURL.StartsWith( "file://" ) )
125 {
126 wxLogError( "Invalid URL: %s", aURL );
127 return;
128 }
129
130 m_browser->LoadURL( aURL );
131}
132
133void WEBVIEW_PANEL::SetPage( const wxString& aHtmlContent )
134{
135 wxLogTrace( "webview", "Setting page content" );
136 m_browser->SetPage( aHtmlContent, "file://" );
137}
138
139bool WEBVIEW_PANEL::AddMessageHandler( const wxString& aName, MESSAGE_HANDLER aHandler )
140{
141 wxLogTrace( "webview", "Adding message handler for: %s", aName );
142 auto it = m_msgHandlers.find( aName );
143
144 if( it != m_msgHandlers.end() )
145 {
146 it->second = std::move( aHandler );
147 return true;
148 }
149
150 m_msgHandlers.emplace( aName, std::move( aHandler ) );
151
152 if( m_initialized )
153 {
154 if( !m_browser->AddScriptMessageHandler( aName ) )
155 wxLogTrace( "webview", "Could not add script message handler %s", aName );
156 }
157
158 return true;
159}
160
162{
163 wxLogTrace( "webview", "Clearing all message handlers" );
164
165 for( const auto& handler : m_msgHandlers )
166 m_browser->RemoveScriptMessageHandler( handler.first );
167
168 m_msgHandlers.clear();
169}
170
171void WEBVIEW_PANEL::OnNavigationRequest( wxWebViewEvent& aEvt )
172{
173 m_loadError = false;
174 wxLogTrace( "webview", "Navigation request to URL: %s", aEvt.GetURL() );
175 // Default behavior: open external links in the system browser
176 bool isExternal = aEvt.GetURL().StartsWith( "http://" ) || aEvt.GetURL().StartsWith( "https://" );
177
178 if( isExternal && !m_handleExternalLinks )
179 {
180 wxLogTrace( "webview", "Opening external URL in system browser: %s", aEvt.GetURL() );
181 wxLaunchDefaultBrowser( aEvt.GetURL() );
182 aEvt.Veto();
183 }
184}
185
186void WEBVIEW_PANEL::OnWebViewLoaded( wxWebViewEvent& aEvt )
187{
188 if( !m_initialized )
189 {
190 // Defer handler registration to avoid running during modal dialog/yield
191 auto initFunc = [this]() {
192 for( const auto& handler : m_msgHandlers )
193 {
194 if( !m_browser->AddScriptMessageHandler( handler.first ) )
195 {
196 wxLogTrace( "webview", "Could not add script message handler %s", handler.first );
197 }
198 }
199
200 // Inject navigation hook for SPA/JS navigation to prevent webkit crashing without new window
201 m_browser->AddUserScript( R"(
202 (function() {
203 // Change window.open to navigate in the same window
204 window.open = function(url) { if (url) window.location.href = url; return null; };
205 window.showModalDialog = function() { return null; };
206
207 if (window.external && window.external.invoke) {
208 function notifyHost() {
209 window.external.invoke('navigation:' + window.location.href);
210 }
211 window.addEventListener('popstate', notifyHost);
212 window.addEventListener('pushstate', notifyHost);
213 window.addEventListener('replacestate', notifyHost);
214 ['pushState', 'replaceState'].forEach(function(type) {
215 var orig = history[type];
216 history[type] = function() {
217 var rv = orig.apply(this, arguments);
218 window.dispatchEvent(new Event(type.toLowerCase()));
219 return rv;
220 };
221 });
222 }
223 })();
224 )" );
225
226 };
227
228 if( m_toolManager && m_tool )
229 m_toolManager->RunMainStack( m_tool, initFunc );
230 else
231 CallAfter( initFunc );
232
233 m_initialized = true;
234 }
235
236 aEvt.Skip();
237}
238
239void WEBVIEW_PANEL::OnNewWindow( wxWebViewEvent& aEvt )
240{
241 m_browser->LoadURL( aEvt.GetURL() );
242 aEvt.Veto(); // Prevent default behavior of opening a new window
243 wxLogTrace( "webview", "New window requested for URL: %s", aEvt.GetURL() );
244 wxLogTrace( "webview", "Target: %s", aEvt.GetTarget() );
245 wxLogTrace( "webview", "Action flags: %d", static_cast<int>(aEvt.GetNavigationAction()) );
246 wxLogTrace( "webview", "Message handler: %s", aEvt.GetMessageHandler() );
247}
248
249void WEBVIEW_PANEL::OnScriptMessage( wxWebViewEvent& aEvt )
250{
251 wxLogTrace( "webview", "Script message received: %s for handler %s", aEvt.GetString(), aEvt.GetMessageHandler() );
252 wxString handler = aEvt.GetMessageHandler();
253 handler.Trim(true).Trim(false);
254
255 if( handler.IsEmpty() )
256 {
257 for( auto handlerPair : m_msgHandlers )
258 {
259 wxLogTrace( "webview", "No handler specified, trying: %s", handlerPair.first );
260 handlerPair.second( aEvt.GetString() );
261 }
262
263 return;
264 }
265
266 auto it = m_msgHandlers.find( handler );
267
268 if( it == m_msgHandlers.end() )
269 {
270 wxLogTrace( "webview", "No handler registered for message: %s", handler );
271 return;
272 }
273
274 // Call the registered handler with the message
275 wxLogTrace( "webview", "Calling handler for message: %s", handler );
276 it->second( aEvt.GetString() );
277}
278
279
280void WEBVIEW_PANEL::OnScriptResult( wxWebViewEvent& aEvt )
281{
282 if( aEvt.IsError() )
283 wxLogTrace( "webview", "Async script execution failed: %s", aEvt.GetString() );
284}
285
286void WEBVIEW_PANEL::OnError( wxWebViewEvent& aEvt )
287{
288 m_loadError = true;
289 wxLogTrace( "webview", "WebView error: %s", aEvt.GetString() );
290}
wxString GetMajorMinorPatchVersion()
Get the major, minor and patch version in a string major.minor.patch This is extracted by CMake from ...
Base abstract interface for all kinds of tools.
Definition tool_base.h:66
Master controller class:
WEBVIEW_PANEL(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, const int style=0, TOOL_MANAGER *aToolManager=nullptr, TOOL_BASE *aTool=nullptr)
TOOL_MANAGER * m_toolManager
wxString m_backend
bool m_loadedEventBound
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)
TOOL_BASE * m_tool
void ClearMessageHandlers()
bool AddMessageHandler(const wxString &name, MESSAGE_HANDLER handler)
bool m_handleExternalLinks
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