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/evtloop.h>
27#include <wx/sizer.h>
28#include <wx/webviewarchivehandler.h>
29#include <wx/webviewfshandler.h>
30#include <wx/utils.h>
31#include <wx/log.h>
32
33#if wxUSE_WEBVIEW_EDGE
34 // note webview2 is available on Windows, but not on MINGW (only webview available)
35 #include <webview2EnvironmentOptions.h>
36#endif
37
38
39WEBVIEW_PANEL::WEBVIEW_PANEL( wxWindow* aParent, wxWindowID aId, const wxPoint& aPos, const wxSize& aSize,
40 const int aStyle, TOOL_MANAGER* aToolManager, TOOL_BASE* aTool ) :
41 wxPanel( aParent, aId, aPos, aSize, aStyle ),
42 m_initialized( false ),
43 m_handleExternalLinks( false ),
44 m_loadError( false ),
45 m_loadedEventBound( false ),
46 m_browser( nullptr ),
47 m_toolManager( aToolManager ),
48 m_tool( aTool )
49{
50 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
51
52 if( !wxGetEnv( wxT( "WEBKIT_DISABLE_COMPOSITING_MODE" ), nullptr ) )
53 {
54 wxSetEnv( wxT( "WEBKIT_DISABLE_COMPOSITING_MODE" ), wxT( "1" ) );
55 }
56
57#if wxCHECK_VERSION( 3, 3, 0 )
58 wxWebViewConfiguration config = wxWebView::NewConfiguration();
59 m_backend = config.GetBackend();
60
61#if wxUSE_WEBVIEW_EDGE
62 if( m_backend == wxWebViewBackendEdge )
63 {
64 ICoreWebView2EnvironmentOptions* webViewOptions =
65 (ICoreWebView2EnvironmentOptions*) config.GetNativeConfiguration();
66
67 // Disable gesture navigation features
68 webViewOptions->put_AdditionalBrowserArguments( L"--disable-features=msEdgeMouseGestureSupported,"
69 L"msEdgeMouseGestureDefaultEnabled,OverscrollHistoryNavigation "
70 L"--enable-features=kEdgeMouseGestureDisabledInCN" );
71 }
72#endif
73
74 m_browser = wxWebView::New( config );
75#else
76 m_browser = wxWebView::New();
77 m_backend = wxWebViewBackendDefault;
78#endif
79
80 wxWebView* browser = GetWebView();
81
82 if( !browser )
83 return;
84
85#ifdef __WXMAC__
86 browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewArchiveHandler( "wxfs" ) ) );
87 browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewFSHandler( "memory" ) ) );
88#endif
89 browser->SetUserAgent( wxString::Format( "KiCad/%s WebView/%s", GetMajorMinorPatchVersion(), wxGetOsDescription() ) );
90 browser->Create( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize );
91 sizer->Add( browser, 1, wxEXPAND );
92 SetSizer( sizer );
93
94#ifndef __WXMAC__
95 browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewArchiveHandler( "wxfs" ) ) );
96 browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewFSHandler( "memory" ) ) );
97#endif
98
99 Bind( wxEVT_WEBVIEW_NAVIGATING, &WEBVIEW_PANEL::OnNavigationRequest, this, browser->GetId() );
100 Bind( wxEVT_WEBVIEW_NEWWINDOW, &WEBVIEW_PANEL::OnNewWindow, this, browser->GetId() );
101 Bind( wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WEBVIEW_PANEL::OnScriptMessage, this, browser->GetId() );
102 Bind( wxEVT_WEBVIEW_SCRIPT_RESULT, &WEBVIEW_PANEL::OnScriptResult, this, browser->GetId() );
103 Bind( wxEVT_WEBVIEW_ERROR, &WEBVIEW_PANEL::OnError, this, browser->GetId() );
104
105 m_initRetryTimer.Bind( wxEVT_TIMER, [this]( wxTimerEvent& ) { DoInitHandlers(); } );
106}
107
112
114{
116 return;
117
118 wxWebView* browser = GetWebView();
119
120 if( !browser )
121 return;
122
123 Bind( wxEVT_WEBVIEW_LOADED, &WEBVIEW_PANEL::OnWebViewLoaded, this, browser->GetId() );
124 m_loadedEventBound = true;
125}
126
127void WEBVIEW_PANEL::LoadURL( const wxString& aURL )
128{
129 wxLogTrace( "webview", "Loading URL: %s", aURL );
130
131 if( aURL.starts_with( "file:/" ) && !aURL.starts_with( "file:///" ) )
132 {
133 wxString new_url = wxString( "file:///" ) + aURL.AfterFirst( '/' );
134
135 if( wxWebView* browser = GetWebView() )
136 browser->LoadURL( new_url );
137
138 return;
139 }
140
141 if( !aURL.StartsWith( "http://" ) && !aURL.StartsWith( "https://" ) && !aURL.StartsWith( "file://" ) )
142 {
143 wxLogError( "Invalid URL: %s", aURL );
144 return;
145 }
146
147 if( wxWebView* browser = GetWebView() )
148 browser->LoadURL( aURL );
149}
150
151void WEBVIEW_PANEL::SetPage( const wxString& aHtmlContent )
152{
153 wxLogTrace( "webview", "Setting page content" );
154
155 if( wxWebView* browser = GetWebView() )
156 browser->SetPage( aHtmlContent, "file://" );
157}
158
159bool WEBVIEW_PANEL::AddMessageHandler( const wxString& aName, MESSAGE_HANDLER aHandler )
160{
161 wxLogTrace( "webview", "Adding message handler for: %s", aName );
162 auto it = m_msgHandlers.find( aName );
163
164 if( it != m_msgHandlers.end() )
165 {
166 it->second = std::move( aHandler );
167 return true;
168 }
169
170 m_msgHandlers.emplace( aName, std::move( aHandler ) );
171
172 if( m_initialized )
173 {
174 wxWebView* browser = GetWebView();
175
176 if( !browser )
177 return true;
178
179 wxEventLoopBase* activeLoop = wxEventLoopBase::GetActive();
180
181 if( activeLoop && ( !activeLoop->IsMain() || activeLoop->IsYielding() ) )
182 {
183 m_initRetryTimer.StartOnce( 200 );
184 }
185 else
186 {
187 if( !browser->AddScriptMessageHandler( aName ) )
188 wxLogTrace( "webview", "Could not add script message handler %s", aName );
189 }
190 }
191
192 return true;
193}
194
196{
197 wxLogTrace( "webview", "Clearing all message handlers" );
198
199 if( wxWebView* browser = GetWebView() )
200 {
201 for( const auto& handler : m_msgHandlers )
202 browser->RemoveScriptMessageHandler( handler.first );
203 }
204
205 m_msgHandlers.clear();
206}
207
208void WEBVIEW_PANEL::OnNavigationRequest( wxWebViewEvent& aEvt )
209{
210 m_loadError = false;
211 wxLogTrace( "webview", "Navigation request to URL: %s", aEvt.GetURL() );
212 // Default behavior: open external links in the system browser
213 bool isExternal = aEvt.GetURL().StartsWith( "http://" ) || aEvt.GetURL().StartsWith( "https://" );
214
215 if( isExternal && !m_handleExternalLinks )
216 {
217 wxLogTrace( "webview", "Opening external URL in system browser: %s", aEvt.GetURL() );
218 wxLaunchDefaultBrowser( aEvt.GetURL() );
219 aEvt.Veto();
220 }
221}
222
224{
225 wxWebView* browser = GetWebView();
226
227 if( !browser )
228 return;
229
230 // WebKit's AddScriptMessageHandler internally calls RunScript which yields the
231 // event loop via wxGUIEventLoop::DoYieldFor. If we're inside a nested event loop
232 // or the main loop is already mid-yield (e.g., wxProgressDialog pumping events),
233 // this causes WebKit's JSC to crash in sanitizeStackForVM when the reentrant
234 // yield tries to create a JS context. Re-defer until the loop is idle.
235 wxEventLoopBase* activeLoop = wxEventLoopBase::GetActive();
236
237 if( activeLoop && ( !activeLoop->IsMain() || activeLoop->IsYielding() ) )
238 {
239 m_initRetryTimer.StartOnce( 200 );
240 return;
241 }
242
243 for( const auto& handler : m_msgHandlers )
244 {
245 if( !browser->AddScriptMessageHandler( handler.first ) )
246 wxLogTrace( "webview", "Could not add script message handler %s", handler.first );
247 }
248
249 browser->AddUserScript( R"(
250 (function() {
251 // Change window.open to navigate in the same window
252 window.open = function(url) { if (url) window.location.href = url; return null; };
253 window.showModalDialog = function() { return null; };
254
255 if (window.external && window.external.invoke) {
256 function notifyHost() {
257 window.external.invoke('navigation:' + window.location.href);
258 }
259 window.addEventListener('popstate', notifyHost);
260 window.addEventListener('pushstate', notifyHost);
261 window.addEventListener('replacestate', notifyHost);
262 ['pushState', 'replaceState'].forEach(function(type) {
263 var orig = history[type];
264 history[type] = function() {
265 var rv = orig.apply(this, arguments);
266 window.dispatchEvent(new Event(type.toLowerCase()));
267 return rv;
268 };
269 });
270 }
271 })();
272 )" );
273}
274
275
276void WEBVIEW_PANEL::OnWebViewLoaded( wxWebViewEvent& aEvt )
277{
278 if( !m_initialized )
279 {
280 m_initialized = true;
281
282 if( m_toolManager && m_tool )
283 m_toolManager->RunMainStack( m_tool, [this]() { DoInitHandlers(); } );
284 else
285 m_initRetryTimer.StartOnce( 1 );
286 }
287
288 aEvt.Skip();
289}
290
291void WEBVIEW_PANEL::OnNewWindow( wxWebViewEvent& aEvt )
292{
293 if( wxWebView* browser = GetWebView() )
294 browser->LoadURL( aEvt.GetURL() );
295
296 aEvt.Veto(); // Prevent default behavior of opening a new window
297 wxLogTrace( "webview", "New window requested for URL: %s", aEvt.GetURL() );
298 wxLogTrace( "webview", "Target: %s", aEvt.GetTarget() );
299 wxLogTrace( "webview", "Action flags: %d", static_cast<int>(aEvt.GetNavigationAction()) );
300 wxLogTrace( "webview", "Message handler: %s", aEvt.GetMessageHandler() );
301}
302
303void WEBVIEW_PANEL::OnScriptMessage( wxWebViewEvent& aEvt )
304{
305 wxLogTrace( "webview", "Script message received: %s for handler %s", aEvt.GetString(), aEvt.GetMessageHandler() );
306 wxString handler = aEvt.GetMessageHandler();
307 handler.Trim(true).Trim(false);
308
309 if( handler.IsEmpty() )
310 {
311 for( auto handlerPair : m_msgHandlers )
312 {
313 wxLogTrace( "webview", "No handler specified, trying: %s", handlerPair.first );
314 handlerPair.second( aEvt.GetString() );
315 }
316
317 return;
318 }
319
320 auto it = m_msgHandlers.find( handler );
321
322 if( it == m_msgHandlers.end() )
323 {
324 wxLogTrace( "webview", "No handler registered for message: %s", handler );
325 return;
326 }
327
328 // Call the registered handler with the message
329 wxLogTrace( "webview", "Calling handler for message: %s", handler );
330 it->second( aEvt.GetString() );
331}
332
333
334void WEBVIEW_PANEL::OnScriptResult( wxWebViewEvent& aEvt )
335{
336 if( aEvt.IsError() )
337 wxLogTrace( "webview", "Async script execution failed: %s", aEvt.GetString() );
338}
339
340void WEBVIEW_PANEL::OnError( wxWebViewEvent& aEvt )
341{
342 m_loadError = true;
343 wxLogTrace( "webview", "WebView error: %s", aEvt.GetString() );
344}
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
wxTimer m_initRetryTimer
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
wxWeakRef< wxWebView > m_browser
void ClearMessageHandlers()
bool AddMessageHandler(const wxString &name, MESSAGE_HANDLER handler)
bool m_handleExternalLinks
std::function< void(const wxString &)> MESSAGE_HANDLER
void OnScriptResult(wxWebViewEvent &evt)
wxWebView * GetWebView() const
void OnError(wxWebViewEvent &evt)
~WEBVIEW_PANEL() override
void LoadURL(const wxString &url)