1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/webview_webkit2.cpp
3 // Purpose: GTK WebKit2 backend for web view component
4 // Author: Scott Talbert
5 // Copyright: (c) 2017 Scott Talbert
6 // Licence: wxWindows licence
7 /////////////////////////////////////////////////////////////////////////////
8
9 // For compilers that support precompilation, includes "wx.h".
10 #include "wx/wxprec.h"
11
12 #if wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT2
13
14 #include "wx/stockitem.h"
15 #include "wx/gtk/webview_webkit.h"
16 #include "wx/gtk/control.h"
17 #include "wx/gtk/private.h"
18 #include "wx/filesys.h"
19 #include "wx/base64.h"
20 #include "wx/log.h"
21 #include "wx/gtk/private/webview_webkit2_extension.h"
22 #include "wx/gtk/private/error.h"
23 #include <webkit2/webkit2.h>
24
25 // ----------------------------------------------------------------------------
26 // GTK callbacks
27 // ----------------------------------------------------------------------------
28
29 extern "C"
30 {
31
32 static void
wxgtk_webview_webkit_load_changed(GtkWidget *,WebKitLoadEvent load_event,wxWebViewWebKit * webKitCtrl)33 wxgtk_webview_webkit_load_changed(GtkWidget *,
34 WebKitLoadEvent load_event,
35 wxWebViewWebKit *webKitCtrl)
36 {
37 wxString url = webKitCtrl->GetCurrentURL();
38
39 wxString target; // TODO: get target (if possible)
40
41 if (load_event == WEBKIT_LOAD_FINISHED)
42 {
43 webKitCtrl->m_busy = false;
44 wxWebViewEvent event(wxEVT_WEBVIEW_LOADED,
45 webKitCtrl->GetId(),
46 url, target);
47
48 webKitCtrl->HandleWindowEvent(event);
49 }
50 else if (load_event == WEBKIT_LOAD_COMMITTED)
51 {
52 webKitCtrl->m_busy = true;
53 wxWebViewEvent event(wxEVT_WEBVIEW_NAVIGATED,
54 webKitCtrl->GetId(),
55 url, target);
56
57 webKitCtrl->HandleWindowEvent(event);
58 }
59 }
60
61 static gboolean
wxgtk_webview_webkit_navigation(WebKitWebView *,WebKitPolicyDecision * decision,wxWebViewWebKit * webKitCtrl)62 wxgtk_webview_webkit_navigation(WebKitWebView *,
63 WebKitPolicyDecision *decision,
64 wxWebViewWebKit *webKitCtrl)
65 {
66 WebKitNavigationPolicyDecision* navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION(decision);
67 WebKitNavigationAction* action = webkit_navigation_policy_decision_get_navigation_action(navigation_decision);
68 WebKitURIRequest* request = webkit_navigation_action_get_request(action);
69 const gchar* uri = webkit_uri_request_get_uri(request);
70 wxString target = webkit_navigation_policy_decision_get_frame_name(navigation_decision);
71
72 //If m_creating is true then we are the result of a new window
73 //and so we need to send the event and veto the load
74 if(webKitCtrl->m_creating)
75 {
76 webKitCtrl->m_creating = false;
77 wxWebViewEvent event(wxEVT_WEBVIEW_NEWWINDOW,
78 webKitCtrl->GetId(),
79 wxString(uri, wxConvUTF8),
80 target);
81
82 webKitCtrl->HandleWindowEvent(event);
83
84 webkit_policy_decision_ignore(decision);
85 return TRUE;
86 }
87
88 webKitCtrl->m_busy = true;
89
90 wxWebViewEvent event(wxEVT_WEBVIEW_NAVIGATING,
91 webKitCtrl->GetId(),
92 wxString( uri, wxConvUTF8 ),
93 target);
94
95 webKitCtrl->HandleWindowEvent(event);
96
97 if (!event.IsAllowed())
98 {
99 webKitCtrl->m_busy = false;
100 webkit_policy_decision_ignore(decision);
101 return TRUE;
102 }
103 else
104 {
105 return FALSE;
106 }
107 }
108
109 static gboolean
wxgtk_webview_webkit_load_failed(WebKitWebView *,WebKitLoadEvent,gchar * uri,GError * error,wxWebViewWebKit * webKitWindow)110 wxgtk_webview_webkit_load_failed(WebKitWebView *,
111 WebKitLoadEvent,
112 gchar *uri,
113 GError *error,
114 wxWebViewWebKit* webKitWindow)
115 {
116 webKitWindow->m_busy = false;
117 wxWebViewNavigationError type = wxWEBVIEW_NAV_ERR_OTHER;
118
119 wxString description(error->message, wxConvUTF8);
120
121 if (strcmp(g_quark_to_string(error->domain), "soup_http_error_quark") == 0)
122 {
123 switch (error->code)
124 {
125 case SOUP_STATUS_CANCELLED:
126 type = wxWEBVIEW_NAV_ERR_USER_CANCELLED;
127 break;
128
129 case SOUP_STATUS_CANT_RESOLVE:
130 case SOUP_STATUS_NOT_FOUND:
131 type = wxWEBVIEW_NAV_ERR_NOT_FOUND;
132 break;
133
134 case SOUP_STATUS_CANT_RESOLVE_PROXY:
135 case SOUP_STATUS_CANT_CONNECT:
136 case SOUP_STATUS_CANT_CONNECT_PROXY:
137 case SOUP_STATUS_SSL_FAILED:
138 case SOUP_STATUS_IO_ERROR:
139 type = wxWEBVIEW_NAV_ERR_CONNECTION;
140 break;
141
142 case SOUP_STATUS_MALFORMED:
143 type = wxWEBVIEW_NAV_ERR_REQUEST;
144 break;
145
146 case SOUP_STATUS_BAD_REQUEST:
147 type = wxWEBVIEW_NAV_ERR_REQUEST;
148 break;
149
150 case SOUP_STATUS_UNAUTHORIZED:
151 case SOUP_STATUS_FORBIDDEN:
152 type = wxWEBVIEW_NAV_ERR_AUTH;
153 break;
154
155 case SOUP_STATUS_METHOD_NOT_ALLOWED:
156 case SOUP_STATUS_NOT_ACCEPTABLE:
157 type = wxWEBVIEW_NAV_ERR_SECURITY;
158 break;
159
160 case SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED:
161 type = wxWEBVIEW_NAV_ERR_AUTH;
162 break;
163
164 case SOUP_STATUS_REQUEST_TIMEOUT:
165 type = wxWEBVIEW_NAV_ERR_CONNECTION;
166 break;
167
168 case SOUP_STATUS_REQUEST_ENTITY_TOO_LARGE:
169 case SOUP_STATUS_REQUEST_URI_TOO_LONG:
170 case SOUP_STATUS_UNSUPPORTED_MEDIA_TYPE:
171 type = wxWEBVIEW_NAV_ERR_REQUEST;
172 break;
173
174 case SOUP_STATUS_BAD_GATEWAY:
175 case SOUP_STATUS_SERVICE_UNAVAILABLE:
176 case SOUP_STATUS_GATEWAY_TIMEOUT:
177 type = wxWEBVIEW_NAV_ERR_CONNECTION;
178 break;
179
180 case SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED:
181 type = wxWEBVIEW_NAV_ERR_REQUEST;
182 break;
183 }
184 }
185 else if (strcmp(g_quark_to_string(error->domain),
186 "webkit-network-error-quark") == 0)
187 {
188 switch (error->code)
189 {
190 case WEBKIT_NETWORK_ERROR_UNKNOWN_PROTOCOL:
191 type = wxWEBVIEW_NAV_ERR_REQUEST;
192 break;
193
194 case WEBKIT_NETWORK_ERROR_CANCELLED:
195 type = wxWEBVIEW_NAV_ERR_USER_CANCELLED;
196 break;
197
198 case WEBKIT_NETWORK_ERROR_FILE_DOES_NOT_EXIST:
199 type = wxWEBVIEW_NAV_ERR_NOT_FOUND;
200 break;
201 }
202 }
203 else if (strcmp(g_quark_to_string(error->domain),
204 "webkit-policy-error-quark") == 0)
205 {
206 switch (error->code)
207 {
208 case WEBKIT_POLICY_ERROR_CANNOT_USE_RESTRICTED_PORT:
209 type = wxWEBVIEW_NAV_ERR_SECURITY;
210 break;
211 }
212 }
213
214 wxWebViewEvent event(wxEVT_WEBVIEW_ERROR,
215 webKitWindow->GetId(),
216 uri, "");
217 event.SetString(description);
218 event.SetInt(type);
219
220 webKitWindow->HandleWindowEvent(event);
221
222 return FALSE;
223 }
224
225 static gboolean
wxgtk_webview_webkit_new_window(WebKitPolicyDecision * decision,wxWebViewWebKit * webKitCtrl)226 wxgtk_webview_webkit_new_window(WebKitPolicyDecision *decision,
227 wxWebViewWebKit *webKitCtrl)
228 {
229 WebKitNavigationPolicyDecision* navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION(decision);
230 WebKitNavigationAction* action = webkit_navigation_policy_decision_get_navigation_action(navigation_decision);
231 WebKitURIRequest* request = webkit_navigation_action_get_request(action);
232 const gchar* uri = webkit_uri_request_get_uri(request);
233
234 wxString target = webkit_navigation_policy_decision_get_frame_name(navigation_decision);
235 wxWebViewEvent event(wxEVT_WEBVIEW_NEWWINDOW,
236 webKitCtrl->GetId(),
237 wxString( uri, wxConvUTF8 ),
238 target);
239
240 webKitCtrl->HandleWindowEvent(event);
241
242 //We always want the user to handle this themselves
243 webkit_policy_decision_ignore(decision);
244 return TRUE;
245 }
246
247 static gboolean
wxgtk_webview_webkit_decide_policy(WebKitWebView * web_view,WebKitPolicyDecision * decision,WebKitPolicyDecisionType type,wxWebViewWebKit * webKitCtrl)248 wxgtk_webview_webkit_decide_policy(WebKitWebView *web_view,
249 WebKitPolicyDecision *decision,
250 WebKitPolicyDecisionType type,
251 wxWebViewWebKit *webKitCtrl)
252 {
253 switch (type)
254 {
255 case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
256 return wxgtk_webview_webkit_navigation(web_view, decision, webKitCtrl);
257 case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
258 return wxgtk_webview_webkit_new_window(decision, webKitCtrl);
259 default:
260 return FALSE;
261 }
262 }
263
264 static void
wxgtk_webview_webkit_title_changed(GtkWidget * widget,GParamSpec *,wxWebViewWebKit * webKitCtrl)265 wxgtk_webview_webkit_title_changed(GtkWidget* widget,
266 GParamSpec *,
267 wxWebViewWebKit *webKitCtrl)
268 {
269 gchar *title;
270 g_object_get(G_OBJECT(widget), "title", &title, NULL);
271
272 wxWebViewEvent event(wxEVT_WEBVIEW_TITLE_CHANGED,
273 webKitCtrl->GetId(),
274 webKitCtrl->GetCurrentURL(),
275 "");
276 event.SetString(wxString(title, wxConvUTF8));
277
278 webKitCtrl->HandleWindowEvent(event);
279
280 g_free(title);
281 }
282
283 static void
wxgtk_webview_webkit_uri_scheme_request_cb(WebKitURISchemeRequest * request,wxWebViewWebKit * webKitCtrl)284 wxgtk_webview_webkit_uri_scheme_request_cb(WebKitURISchemeRequest *request,
285 wxWebViewWebKit *webKitCtrl)
286 {
287 const wxString scheme = wxString::FromUTF8(webkit_uri_scheme_request_get_scheme(request));
288
289 wxSharedPtr<wxWebViewHandler> handler;
290 wxVector<wxSharedPtr<wxWebViewHandler> > handlers = webKitCtrl->GetHandlers();
291
292 for(wxVector<wxSharedPtr<wxWebViewHandler> >::iterator it = handlers.begin();
293 it != handlers.end(); ++it)
294 {
295 if(scheme == (*it)->GetName())
296 {
297 handler = (*it);
298 }
299 }
300
301 if(handler)
302 {
303 const wxString uri = wxString::FromUTF8(webkit_uri_scheme_request_get_uri(request));
304
305 wxFSFile* file = handler->GetFile(uri);
306 if(file)
307 {
308 gint64 length = file->GetStream()->GetLength();
309 guint8 *data = g_new(guint8, length);
310 file->GetStream()->Read(data, length);
311 GInputStream *stream = g_memory_input_stream_new_from_data(data,
312 length,
313 g_free);
314 wxString mime = file->GetMimeType();
315 webkit_uri_scheme_request_finish(request, stream, length, mime.utf8_str());
316 }
317 else
318 {
319 wxGtkError error(g_error_new(WEBKIT_NETWORK_ERROR,
320 WEBKIT_NETWORK_ERROR_FILE_DOES_NOT_EXIST,
321 "File not found: %s", uri.utf8_str().data()));
322 webkit_uri_scheme_request_finish_error(request, error);
323 }
324 }
325 else
326 {
327 wxGtkError error(g_error_new(WEBKIT_NETWORK_ERROR,
328 WEBKIT_NETWORK_ERROR_UNKNOWN_PROTOCOL,
329 "Unknown scheme: %s", scheme.utf8_str().data()));
330 webkit_uri_scheme_request_finish_error(request, error);
331 }
332 }
333
334 static gboolean
wxgtk_webview_webkit_context_menu(WebKitWebView *,WebKitContextMenu *,GdkEvent *,WebKitHitTestResult *,wxWebViewWebKit * webKitCtrl)335 wxgtk_webview_webkit_context_menu(WebKitWebView *,
336 WebKitContextMenu *,
337 GdkEvent *,
338 WebKitHitTestResult *,
339 wxWebViewWebKit *webKitCtrl)
340 {
341 return !webKitCtrl->IsContextMenuEnabled();
342 }
343
344 static WebKitWebView*
wxgtk_webview_webkit_create_webview(WebKitWebView * web_view,WebKitNavigationAction *,wxWebViewWebKit * webKitCtrl)345 wxgtk_webview_webkit_create_webview(WebKitWebView *web_view,
346 WebKitNavigationAction *,
347 wxWebViewWebKit *webKitCtrl)
348 {
349 //As we do not know the uri being loaded at this point allow the load to
350 //continue and catch it in navigation-policy-decision-requested
351 webKitCtrl->m_creating = true;
352 return web_view;
353 }
354
355 static void
wxgtk_webview_webkit_counted_matches(WebKitFindController *,guint match_count,int * findCount)356 wxgtk_webview_webkit_counted_matches(WebKitFindController *,
357 guint match_count,
358 int *findCount)
359 {
360 *findCount = match_count;
361 }
362
363 static void
wxgtk_initialize_web_extensions(WebKitWebContext * context,GDBusServer * dbusServer)364 wxgtk_initialize_web_extensions(WebKitWebContext *context,
365 GDBusServer *dbusServer)
366 {
367 const char *address = g_dbus_server_get_client_address(dbusServer);
368 GVariant *user_data = g_variant_new("(s)", address);
369 webkit_web_context_set_web_extensions_directory(context,
370 WX_WEB_EXTENSIONS_DIRECTORY);
371 webkit_web_context_set_web_extensions_initialization_user_data(context,
372 user_data);
373 }
374
375 static gboolean
wxgtk_new_connection_cb(GDBusServer *,GDBusConnection * connection,GDBusProxy ** proxy)376 wxgtk_new_connection_cb(GDBusServer *,
377 GDBusConnection *connection,
378 GDBusProxy **proxy)
379 {
380 GError *error = NULL;
381 GDBusProxyFlags flags = GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS);
382 *proxy = g_dbus_proxy_new_sync(connection,
383 flags,
384 NULL,
385 NULL,
386 WXGTK_WEB_EXTENSION_OBJECT_PATH,
387 WXGTK_WEB_EXTENSION_INTERFACE,
388 NULL,
389 &error);
390 if (error)
391 {
392 g_warning("Failed to create dbus proxy: %s", error->message);
393 g_error_free(error);
394 }
395
396 return TRUE;
397 }
398
399 static
400 gboolean
wxgtk_dbus_peer_is_authorized(GCredentials * peer_credentials)401 wxgtk_dbus_peer_is_authorized(GCredentials *peer_credentials)
402 {
403 static GCredentials *own_credentials = g_credentials_new();
404 GError *error = NULL;
405
406 if (peer_credentials && g_credentials_is_same_user(peer_credentials, own_credentials, &error))
407 {
408 return TRUE;
409 }
410
411 if (error)
412 {
413 g_warning("Failed to authorize web extension connection: %s", error->message);
414 g_error_free(error);
415 }
416 return FALSE;
417 }
418
419 static gboolean
wxgtk_authorize_authenticated_peer_cb(GDBusAuthObserver *,GIOStream *,GCredentials * credentials,wxWebViewWebKit *)420 wxgtk_authorize_authenticated_peer_cb(GDBusAuthObserver *,
421 GIOStream *,
422 GCredentials *credentials,
423 wxWebViewWebKit *)
424 {
425 return wxgtk_dbus_peer_is_authorized(credentials);
426 }
427
428 } // extern "C"
429
430 //-----------------------------------------------------------------------------
431 // wxWebViewWebKit
432 //-----------------------------------------------------------------------------
433
434 wxIMPLEMENT_DYNAMIC_CLASS(wxWebViewWebKit, wxWebView);
435
wxWebViewWebKit()436 wxWebViewWebKit::wxWebViewWebKit()
437 {
438 m_web_view = NULL;
439 m_dbusServer = NULL;
440 m_extension = NULL;
441 }
442
Create(wxWindow * parent,wxWindowID id,const wxString & url,const wxPoint & pos,const wxSize & size,long style,const wxString & name)443 bool wxWebViewWebKit::Create(wxWindow *parent,
444 wxWindowID id,
445 const wxString &url,
446 const wxPoint& pos,
447 const wxSize& size,
448 long style,
449 const wxString& name)
450 {
451 m_web_view = NULL;
452 m_dbusServer = NULL;
453 m_extension = NULL;
454 m_busy = false;
455 m_guard = false;
456 m_creating = false;
457 FindClear();
458
459 // We currently unconditionally impose scrolling in both directions as it's
460 // necessary to show arbitrary pages.
461 style |= wxHSCROLL | wxVSCROLL;
462
463 if (!PreCreation( parent, pos, size ) ||
464 !CreateBase( parent, id, pos, size, style, wxDefaultValidator, name ))
465 {
466 wxFAIL_MSG( wxT("wxWebViewWebKit creation failed") );
467 return false;
468 }
469
470 SetupWebExtensionServer();
471 g_signal_connect(webkit_web_context_get_default(),
472 "initialize-web-extensions",
473 G_CALLBACK(wxgtk_initialize_web_extensions),
474 m_dbusServer);
475
476 m_web_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
477 GTKCreateScrolledWindowWith(GTK_WIDGET(m_web_view));
478 g_object_ref(m_widget);
479
480 g_signal_connect(m_web_view, "decide-policy",
481 G_CALLBACK(wxgtk_webview_webkit_decide_policy),
482 this);
483
484 g_signal_connect(m_web_view, "load-failed",
485 G_CALLBACK(wxgtk_webview_webkit_load_failed), this);
486
487 g_signal_connect(m_web_view, "notify::title",
488 G_CALLBACK(wxgtk_webview_webkit_title_changed), this);
489
490 g_signal_connect(m_web_view, "context-menu",
491 G_CALLBACK(wxgtk_webview_webkit_context_menu), this);
492
493 g_signal_connect(m_web_view, "create",
494 G_CALLBACK(wxgtk_webview_webkit_create_webview), this);
495
496 WebKitFindController* findctrl = webkit_web_view_get_find_controller(m_web_view);
497 g_signal_connect(findctrl, "counted-matches",
498 G_CALLBACK(wxgtk_webview_webkit_counted_matches),
499 &m_findCount);
500
501 m_parent->DoAddChild( this );
502
503 PostCreation(size);
504
505 /* Open a webpage */
506 webkit_web_view_load_uri(m_web_view, url.utf8_str());
507
508 // last to avoid getting signal too early
509 g_signal_connect(m_web_view, "load-changed",
510 G_CALLBACK(wxgtk_webview_webkit_load_changed),
511 this);
512
513 return true;
514 }
515
~wxWebViewWebKit()516 wxWebViewWebKit::~wxWebViewWebKit()
517 {
518 if (m_web_view)
519 GTKDisconnect(m_web_view);
520 if (m_dbusServer)
521 g_dbus_server_stop(m_dbusServer);
522 g_clear_object(&m_dbusServer);
523 g_clear_object(&m_extension);
524 }
525
Enable(bool enable)526 bool wxWebViewWebKit::Enable( bool enable )
527 {
528 if (!wxControl::Enable(enable))
529 return false;
530
531 gtk_widget_set_sensitive(gtk_bin_get_child(GTK_BIN(m_widget)), enable);
532
533 return true;
534 }
535
536 GdkWindow*
GTKGetWindow(wxArrayGdkWindows & WXUNUSED (windows)) const537 wxWebViewWebKit::GTKGetWindow(wxArrayGdkWindows& WXUNUSED(windows)) const
538 {
539 GdkWindow* window = gtk_widget_get_parent_window(m_widget);
540 return window;
541 }
542
ZoomIn()543 void wxWebViewWebKit::ZoomIn()
544 {
545 SetWebkitZoom(GetWebkitZoom() + 0.1);
546 }
547
ZoomOut()548 void wxWebViewWebKit::ZoomOut()
549 {
550 SetWebkitZoom(GetWebkitZoom() - 0.1);
551 }
552
SetWebkitZoom(float level)553 void wxWebViewWebKit::SetWebkitZoom(float level)
554 {
555 webkit_web_view_set_zoom_level(m_web_view, level);
556 }
557
GetWebkitZoom() const558 float wxWebViewWebKit::GetWebkitZoom() const
559 {
560 return webkit_web_view_get_zoom_level(m_web_view);
561 }
562
Stop()563 void wxWebViewWebKit::Stop()
564 {
565 webkit_web_view_stop_loading(m_web_view);
566 }
567
Reload(wxWebViewReloadFlags flags)568 void wxWebViewWebKit::Reload(wxWebViewReloadFlags flags)
569 {
570 if (flags & wxWEBVIEW_RELOAD_NO_CACHE)
571 {
572 webkit_web_view_reload_bypass_cache(m_web_view);
573 }
574 else
575 {
576 webkit_web_view_reload(m_web_view);
577 }
578 }
579
LoadURL(const wxString & url)580 void wxWebViewWebKit::LoadURL(const wxString& url)
581 {
582 webkit_web_view_load_uri(m_web_view, wxGTK_CONV(url));
583 }
584
585
GoBack()586 void wxWebViewWebKit::GoBack()
587 {
588 webkit_web_view_go_back(m_web_view);
589 }
590
GoForward()591 void wxWebViewWebKit::GoForward()
592 {
593 webkit_web_view_go_forward(m_web_view);
594 }
595
596
CanGoBack() const597 bool wxWebViewWebKit::CanGoBack() const
598 {
599 return webkit_web_view_can_go_back(m_web_view) != 0;
600 }
601
602
CanGoForward() const603 bool wxWebViewWebKit::CanGoForward() const
604 {
605 return webkit_web_view_can_go_forward(m_web_view) != 0;
606 }
607
ClearHistory()608 void wxWebViewWebKit::ClearHistory()
609 {
610 // In WebKit2GTK+, the BackForwardList can't be cleared so do nothing.
611 }
612
EnableHistory(bool)613 void wxWebViewWebKit::EnableHistory(bool)
614 {
615 // In WebKit2GTK+, history can't be disabled so do nothing here.
616 }
617
GetBackwardHistory()618 wxVector<wxSharedPtr<wxWebViewHistoryItem> > wxWebViewWebKit::GetBackwardHistory()
619 {
620 wxVector<wxSharedPtr<wxWebViewHistoryItem> > backhist;
621 WebKitBackForwardList* history =
622 webkit_web_view_get_back_forward_list(m_web_view);
623 GList* list = webkit_back_forward_list_get_back_list(history);
624 //We need to iterate in reverse to get the order we desire
625 for(int i = g_list_length(list) - 1; i >= 0 ; i--)
626 {
627 WebKitBackForwardListItem* gtkitem = (WebKitBackForwardListItem*)g_list_nth_data(list, i);
628 wxWebViewHistoryItem* wxitem = new wxWebViewHistoryItem(
629 webkit_back_forward_list_item_get_uri(gtkitem),
630 webkit_back_forward_list_item_get_title(gtkitem));
631 wxitem->m_histItem = gtkitem;
632 wxSharedPtr<wxWebViewHistoryItem> item(wxitem);
633 backhist.push_back(item);
634 }
635 return backhist;
636 }
637
GetForwardHistory()638 wxVector<wxSharedPtr<wxWebViewHistoryItem> > wxWebViewWebKit::GetForwardHistory()
639 {
640 wxVector<wxSharedPtr<wxWebViewHistoryItem> > forwardhist;
641 WebKitBackForwardList* history =
642 webkit_web_view_get_back_forward_list(m_web_view);
643 GList* list = webkit_back_forward_list_get_forward_list(history);
644 for(guint i = 0; i < g_list_length(list); i++)
645 {
646 WebKitBackForwardListItem* gtkitem = (WebKitBackForwardListItem*)g_list_nth_data(list, i);
647 wxWebViewHistoryItem* wxitem = new wxWebViewHistoryItem(
648 webkit_back_forward_list_item_get_uri(gtkitem),
649 webkit_back_forward_list_item_get_title(gtkitem));
650 wxitem->m_histItem = gtkitem;
651 wxSharedPtr<wxWebViewHistoryItem> item(wxitem);
652 forwardhist.push_back(item);
653 }
654 return forwardhist;
655 }
656
LoadHistoryItem(wxSharedPtr<wxWebViewHistoryItem> item)657 void wxWebViewWebKit::LoadHistoryItem(wxSharedPtr<wxWebViewHistoryItem> item)
658 {
659 WebKitBackForwardListItem* gtkitem = (WebKitBackForwardListItem*)item->m_histItem;
660 if(gtkitem)
661 {
662 webkit_web_view_go_to_back_forward_list_item(m_web_view,
663 WEBKIT_BACK_FORWARD_LIST_ITEM(gtkitem));
664 }
665 }
666
667 extern "C" {
wxgtk_can_execute_editing_command_cb(GObject *,GAsyncResult * res,void * user_data)668 static void wxgtk_can_execute_editing_command_cb(GObject*,
669 GAsyncResult *res,
670 void* user_data)
671 {
672 GAsyncResult** res_out = static_cast<GAsyncResult**>(user_data);
673 g_object_ref(res);
674 *res_out = res;
675 }
676 }
677
CanExecuteEditingCommand(const gchar * command) const678 bool wxWebViewWebKit::CanExecuteEditingCommand(const gchar* command) const
679 {
680 GAsyncResult *result = NULL;
681 webkit_web_view_can_execute_editing_command(m_web_view,
682 command,
683 NULL,
684 wxgtk_can_execute_editing_command_cb,
685 &result);
686
687 GMainContext *main_context = g_main_context_get_thread_default();
688 while (!result)
689 {
690 g_main_context_iteration(main_context, TRUE);
691 }
692
693 gboolean can_execute = webkit_web_view_can_execute_editing_command_finish(m_web_view,
694 result,
695 NULL);
696 g_object_unref(result);
697
698 return can_execute != 0;
699 }
700
CanCut() const701 bool wxWebViewWebKit::CanCut() const
702 {
703 return CanExecuteEditingCommand(WEBKIT_EDITING_COMMAND_CUT);
704 }
705
CanCopy() const706 bool wxWebViewWebKit::CanCopy() const
707 {
708 return CanExecuteEditingCommand(WEBKIT_EDITING_COMMAND_COPY);
709 }
710
CanPaste() const711 bool wxWebViewWebKit::CanPaste() const
712 {
713 return CanExecuteEditingCommand(WEBKIT_EDITING_COMMAND_PASTE);
714 }
715
Cut()716 void wxWebViewWebKit::Cut()
717 {
718 webkit_web_view_execute_editing_command(m_web_view,
719 WEBKIT_EDITING_COMMAND_CUT);
720 }
721
Copy()722 void wxWebViewWebKit::Copy()
723 {
724 webkit_web_view_execute_editing_command(m_web_view,
725 WEBKIT_EDITING_COMMAND_COPY);
726 }
727
Paste()728 void wxWebViewWebKit::Paste()
729 {
730 webkit_web_view_execute_editing_command(m_web_view,
731 WEBKIT_EDITING_COMMAND_PASTE);
732 }
733
CanUndo() const734 bool wxWebViewWebKit::CanUndo() const
735 {
736 return CanExecuteEditingCommand(WEBKIT_EDITING_COMMAND_UNDO);
737 }
738
CanRedo() const739 bool wxWebViewWebKit::CanRedo() const
740 {
741 return CanExecuteEditingCommand(WEBKIT_EDITING_COMMAND_REDO);
742 }
743
Undo()744 void wxWebViewWebKit::Undo()
745 {
746 webkit_web_view_execute_editing_command(m_web_view,
747 WEBKIT_EDITING_COMMAND_UNDO);
748 }
749
Redo()750 void wxWebViewWebKit::Redo()
751 {
752 webkit_web_view_execute_editing_command(m_web_view,
753 WEBKIT_EDITING_COMMAND_REDO);
754 }
755
GetCurrentURL() const756 wxString wxWebViewWebKit::GetCurrentURL() const
757 {
758 // FIXME: check which encoding the web kit control uses instead of
759 // assuming UTF8 (here and elsewhere too)
760 return wxString::FromUTF8(webkit_web_view_get_uri(m_web_view));
761 }
762
763
GetCurrentTitle() const764 wxString wxWebViewWebKit::GetCurrentTitle() const
765 {
766 return wxString::FromUTF8(webkit_web_view_get_title(m_web_view));
767 }
768
769
wxgtk_web_resource_get_data_cb(WebKitWebResource *,GAsyncResult * res,GAsyncResult ** res_out)770 static void wxgtk_web_resource_get_data_cb(WebKitWebResource *,
771 GAsyncResult *res,
772 GAsyncResult **res_out)
773 {
774 *res_out = (GAsyncResult*)g_object_ref(res);
775 }
776
GetPageSource() const777 wxString wxWebViewWebKit::GetPageSource() const
778 {
779 WebKitWebResource *resource = webkit_web_view_get_main_resource(m_web_view);
780 if (!resource)
781 {
782 return wxString();
783 }
784
785 GAsyncResult *result = NULL;
786 webkit_web_resource_get_data(resource, NULL,
787 (GAsyncReadyCallback)wxgtk_web_resource_get_data_cb,
788 &result);
789
790 GMainContext *main_context = g_main_context_get_thread_default();
791 while (!result)
792 {
793 g_main_context_iteration(main_context, TRUE);
794 }
795
796 guchar *source = webkit_web_resource_get_data_finish(resource, result,
797 NULL, NULL);
798 if (result)
799 {
800 g_object_unref(result);
801 }
802
803 if (source)
804 {
805 wxString wxs = wxString(source, wxConvUTF8);
806 free(source);
807 return wxs;
808 }
809 return wxString();
810 }
811
812
GetZoom() const813 wxWebViewZoom wxWebViewWebKit::GetZoom() const
814 {
815 float zoom = GetWebkitZoom();
816
817 // arbitrary way to map float zoom to our common zoom enum
818 if (zoom <= 0.65)
819 {
820 return wxWEBVIEW_ZOOM_TINY;
821 }
822 if (zoom <= 0.90)
823 {
824 return wxWEBVIEW_ZOOM_SMALL;
825 }
826 if (zoom <= 1.15)
827 {
828 return wxWEBVIEW_ZOOM_MEDIUM;
829 }
830 if (zoom <= 1.45)
831 {
832 return wxWEBVIEW_ZOOM_LARGE;
833 }
834 return wxWEBVIEW_ZOOM_LARGEST;
835 }
836
837
SetZoom(wxWebViewZoom zoom)838 void wxWebViewWebKit::SetZoom(wxWebViewZoom zoom)
839 {
840 // arbitrary way to map our common zoom enum to float zoom
841 switch (zoom)
842 {
843 case wxWEBVIEW_ZOOM_TINY:
844 SetWebkitZoom(0.6f);
845 break;
846
847 case wxWEBVIEW_ZOOM_SMALL:
848 SetWebkitZoom(0.8f);
849 break;
850
851 case wxWEBVIEW_ZOOM_MEDIUM:
852 SetWebkitZoom(1.0f);
853 break;
854
855 case wxWEBVIEW_ZOOM_LARGE:
856 SetWebkitZoom(1.3);
857 break;
858
859 case wxWEBVIEW_ZOOM_LARGEST:
860 SetWebkitZoom(1.6);
861 break;
862
863 default:
864 wxFAIL;
865 }
866 }
867
SetZoomType(wxWebViewZoomType type)868 void wxWebViewWebKit::SetZoomType(wxWebViewZoomType type)
869 {
870 WebKitSettings* settings = webkit_web_view_get_settings(m_web_view);
871 webkit_settings_set_zoom_text_only(settings,
872 type == wxWEBVIEW_ZOOM_TYPE_TEXT);
873 }
874
GetZoomType() const875 wxWebViewZoomType wxWebViewWebKit::GetZoomType() const
876 {
877 WebKitSettings* settings = webkit_web_view_get_settings(m_web_view);
878 gboolean tozoom = webkit_settings_get_zoom_text_only(settings);
879
880 if (tozoom)
881 return wxWEBVIEW_ZOOM_TYPE_TEXT;
882 else
883 return wxWEBVIEW_ZOOM_TYPE_LAYOUT;
884 }
885
CanSetZoomType(wxWebViewZoomType) const886 bool wxWebViewWebKit::CanSetZoomType(wxWebViewZoomType) const
887 {
888 // this port supports all zoom types
889 return true;
890 }
891
DoSetPage(const wxString & html,const wxString & baseUri)892 void wxWebViewWebKit::DoSetPage(const wxString& html, const wxString& baseUri)
893 {
894 webkit_web_view_load_html(m_web_view,
895 html.mb_str(wxConvUTF8),
896 baseUri.mb_str(wxConvUTF8));
897 }
898
Print()899 void wxWebViewWebKit::Print()
900 {
901 WebKitPrintOperation* printop = webkit_print_operation_new(m_web_view);
902 webkit_print_operation_run_dialog(printop, NULL);
903 g_object_unref(printop);
904 }
905
906
IsBusy() const907 bool wxWebViewWebKit::IsBusy() const
908 {
909 return m_busy;
910 }
911
SetEditable(bool enable)912 void wxWebViewWebKit::SetEditable(bool enable)
913 {
914 webkit_web_view_set_editable(m_web_view, enable);
915 }
916
IsEditable() const917 bool wxWebViewWebKit::IsEditable() const
918 {
919 gboolean editable;
920 g_object_get(m_web_view, "editable", &editable, NULL);
921 return editable != 0;
922 }
923
DeleteSelection()924 void wxWebViewWebKit::DeleteSelection()
925 {
926 if (m_extension)
927 {
928 guint64 page_id = webkit_web_view_get_page_id(m_web_view);
929 GVariant *retval = g_dbus_proxy_call_sync(m_extension,
930 "DeleteSelection",
931 g_variant_new("(t)", page_id),
932 G_DBUS_CALL_FLAGS_NONE, -1,
933 NULL, NULL);
934 if (retval)
935 {
936 g_variant_unref(retval);
937 }
938 }
939 }
940
HasSelection() const941 bool wxWebViewWebKit::HasSelection() const
942 {
943 if (m_extension)
944 {
945 guint64 page_id = webkit_web_view_get_page_id(m_web_view);
946 GVariant *retval = g_dbus_proxy_call_sync(m_extension,
947 "HasSelection",
948 g_variant_new("(t)", page_id),
949 G_DBUS_CALL_FLAGS_NONE, -1,
950 NULL, NULL);
951 if (retval)
952 {
953 gboolean has_selection = FALSE;
954 g_variant_get(retval, "(b)", &has_selection);
955 g_variant_unref(retval);
956 return has_selection != 0;
957 }
958 }
959 return false;
960 }
961
SelectAll()962 void wxWebViewWebKit::SelectAll()
963 {
964 webkit_web_view_execute_editing_command(m_web_view,
965 WEBKIT_EDITING_COMMAND_SELECT_ALL);
966 }
967
GetSelectedText() const968 wxString wxWebViewWebKit::GetSelectedText() const
969 {
970 if (m_extension)
971 {
972 guint64 page_id = webkit_web_view_get_page_id(m_web_view);
973 GVariant *retval = g_dbus_proxy_call_sync(m_extension,
974 "GetSelectedText",
975 g_variant_new("(t)", page_id),
976 G_DBUS_CALL_FLAGS_NONE, -1,
977 NULL, NULL);
978 if (retval)
979 {
980 char *text;
981 g_variant_get(retval, "(s)", &text);
982 g_variant_unref(retval);
983 return wxString(text, wxConvUTF8);
984 }
985 }
986 return wxString();
987 }
988
GetSelectedSource() const989 wxString wxWebViewWebKit::GetSelectedSource() const
990 {
991 if (m_extension)
992 {
993 guint64 page_id = webkit_web_view_get_page_id(m_web_view);
994 GVariant *retval = g_dbus_proxy_call_sync(m_extension,
995 "GetSelectedSource",
996 g_variant_new("(t)", page_id),
997 G_DBUS_CALL_FLAGS_NONE, -1,
998 NULL, NULL);
999 if (retval)
1000 {
1001 char *source;
1002 g_variant_get(retval, "(s)", &source);
1003 g_variant_unref(retval);
1004 return wxString(source, wxConvUTF8);
1005 }
1006 }
1007 return wxString();
1008 }
1009
ClearSelection()1010 void wxWebViewWebKit::ClearSelection()
1011 {
1012 if (m_extension)
1013 {
1014 guint64 page_id = webkit_web_view_get_page_id(m_web_view);
1015 GVariant *retval = g_dbus_proxy_call_sync(m_extension,
1016 "ClearSelection",
1017 g_variant_new("(t)", page_id),
1018 G_DBUS_CALL_FLAGS_NONE, -1,
1019 NULL, NULL);
1020 if (retval)
1021 {
1022 g_variant_unref(retval);
1023 }
1024 }
1025 }
1026
GetPageText() const1027 wxString wxWebViewWebKit::GetPageText() const
1028 {
1029 if (m_extension)
1030 {
1031 guint64 page_id = webkit_web_view_get_page_id(m_web_view);
1032 GVariant *retval = g_dbus_proxy_call_sync(m_extension,
1033 "GetPageText",
1034 g_variant_new("(t)", page_id),
1035 G_DBUS_CALL_FLAGS_NONE, -1,
1036 NULL, NULL);
1037 if (retval)
1038 {
1039 char *text;
1040 g_variant_get(retval, "(s)", &text);
1041 g_variant_unref(retval);
1042 return wxString(text, wxConvUTF8);
1043 }
1044 }
1045 return wxString();
1046 }
1047
RunScript(const wxString & javascript)1048 void wxWebViewWebKit::RunScript(const wxString& javascript)
1049 {
1050 webkit_web_view_run_javascript(m_web_view,
1051 javascript.mb_str(wxConvUTF8),
1052 NULL,
1053 NULL,
1054 NULL);
1055 }
1056
RegisterHandler(wxSharedPtr<wxWebViewHandler> handler)1057 void wxWebViewWebKit::RegisterHandler(wxSharedPtr<wxWebViewHandler> handler)
1058 {
1059 m_handlerList.push_back(handler);
1060 WebKitWebContext* context = webkit_web_context_get_default();
1061 webkit_web_context_register_uri_scheme(context, handler->GetName().utf8_str(),
1062 (WebKitURISchemeRequestCallback)wxgtk_webview_webkit_uri_scheme_request_cb,
1063 this, NULL);
1064 }
1065
EnableContextMenu(bool enable)1066 void wxWebViewWebKit::EnableContextMenu(bool enable)
1067 {
1068 wxWebView::EnableContextMenu(enable);
1069 }
1070
Find(const wxString & text,int flags)1071 long wxWebViewWebKit::Find(const wxString& text, int flags)
1072 {
1073 WebKitFindController* findctrl = webkit_web_view_get_find_controller(m_web_view);
1074 bool newSearch = false;
1075 if(text != m_findText ||
1076 (flags & wxWEBVIEW_FIND_MATCH_CASE) != (m_findFlags & wxWEBVIEW_FIND_MATCH_CASE))
1077 {
1078 newSearch = true;
1079 //If it is a new search we need to clear existing highlights
1080 webkit_find_controller_search_finish(findctrl);
1081 }
1082
1083 m_findFlags = flags;
1084 m_findText = text;
1085
1086 //If the search string is empty then we clear any selection and highlight
1087 if(text.empty())
1088 {
1089 webkit_find_controller_search_finish(findctrl);
1090 ClearSelection();
1091 return wxNOT_FOUND;
1092 }
1093
1094 bool wrap = false, forward = true;
1095 guint32 options = WEBKIT_FIND_OPTIONS_NONE;
1096 if(flags & wxWEBVIEW_FIND_WRAP)
1097 {
1098 wrap = true;
1099 options |= WEBKIT_FIND_OPTIONS_WRAP_AROUND;
1100 }
1101 if(!(flags & wxWEBVIEW_FIND_MATCH_CASE))
1102 {
1103 options |= WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE;
1104 }
1105 if(flags & wxWEBVIEW_FIND_BACKWARDS)
1106 {
1107 forward = false;
1108 options |= WEBKIT_FIND_OPTIONS_BACKWARDS;
1109 }
1110
1111 if(newSearch)
1112 {
1113 //Initially we count the matches to know how many we have
1114 m_findCount = -1;
1115 webkit_find_controller_count_matches(findctrl,
1116 wxGTK_CONV(text),
1117 options,
1118 G_MAXUINT);
1119 GMainContext *main_context = g_main_context_get_thread_default();
1120 while (m_findCount == -1)
1121 {
1122 g_main_context_iteration(main_context, TRUE);
1123 }
1124 //Highlight them if needed
1125 if(flags & wxWEBVIEW_FIND_HIGHLIGHT_RESULT)
1126 {
1127 webkit_find_controller_search(findctrl,
1128 wxGTK_CONV(text),
1129 options,
1130 G_MAXUINT);
1131 }
1132 //In this case we return early to match IE behaviour
1133 m_findPosition = -1;
1134 return m_findCount;
1135 }
1136 else
1137 {
1138 if(forward)
1139 m_findPosition++;
1140 else
1141 m_findPosition--;
1142 if(m_findPosition < 0)
1143 m_findPosition += m_findCount;
1144 if(m_findPosition > m_findCount)
1145 m_findPosition -= m_findCount;
1146 }
1147
1148 if(forward)
1149 {
1150 webkit_find_controller_search_next(findctrl);
1151 if(m_findPosition == m_findCount && !wrap)
1152 {
1153 return wxNOT_FOUND;
1154 }
1155 }
1156 else
1157 {
1158 webkit_find_controller_search_previous(findctrl);
1159 if(m_findPosition == -1 && !wrap)
1160 {
1161 return wxNOT_FOUND;
1162 }
1163 }
1164
1165 return newSearch ? m_findCount : m_findPosition;
1166 }
1167
FindClear()1168 void wxWebViewWebKit::FindClear()
1169 {
1170 m_findCount = 0;
1171 m_findFlags = 0;
1172 m_findText.clear();
1173 m_findPosition = -1;
1174 }
1175
1176 // static
1177 wxVisualAttributes
GetClassDefaultAttributes(wxWindowVariant WXUNUSED (variant))1178 wxWebViewWebKit::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
1179 {
1180 return GetDefaultAttributesFromGTKWidget(webkit_web_view_new());
1181 }
1182
SetupWebExtensionServer()1183 void wxWebViewWebKit::SetupWebExtensionServer()
1184 {
1185 char *address = g_strdup_printf("unix:tmpdir=%s", g_get_tmp_dir());
1186 char *guid = g_dbus_generate_guid();
1187 GDBusAuthObserver *observer = g_dbus_auth_observer_new();
1188 GError *error = NULL;
1189
1190 g_signal_connect(observer, "authorize-authenticated-peer",
1191 G_CALLBACK(wxgtk_authorize_authenticated_peer_cb), this);
1192
1193 m_dbusServer = g_dbus_server_new_sync(address,
1194 G_DBUS_SERVER_FLAGS_NONE,
1195 guid,
1196 observer,
1197 NULL,
1198 &error);
1199
1200 if (error)
1201 {
1202 g_warning("Failed to start web extension server on %s: %s", address, error->message);
1203 g_error_free(error);
1204 }
1205 else
1206 {
1207 g_signal_connect(m_dbusServer, "new-connection",
1208 G_CALLBACK(wxgtk_new_connection_cb), &m_extension);
1209 g_dbus_server_start(m_dbusServer);
1210 }
1211
1212 g_free(address);
1213 g_free(guid);
1214 g_object_unref(observer);
1215 }
1216
1217 #endif // wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT2
1218