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
32WEBVIEW_PANEL::WEBVIEW_PANEL( wxWindow* aParent, wxWindowID aId, const wxPoint& aPos, const wxSize& aSize,
33 const int aStyle, TOOL_MANAGER* aToolManager, TOOL_BASE* aTool ) :
34 wxPanel( aParent, aId, aPos, aSize, aStyle ),
35 m_initialized( false ),
36 m_handleExternalLinks( false ),
37 m_loadError( false ),
38 m_loadedEventBound( false ),
39 m_browser( wxWebView::New() ),
40 m_toolManager( aToolManager ),
41 m_tool( aTool )
42{
43 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
44
45 if( !wxGetEnv( wxT( "WEBKIT_DISABLE_COMPOSITING_MODE" ), nullptr ) )
46 {
47 wxSetEnv( wxT( "WEBKIT_DISABLE_COMPOSITING_MODE" ), wxT( "1" ) );
48 }
49
50#ifdef __WXMAC__
51 m_browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewArchiveHandler( "wxfs" ) ) );
52 m_browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewFSHandler( "memory" ) ) );
53#endif
54 m_browser->SetUserAgent( wxString::Format( "KiCad/%s WebView/%s", GetMajorMinorPatchVersion(), wxGetOsDescription() ) );
55 m_browser->Create( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize );
56 sizer->Add( m_browser, 1, wxEXPAND );
57 SetSizer( sizer );
58
59#ifndef __WXMAC__
60 m_browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewArchiveHandler( "wxfs" ) ) );
61 m_browser->RegisterHandler( wxSharedPtr<wxWebViewHandler>( new wxWebViewFSHandler( "memory" ) ) );
62#endif
63
64 Bind( wxEVT_WEBVIEW_NAVIGATING, &WEBVIEW_PANEL::OnNavigationRequest, this, m_browser->GetId() );
65 Bind( wxEVT_WEBVIEW_NEWWINDOW, &WEBVIEW_PANEL::OnNewWindow, this, m_browser->GetId() );
66 Bind( wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WEBVIEW_PANEL::OnScriptMessage, this, m_browser->GetId() );
67 Bind( wxEVT_WEBVIEW_SCRIPT_RESULT, &WEBVIEW_PANEL::OnScriptResult, this, m_browser->GetId() );
68 Bind( wxEVT_WEBVIEW_ERROR, &WEBVIEW_PANEL::OnError, this, m_browser->GetId() );
69}
70
74
76{
78 return;
79
80 Bind( wxEVT_WEBVIEW_LOADED, &WEBVIEW_PANEL::OnWebViewLoaded, this, m_browser->GetId() );
81 m_loadedEventBound = true;
82}
83
84void WEBVIEW_PANEL::LoadURL( const wxString& aURL )
85{
86 wxLogTrace( "webview", "Loading URL: %s", aURL );
87
88 if( aURL.starts_with( "file:/" ) && !aURL.starts_with( "file:///" ) )
89 {
90 wxString new_url = wxString( "file:///" ) + aURL.AfterFirst( '/' );
91 m_browser->LoadURL( new_url );
92 return;
93 }
94
95 if( !aURL.StartsWith( "http://" ) && !aURL.StartsWith( "https://" ) && !aURL.StartsWith( "file://" ) )
96 {
97 wxLogError( "Invalid URL: %s", aURL );
98 return;
99 }
100
101 m_browser->LoadURL( aURL );
102}
103
104void WEBVIEW_PANEL::SetPage( const wxString& aHtmlContent )
105{
106 wxLogTrace( "webview", "Setting page content" );
107 m_browser->SetPage( aHtmlContent, "file://" );
108}
109
110bool WEBVIEW_PANEL::AddMessageHandler( const wxString& aName, MESSAGE_HANDLER aHandler )
111{
112 wxLogTrace( "webview", "Adding message handler for: %s", aName );
113 auto it = m_msgHandlers.find( aName );
114
115 if( it != m_msgHandlers.end() )
116 {
117 it->second = std::move( aHandler );
118 return true;
119 }
120
121 m_msgHandlers.emplace( aName, std::move( aHandler ) );
122
123 if( m_initialized )
124 {
125 if( !m_browser->AddScriptMessageHandler( aName ) )
126 wxLogDebug( "Could not add script message handler %s", aName );
127 }
128
129 return true;
130}
131
133{
134 wxLogTrace( "webview", "Clearing all message handlers" );
135
136 for( const auto& handler : m_msgHandlers )
137 m_browser->RemoveScriptMessageHandler( handler.first );
138
139 m_msgHandlers.clear();
140}
141
142void WEBVIEW_PANEL::OnNavigationRequest( wxWebViewEvent& aEvt )
143{
144 m_loadError = false;
145 wxLogTrace( "webview", "Navigation request to URL: %s", aEvt.GetURL() );
146 // Default behavior: open external links in the system browser
147 bool isExternal = aEvt.GetURL().StartsWith( "http://" ) || aEvt.GetURL().StartsWith( "https://" );
148
149 if( isExternal && !m_handleExternalLinks )
150 {
151 wxLogTrace( "webview", "Opening external URL in system browser: %s", aEvt.GetURL() );
152 wxLaunchDefaultBrowser( aEvt.GetURL() );
153 aEvt.Veto();
154 }
155}
156
157void WEBVIEW_PANEL::OnWebViewLoaded( wxWebViewEvent& aEvt )
158{
159 if( !m_initialized )
160 {
161 // Defer handler registration to avoid running during modal dialog/yield
162 auto initFunc = [this]() {
163 for( const auto& handler : m_msgHandlers )
164 {
165 if( !m_browser->AddScriptMessageHandler( handler.first ) )
166 {
167 wxLogDebug( "Could not add script message handler %s", handler.first );
168 }
169 }
170
171 // Inject navigation hook for SPA/JS navigation to prevent webkit crashing without new window
172 m_browser->AddUserScript( R"(
173 (function() {
174 // Change window.open to navigate in the same window
175 window.open = function(url) { if (url) window.location.href = url; return null; };
176 window.showModalDialog = function() { return null; };
177
178 if (window.external && window.external.invoke) {
179 function notifyHost() {
180 window.external.invoke('navigation:' + window.location.href);
181 }
182 window.addEventListener('popstate', notifyHost);
183 window.addEventListener('pushstate', notifyHost);
184 window.addEventListener('replacestate', notifyHost);
185 ['pushState', 'replaceState'].forEach(function(type) {
186 var orig = history[type];
187 history[type] = function() {
188 var rv = orig.apply(this, arguments);
189 window.dispatchEvent(new Event(type.toLowerCase()));
190 return rv;
191 };
192 });
193 }
194 })();
195 )" );
196
197 };
198
199 if( m_toolManager && m_tool )
200 m_toolManager->RunMainStack( m_tool, initFunc );
201 else
202 CallAfter( initFunc );
203
204 m_initialized = true;
205 }
206
207 aEvt.Skip();
208}
209
210void WEBVIEW_PANEL::OnNewWindow( wxWebViewEvent& aEvt )
211{
212 m_browser->LoadURL( aEvt.GetURL() );
213 aEvt.Veto(); // Prevent default behavior of opening a new window
214 wxLogTrace( "webview", "New window requested for URL: %s", aEvt.GetURL() );
215 wxLogTrace( "webview", "Target: %s", aEvt.GetTarget() );
216 wxLogTrace( "webview", "Action flags: %d", static_cast<int>(aEvt.GetNavigationAction()) );
217 wxLogTrace( "webview", "Message handler: %s", aEvt.GetMessageHandler() );
218}
219
220void WEBVIEW_PANEL::OnScriptMessage( wxWebViewEvent& aEvt )
221{
222 wxLogTrace( "webview", "Script message received: %s for handler %s", aEvt.GetString(), aEvt.GetMessageHandler() );
223 wxString handler = aEvt.GetMessageHandler();
224 handler.Trim(true).Trim(false);
225
226 if( handler.IsEmpty() )
227 {
228 for( auto handlerPair : m_msgHandlers )
229 {
230 wxLogTrace( "webview", "No handler specified, trying: %s", handlerPair.first );
231 handlerPair.second( aEvt.GetString() );
232 }
233
234 return;
235 }
236
237 auto it = m_msgHandlers.find( handler );
238
239 if( it == m_msgHandlers.end() )
240 {
241 wxLogDebug( "No handler registered for message: %s", handler );
242 return;
243 }
244
245 // Call the registered handler with the message
246 wxLogTrace( "webview", "Calling handler for message: %s", handler );
247 it->second( aEvt.GetString() );
248}
249
250void WEBVIEW_PANEL::OnScriptResult( wxWebViewEvent& aEvt )
251{
252 if( aEvt.IsError() )
253 wxLogDebug( "Async script execution failed: %s", aEvt.GetString() );
254}
255
256void WEBVIEW_PANEL::OnError( wxWebViewEvent& aEvt )
257{
258 m_loadError = true;
259 wxLogDebug( "WebView error: %s", aEvt.GetString() );
260}
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
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