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