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/dir.h"
15 #include "wx/dynlib.h"
16 #include "wx/filename.h"
17 #include "wx/stdpaths.h"
18 #include "wx/stockitem.h"
19 #include "wx/gtk/webview_webkit.h"
20 #include "wx/gtk/control.h"
21 #include "wx/gtk/private.h"
22 #include "wx/filesys.h"
23 #include "wx/base64.h"
24 #include "wx/log.h"
25 #include "wx/gtk/private/webview_webkit2_extension.h"
26 #include "wx/gtk/private/string.h"
27 #include "wx/gtk/private/webkit.h"
28 #include "wx/gtk/private/error.h"
29 #include "wx/private/jsscriptwrapper.h"
30 #include <webkit2/webkit2.h>
31 #include <JavaScriptCore/JSValueRef.h>
32 #include <JavaScriptCore/JSStringRef.h>
33 
34 // Helper function to get string from Webkit JS result
wxGetStringFromJSResult(WebKitJavascriptResult * js_result,wxString * output)35 bool wxGetStringFromJSResult(WebKitJavascriptResult* js_result, wxString* output)
36 {
37     JSGlobalContextRef context = webkit_javascript_result_get_global_context(js_result);
38     JSValueRef value = webkit_javascript_result_get_value(js_result);
39 
40     JSValueRef exception = NULL;
41     wxJSStringRef js_value
42                   (
43                    JSValueIsObject(context, value)
44                        ? JSValueCreateJSONString(context, value, 0, &exception)
45                        : JSValueToStringCopy(context, value, &exception)
46                   );
47 
48     if ( exception )
49     {
50         if ( output )
51         {
52             wxJSStringRef ex_value(JSValueToStringCopy(context, exception, NULL));
53             *output = ex_value.ToWxString();
54         }
55 
56         return false;
57     }
58 
59     if ( output != NULL )
60         *output = js_value.ToWxString();
61 
62     return true;
63 }
64 
65 // ----------------------------------------------------------------------------
66 // GTK callbacks
67 // ----------------------------------------------------------------------------
68 
69 extern "C"
70 {
71 
72 static void
wxgtk_webview_webkit_load_changed(GtkWidget *,WebKitLoadEvent load_event,wxWebViewWebKit * webKitCtrl)73 wxgtk_webview_webkit_load_changed(GtkWidget *,
74                                   WebKitLoadEvent load_event,
75                                   wxWebViewWebKit *webKitCtrl)
76 {
77     wxString url = webKitCtrl->GetCurrentURL();
78 
79     wxString target; // TODO: get target (if possible)
80 
81     if (load_event == WEBKIT_LOAD_FINISHED)
82     {
83         webKitCtrl->m_busy = false;
84         wxWebViewEvent event(wxEVT_WEBVIEW_LOADED,
85                              webKitCtrl->GetId(),
86                              url, target);
87         event.SetEventObject(webKitCtrl);
88 
89         webKitCtrl->HandleWindowEvent(event);
90     }
91     else if (load_event == WEBKIT_LOAD_COMMITTED)
92     {
93         webKitCtrl->m_busy = true;
94         wxWebViewEvent event(wxEVT_WEBVIEW_NAVIGATED,
95                              webKitCtrl->GetId(),
96                              url, target);
97         event.SetEventObject(webKitCtrl);
98 
99         webKitCtrl->HandleWindowEvent(event);
100     }
101 }
102 
103 static gboolean
wxgtk_webview_webkit_navigation(WebKitWebView *,WebKitPolicyDecision * decision,wxWebViewWebKit * webKitCtrl)104 wxgtk_webview_webkit_navigation(WebKitWebView *,
105                                 WebKitPolicyDecision *decision,
106                                 wxWebViewWebKit *webKitCtrl)
107 {
108     WebKitNavigationPolicyDecision* navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION(decision);
109     WebKitNavigationAction* action = webkit_navigation_policy_decision_get_navigation_action(navigation_decision);
110     WebKitURIRequest* request = webkit_navigation_action_get_request(action);
111     const gchar* uri = webkit_uri_request_get_uri(request);
112     wxString target = webkit_navigation_policy_decision_get_frame_name(navigation_decision);
113 
114     //If m_creating is true then we are the result of a new window
115     //and so we need to send the event and veto the load
116     if(webKitCtrl->m_creating)
117     {
118         webKitCtrl->m_creating = false;
119         wxWebViewEvent event(wxEVT_WEBVIEW_NEWWINDOW,
120                              webKitCtrl->GetId(),
121                              wxString(uri, wxConvUTF8),
122                              target);
123         event.SetEventObject(webKitCtrl);
124 
125         webKitCtrl->HandleWindowEvent(event);
126 
127         webkit_policy_decision_ignore(decision);
128         return TRUE;
129     }
130 
131     webKitCtrl->m_busy = true;
132 
133     wxWebViewEvent event(wxEVT_WEBVIEW_NAVIGATING,
134                          webKitCtrl->GetId(),
135                          wxString( uri, wxConvUTF8 ),
136                          target);
137     event.SetEventObject(webKitCtrl);
138 
139     webKitCtrl->HandleWindowEvent(event);
140 
141     if (!event.IsAllowed())
142     {
143         webKitCtrl->m_busy = false;
144         webkit_policy_decision_ignore(decision);
145         return TRUE;
146     }
147     else
148     {
149         return FALSE;
150     }
151 }
152 
153 static gboolean
wxgtk_webview_webkit_load_failed(WebKitWebView *,WebKitLoadEvent,gchar * uri,GError * error,wxWebViewWebKit * webKitWindow)154 wxgtk_webview_webkit_load_failed(WebKitWebView *,
155                                  WebKitLoadEvent,
156                                  gchar *uri,
157                                  GError *error,
158                                  wxWebViewWebKit* webKitWindow)
159 {
160     webKitWindow->m_busy = false;
161     wxWebViewNavigationError type = wxWEBVIEW_NAV_ERR_OTHER;
162 
163     wxString description(error->message, wxConvUTF8);
164 
165     if (strcmp(g_quark_to_string(error->domain), "soup_http_error_quark") == 0)
166     {
167         switch (error->code)
168         {
169             case SOUP_STATUS_CANCELLED:
170                 type = wxWEBVIEW_NAV_ERR_USER_CANCELLED;
171                 break;
172 
173             case SOUP_STATUS_CANT_RESOLVE:
174             case SOUP_STATUS_NOT_FOUND:
175                 type = wxWEBVIEW_NAV_ERR_NOT_FOUND;
176                 break;
177 
178             case SOUP_STATUS_CANT_RESOLVE_PROXY:
179             case SOUP_STATUS_CANT_CONNECT:
180             case SOUP_STATUS_CANT_CONNECT_PROXY:
181             case SOUP_STATUS_SSL_FAILED:
182             case SOUP_STATUS_IO_ERROR:
183                 type = wxWEBVIEW_NAV_ERR_CONNECTION;
184                 break;
185 
186             case SOUP_STATUS_MALFORMED:
187                 type = wxWEBVIEW_NAV_ERR_REQUEST;
188                 break;
189 
190             case SOUP_STATUS_BAD_REQUEST:
191                 type = wxWEBVIEW_NAV_ERR_REQUEST;
192                 break;
193 
194             case SOUP_STATUS_UNAUTHORIZED:
195             case SOUP_STATUS_FORBIDDEN:
196                 type = wxWEBVIEW_NAV_ERR_AUTH;
197                 break;
198 
199             case SOUP_STATUS_METHOD_NOT_ALLOWED:
200             case SOUP_STATUS_NOT_ACCEPTABLE:
201                 type = wxWEBVIEW_NAV_ERR_SECURITY;
202                 break;
203 
204             case SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED:
205                 type = wxWEBVIEW_NAV_ERR_AUTH;
206                 break;
207 
208             case SOUP_STATUS_REQUEST_TIMEOUT:
209                 type = wxWEBVIEW_NAV_ERR_CONNECTION;
210                 break;
211 
212             case SOUP_STATUS_REQUEST_ENTITY_TOO_LARGE:
213             case SOUP_STATUS_REQUEST_URI_TOO_LONG:
214             case SOUP_STATUS_UNSUPPORTED_MEDIA_TYPE:
215                 type = wxWEBVIEW_NAV_ERR_REQUEST;
216                 break;
217 
218             case SOUP_STATUS_BAD_GATEWAY:
219             case SOUP_STATUS_SERVICE_UNAVAILABLE:
220             case SOUP_STATUS_GATEWAY_TIMEOUT:
221                 type = wxWEBVIEW_NAV_ERR_CONNECTION;
222                 break;
223 
224             case SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED:
225                 type = wxWEBVIEW_NAV_ERR_REQUEST;
226                 break;
227         }
228     }
229     else if (strcmp(g_quark_to_string(error->domain),
230                     "webkit-network-error-quark") == 0)
231     {
232         switch (error->code)
233         {
234             case WEBKIT_NETWORK_ERROR_UNKNOWN_PROTOCOL:
235                 type = wxWEBVIEW_NAV_ERR_REQUEST;
236                 break;
237 
238             case WEBKIT_NETWORK_ERROR_CANCELLED:
239                 type = wxWEBVIEW_NAV_ERR_USER_CANCELLED;
240                 break;
241 
242             case WEBKIT_NETWORK_ERROR_FILE_DOES_NOT_EXIST:
243                 type = wxWEBVIEW_NAV_ERR_NOT_FOUND;
244                 break;
245         }
246     }
247     else if (strcmp(g_quark_to_string(error->domain),
248                     "webkit-policy-error-quark") == 0)
249     {
250         switch (error->code)
251         {
252             case WEBKIT_POLICY_ERROR_CANNOT_USE_RESTRICTED_PORT:
253                 type = wxWEBVIEW_NAV_ERR_SECURITY;
254                 break;
255         }
256     }
257 
258     wxWebViewEvent event(wxEVT_WEBVIEW_ERROR,
259                          webKitWindow->GetId(),
260                          uri, "");
261     event.SetEventObject(webKitWindow);
262     event.SetString(description);
263     event.SetInt(type);
264 
265 
266     webKitWindow->HandleWindowEvent(event);
267 
268     return FALSE;
269 }
270 
271 static gboolean
wxgtk_webview_webkit_new_window(WebKitPolicyDecision * decision,wxWebViewWebKit * webKitCtrl)272 wxgtk_webview_webkit_new_window(WebKitPolicyDecision *decision,
273                                 wxWebViewWebKit *webKitCtrl)
274 {
275     WebKitNavigationPolicyDecision* navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION(decision);
276     WebKitNavigationAction* action = webkit_navigation_policy_decision_get_navigation_action(navigation_decision);
277     WebKitURIRequest* request = webkit_navigation_action_get_request(action);
278     const gchar* uri = webkit_uri_request_get_uri(request);
279 
280     wxString target = webkit_navigation_policy_decision_get_frame_name(navigation_decision);
281     wxWebViewEvent event(wxEVT_WEBVIEW_NEWWINDOW,
282                                        webKitCtrl->GetId(),
283                                        wxString( uri, wxConvUTF8 ),
284                                        target);
285     event.SetEventObject(webKitCtrl);
286 
287     webKitCtrl->HandleWindowEvent(event);
288 
289     //We always want the user to handle this themselves
290     webkit_policy_decision_ignore(decision);
291     return TRUE;
292 }
293 
294 static gboolean
wxgtk_webview_webkit_enter_fullscreen(WebKitWebView * WXUNUSED (web_view),wxWebViewWebKit * webKitCtrl)295 wxgtk_webview_webkit_enter_fullscreen(WebKitWebView *WXUNUSED(web_view),
296                                       wxWebViewWebKit *webKitCtrl)
297 {
298     wxWebViewEvent event(wxEVT_WEBVIEW_FULLSCREEN_CHANGED,
299                                        webKitCtrl->GetId(),
300                                        wxString(),
301                                        wxString());
302     event.SetEventObject(webKitCtrl);
303     event.SetInt(1);
304     webKitCtrl->HandleWindowEvent(event);
305 
306     return FALSE;
307 }
308 
309 static gboolean
wxgtk_webview_webkit_leave_fullscreen(WebKitWebView * WXUNUSED (web_view),wxWebViewWebKit * webKitCtrl)310 wxgtk_webview_webkit_leave_fullscreen(WebKitWebView *WXUNUSED(web_view),
311                                       wxWebViewWebKit *webKitCtrl)
312 {
313     wxWebViewEvent event(wxEVT_WEBVIEW_FULLSCREEN_CHANGED,
314                                        webKitCtrl->GetId(),
315                                        wxString(),
316                                        wxString());
317     event.SetEventObject(webKitCtrl);
318     event.SetInt(0);
319     webKitCtrl->HandleWindowEvent(event);
320 
321     return FALSE;
322 }
323 
324 static void
wxgtk_webview_webkit_script_message_received(WebKitUserContentManager * WXUNUSED (content_manager),WebKitJavascriptResult * js_result,wxWebViewWebKit * webKitCtrl)325 wxgtk_webview_webkit_script_message_received(WebKitUserContentManager *WXUNUSED(content_manager),
326                                              WebKitJavascriptResult *js_result,
327                                              wxWebViewWebKit *webKitCtrl)
328 {
329     wxWebViewEvent event(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED,
330                          webKitCtrl->GetId(),
331                          webKitCtrl->GetCurrentURL(),
332                          "");
333     wxString msgStr;
334     if (wxGetStringFromJSResult(js_result, &msgStr))
335         event.SetString(msgStr);
336     webKitCtrl->HandleWindowEvent(event);
337 }
338 
339 static gboolean
wxgtk_webview_webkit_decide_policy(WebKitWebView * web_view,WebKitPolicyDecision * decision,WebKitPolicyDecisionType type,wxWebViewWebKit * webKitCtrl)340 wxgtk_webview_webkit_decide_policy(WebKitWebView *web_view,
341                                    WebKitPolicyDecision *decision,
342                                    WebKitPolicyDecisionType type,
343                                    wxWebViewWebKit *webKitCtrl)
344 {
345     switch (type)
346     {
347         case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
348             return wxgtk_webview_webkit_navigation(web_view, decision, webKitCtrl);
349         case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
350             return wxgtk_webview_webkit_new_window(decision, webKitCtrl);
351         default:
352             return FALSE;
353     }
354 }
355 
356 static void
wxgtk_webview_webkit_title_changed(GtkWidget * widget,GParamSpec *,wxWebViewWebKit * webKitCtrl)357 wxgtk_webview_webkit_title_changed(GtkWidget* widget,
358                                    GParamSpec *,
359                                    wxWebViewWebKit *webKitCtrl)
360 {
361     gchar *title;
362     g_object_get(G_OBJECT(widget), "title", &title, NULL);
363 
364     wxWebViewEvent event(wxEVT_WEBVIEW_TITLE_CHANGED,
365                          webKitCtrl->GetId(),
366                          webKitCtrl->GetCurrentURL(),
367                          "");
368     event.SetEventObject(webKitCtrl);
369     event.SetString(wxString(title, wxConvUTF8));
370 
371     webKitCtrl->HandleWindowEvent(event);
372 
373     g_free(title);
374 }
375 
376 static void
wxgtk_webview_webkit_uri_scheme_request_cb(WebKitURISchemeRequest * request,wxWebViewWebKit * webKitCtrl)377 wxgtk_webview_webkit_uri_scheme_request_cb(WebKitURISchemeRequest *request,
378                                            wxWebViewWebKit *webKitCtrl)
379 {
380     const wxString scheme = wxString::FromUTF8(webkit_uri_scheme_request_get_scheme(request));
381 
382     wxSharedPtr<wxWebViewHandler> handler;
383     wxVector<wxSharedPtr<wxWebViewHandler> > handlers = webKitCtrl->GetHandlers();
384 
385     for(wxVector<wxSharedPtr<wxWebViewHandler> >::iterator it = handlers.begin();
386         it != handlers.end(); ++it)
387     {
388         if(scheme == (*it)->GetName())
389         {
390             handler = (*it);
391         }
392     }
393 
394     if(handler)
395     {
396         const wxString uri = wxString::FromUTF8(webkit_uri_scheme_request_get_uri(request));
397 
398         wxFSFile* file = handler->GetFile(uri);
399         if(file)
400         {
401             gint64 length = file->GetStream()->GetLength();
402             guint8 *data = g_new(guint8, length);
403             file->GetStream()->Read(data, length);
404             GInputStream *stream = g_memory_input_stream_new_from_data(data,
405                                                                        length,
406                                                                        g_free);
407             wxString mime = file->GetMimeType();
408             webkit_uri_scheme_request_finish(request, stream, length, mime.utf8_str());
409         }
410         else
411         {
412             wxGtkError error(g_error_new(WEBKIT_NETWORK_ERROR,
413                                          WEBKIT_NETWORK_ERROR_FILE_DOES_NOT_EXIST,
414                                          "File not found: %s", uri.utf8_str().data()));
415             webkit_uri_scheme_request_finish_error(request, error);
416         }
417     }
418     else
419     {
420         wxGtkError error(g_error_new(WEBKIT_NETWORK_ERROR,
421                                      WEBKIT_NETWORK_ERROR_UNKNOWN_PROTOCOL,
422                                      "Unknown scheme: %s", scheme.utf8_str().data()));
423         webkit_uri_scheme_request_finish_error(request, error);
424     }
425 }
426 
427 static gboolean
wxgtk_webview_webkit_context_menu(WebKitWebView *,WebKitContextMenu *,GdkEvent *,WebKitHitTestResult *,wxWebViewWebKit * webKitCtrl)428 wxgtk_webview_webkit_context_menu(WebKitWebView *,
429                                   WebKitContextMenu *,
430                                   GdkEvent *,
431                                   WebKitHitTestResult *,
432                                   wxWebViewWebKit *webKitCtrl)
433 {
434     return !webKitCtrl->IsContextMenuEnabled();
435 }
436 
437 static WebKitWebView*
wxgtk_webview_webkit_create_webview(WebKitWebView * web_view,WebKitNavigationAction *,wxWebViewWebKit * webKitCtrl)438 wxgtk_webview_webkit_create_webview(WebKitWebView *web_view,
439                                     WebKitNavigationAction *,
440                                     wxWebViewWebKit *webKitCtrl)
441 {
442     //As we do not know the uri being loaded at this point allow the load to
443     //continue and catch it in navigation-policy-decision-requested
444     webKitCtrl->m_creating = true;
445     return web_view;
446 }
447 
448 static void
wxgtk_webview_webkit_counted_matches(WebKitFindController *,guint match_count,int * findCount)449 wxgtk_webview_webkit_counted_matches(WebKitFindController *,
450                                      guint match_count,
451                                      int *findCount)
452 {
453     *findCount = match_count;
454 }
455 
456 // This function checks if the specified directory contains our web extension.
CheckDirectoryForWebExt(const wxString & dirname)457 static bool CheckDirectoryForWebExt(const wxString& dirname)
458 {
459     wxDir dir;
460     if ( !wxDir::Exists(dirname) || !dir.Open(dirname) )
461         return false;
462 
463     wxString file;
464     bool cont = dir.GetFirst
465                     (
466                         &file,
467                         "webkit2_ext*" + wxDynamicLibrary::GetDllExt(wxDL_MODULE),
468                         wxDIR_FILES
469                     );
470     while ( cont )
471     {
472         wxDynamicLibrary dl;
473         if ( dl.Load(wxFileName(dirname, file).GetFullPath(),
474                      wxDL_VERBATIM | wxDL_LAZY) &&
475                 dl.HasSymbol("webkit_web_extension_initialize_with_user_data") )
476         {
477             // Looks like our extension.
478             return true;
479         }
480 
481         cont = dir.GetNext(&file);
482     }
483 
484     return false;
485 }
486 
TrySetWebExtensionsDirectory(WebKitWebContext * context,const wxString & dir)487 static bool TrySetWebExtensionsDirectory(WebKitWebContext *context, const wxString& dir)
488 {
489     if (dir.empty() || !CheckDirectoryForWebExt(dir))
490         return false;
491 
492     webkit_web_context_set_web_extensions_directory(context, dir.utf8_str());
493     return true;
494 }
495 
GetStandardWebExtensionsDir()496 static wxString GetStandardWebExtensionsDir()
497 {
498     wxString dir = wxDynamicLibrary::GetPluginsDirectory();
499     if ( !dir.empty() )
500         dir += "/web-extensions";
501     return dir;
502 }
503 
504 static void
wxgtk_initialize_web_extensions(WebKitWebContext * context,GDBusServer * dbusServer)505 wxgtk_initialize_web_extensions(WebKitWebContext *context,
506                                 GDBusServer *dbusServer)
507 {
508     const char *address = g_dbus_server_get_client_address(dbusServer);
509     GVariant *user_data = g_variant_new("(s)", address);
510 
511     // Try to setup extension loading from the location it is supposed to be
512     // normally installed in.
513     if ( !TrySetWebExtensionsDirectory(context, GetStandardWebExtensionsDir()) )
514     {
515         // These relative locations are used as fallbacks to allow running
516         // the tests and sample using wxWebView before installing it.
517         wxString exepath = wxFileName(wxStandardPaths::Get().GetExecutablePath()).GetPath();
518         if ( !exepath.empty() )
519         {
520             wxString const directories[] =
521             {
522                 exepath + "/..",
523                 exepath + "/../..",
524                 exepath + "/lib",
525             };
526 
527             for ( size_t n = 0; n < WXSIZEOF(directories); ++n )
528             {
529                 if ( !TrySetWebExtensionsDirectory(context, directories[n]) )
530                     break;
531             }
532         }
533     }
534 
535     webkit_web_context_set_web_extensions_initialization_user_data(context,
536                                                                    user_data);
537 }
538 
539 static gboolean
wxgtk_new_connection_cb(GDBusServer *,GDBusConnection * connection,GDBusProxy ** proxy)540 wxgtk_new_connection_cb(GDBusServer *,
541                         GDBusConnection *connection,
542                         GDBusProxy **proxy)
543 {
544     GError *error = NULL;
545     GDBusProxyFlags flags = GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS);
546     *proxy = g_dbus_proxy_new_sync(connection,
547                                    flags,
548                                    NULL,
549                                    NULL,
550                                    WXGTK_WEB_EXTENSION_OBJECT_PATH,
551                                    WXGTK_WEB_EXTENSION_INTERFACE,
552                                    NULL,
553                                    &error);
554     if (error)
555     {
556         g_warning("Failed to create dbus proxy: %s", error->message);
557         g_error_free(error);
558     }
559 
560     return TRUE;
561 }
562 
563 static
564 gboolean
wxgtk_dbus_peer_is_authorized(GCredentials * peer_credentials)565 wxgtk_dbus_peer_is_authorized(GCredentials *peer_credentials)
566 {
567     static GCredentials *own_credentials = g_credentials_new();
568     GError *error = NULL;
569 
570     if (peer_credentials && g_credentials_is_same_user(peer_credentials, own_credentials, &error))
571     {
572         return TRUE;
573     }
574 
575     if (error)
576     {
577         g_warning("Failed to authorize web extension connection: %s", error->message);
578         g_error_free(error);
579     }
580     return FALSE;
581 }
582 
583 static gboolean
wxgtk_authorize_authenticated_peer_cb(GDBusAuthObserver *,GIOStream *,GCredentials * credentials,wxWebViewWebKit *)584 wxgtk_authorize_authenticated_peer_cb(GDBusAuthObserver *,
585                                       GIOStream *,
586                                       GCredentials *credentials,
587                                       wxWebViewWebKit *)
588 {
589     return wxgtk_dbus_peer_is_authorized(credentials);
590 }
591 
592 } // extern "C"
593 
594 //-----------------------------------------------------------------------------
595 // wxWebViewFactoryWebKit
596 //-----------------------------------------------------------------------------
597 
GetVersionInfo()598 wxVersionInfo wxWebViewFactoryWebKit::GetVersionInfo()
599 {
600     return wxVersionInfo("webkit2", webkit_get_major_version(),
601         webkit_get_minor_version(), webkit_get_micro_version());
602 }
603 
604 //-----------------------------------------------------------------------------
605 // wxWebViewWebKit
606 //-----------------------------------------------------------------------------
607 
608 wxIMPLEMENT_DYNAMIC_CLASS(wxWebViewWebKit, wxWebView);
609 
wxWebViewWebKit()610 wxWebViewWebKit::wxWebViewWebKit()
611 {
612     m_web_view = NULL;
613     m_dbusServer = NULL;
614     m_extension = NULL;
615 }
616 
Create(wxWindow * parent,wxWindowID id,const wxString & url,const wxPoint & pos,const wxSize & size,long style,const wxString & name)617 bool wxWebViewWebKit::Create(wxWindow *parent,
618                       wxWindowID id,
619                       const wxString &url,
620                       const wxPoint& pos,
621                       const wxSize& size,
622                       long style,
623                       const wxString& name)
624 {
625     m_web_view = NULL;
626     m_dbusServer = NULL;
627     m_extension = NULL;
628     m_busy = false;
629     m_guard = false;
630     m_creating = false;
631     FindClear();
632 
633     // We currently unconditionally impose scrolling in both directions as it's
634     // necessary to show arbitrary pages.
635     style |= wxHSCROLL | wxVSCROLL;
636 
637     if (!PreCreation( parent, pos, size ) ||
638         !CreateBase( parent, id, pos, size, style, wxDefaultValidator, name ))
639     {
640         wxFAIL_MSG( wxT("wxWebViewWebKit creation failed") );
641         return false;
642     }
643 
644     SetupWebExtensionServer();
645     g_signal_connect(webkit_web_context_get_default(),
646                      "initialize-web-extensions",
647                      G_CALLBACK(wxgtk_initialize_web_extensions),
648                      m_dbusServer);
649 
650     m_web_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
651     GTKCreateScrolledWindowWith(GTK_WIDGET(m_web_view));
652     g_object_ref(m_widget);
653 
654     if (!m_customUserAgent.empty())
655         SetUserAgent(m_customUserAgent);
656 
657     g_signal_connect(m_web_view, "decide-policy",
658                      G_CALLBACK(wxgtk_webview_webkit_decide_policy),
659                      this);
660 
661     g_signal_connect(m_web_view, "load-failed",
662                      G_CALLBACK(wxgtk_webview_webkit_load_failed), this);
663 
664     g_signal_connect(m_web_view, "notify::title",
665                      G_CALLBACK(wxgtk_webview_webkit_title_changed), this);
666 
667     g_signal_connect(m_web_view, "context-menu",
668                      G_CALLBACK(wxgtk_webview_webkit_context_menu), this);
669 
670     g_signal_connect(m_web_view, "create",
671                      G_CALLBACK(wxgtk_webview_webkit_create_webview), this);
672 
673     g_signal_connect(m_web_view, "enter-fullscreen",
674                      G_CALLBACK(wxgtk_webview_webkit_enter_fullscreen), this);
675 
676     g_signal_connect(m_web_view, "leave-fullscreen",
677                      G_CALLBACK(wxgtk_webview_webkit_leave_fullscreen), this);
678 
679     WebKitFindController* findctrl = webkit_web_view_get_find_controller(m_web_view);
680     g_signal_connect(findctrl, "counted-matches",
681                      G_CALLBACK(wxgtk_webview_webkit_counted_matches),
682                      &m_findCount);
683 
684     m_parent->DoAddChild( this );
685 
686     PostCreation(size);
687 
688     /* Open a webpage */
689     webkit_web_view_load_uri(m_web_view, url.utf8_str());
690 
691     // last to avoid getting signal too early
692     g_signal_connect(m_web_view, "load-changed",
693                      G_CALLBACK(wxgtk_webview_webkit_load_changed),
694                      this);
695 
696     return true;
697 }
698 
~wxWebViewWebKit()699 wxWebViewWebKit::~wxWebViewWebKit()
700 {
701     if (m_web_view)
702         GTKDisconnect(m_web_view);
703     if (m_dbusServer)
704     {
705         g_dbus_server_stop(m_dbusServer);
706         g_signal_handlers_disconnect_by_data(
707             webkit_web_context_get_default(), m_dbusServer);
708     }
709     g_clear_object(&m_dbusServer);
710     g_clear_object(&m_extension);
711 }
712 
Enable(bool enable)713 bool wxWebViewWebKit::Enable( bool enable )
714 {
715     if (!wxControl::Enable(enable))
716         return false;
717 
718     gtk_widget_set_sensitive(gtk_bin_get_child(GTK_BIN(m_widget)), enable);
719 
720     return true;
721 }
722 
723 GdkWindow*
GTKGetWindow(wxArrayGdkWindows & WXUNUSED (windows)) const724 wxWebViewWebKit::GTKGetWindow(wxArrayGdkWindows& WXUNUSED(windows)) const
725 {
726     GdkWindow* window = gtk_widget_get_parent_window(m_widget);
727     return window;
728 }
729 
ZoomIn()730 void wxWebViewWebKit::ZoomIn()
731 {
732     SetWebkitZoom(GetWebkitZoom() + 0.1f);
733 }
734 
ZoomOut()735 void wxWebViewWebKit::ZoomOut()
736 {
737     SetWebkitZoom(GetWebkitZoom() - 0.1f);
738 }
739 
SetWebkitZoom(float level)740 void wxWebViewWebKit::SetWebkitZoom(float level)
741 {
742     webkit_web_view_set_zoom_level(m_web_view, double(level));
743 }
744 
GetWebkitZoom() const745 float wxWebViewWebKit::GetWebkitZoom() const
746 {
747     return webkit_web_view_get_zoom_level(m_web_view);
748 }
749 
EnableAccessToDevTools(bool enable)750 void wxWebViewWebKit::EnableAccessToDevTools(bool enable)
751 {
752     WebKitSettings* settings = webkit_web_view_get_settings(m_web_view);
753     webkit_settings_set_enable_developer_extras(settings, enable);
754 }
755 
IsAccessToDevToolsEnabled() const756 bool wxWebViewWebKit::IsAccessToDevToolsEnabled() const
757 {
758     WebKitSettings* settings = webkit_web_view_get_settings(m_web_view);
759     return webkit_settings_get_enable_developer_extras(settings);
760 }
761 
SetUserAgent(const wxString & userAgent)762 bool wxWebViewWebKit::SetUserAgent(const wxString& userAgent)
763 {
764     if (m_web_view)
765     {
766         WebKitSettings* settings = webkit_web_view_get_settings(m_web_view);
767         webkit_settings_set_user_agent(settings, userAgent.utf8_str());
768     }
769     else
770         m_customUserAgent = userAgent;
771     return true;
772 }
773 
Stop()774 void wxWebViewWebKit::Stop()
775 {
776      webkit_web_view_stop_loading(m_web_view);
777 }
778 
Reload(wxWebViewReloadFlags flags)779 void wxWebViewWebKit::Reload(wxWebViewReloadFlags flags)
780 {
781     if (flags & wxWEBVIEW_RELOAD_NO_CACHE)
782     {
783         webkit_web_view_reload_bypass_cache(m_web_view);
784     }
785     else
786     {
787         webkit_web_view_reload(m_web_view);
788     }
789 }
790 
LoadURL(const wxString & url)791 void wxWebViewWebKit::LoadURL(const wxString& url)
792 {
793     webkit_web_view_load_uri(m_web_view, wxGTK_CONV(url));
794 }
795 
796 
GoBack()797 void wxWebViewWebKit::GoBack()
798 {
799     webkit_web_view_go_back(m_web_view);
800 }
801 
GoForward()802 void wxWebViewWebKit::GoForward()
803 {
804     webkit_web_view_go_forward(m_web_view);
805 }
806 
807 
CanGoBack() const808 bool wxWebViewWebKit::CanGoBack() const
809 {
810     return webkit_web_view_can_go_back(m_web_view) != 0;
811 }
812 
813 
CanGoForward() const814 bool wxWebViewWebKit::CanGoForward() const
815 {
816     return webkit_web_view_can_go_forward(m_web_view) != 0;
817 }
818 
ClearHistory()819 void wxWebViewWebKit::ClearHistory()
820 {
821     // In WebKit2GTK+, the BackForwardList can't be cleared so do nothing.
822 }
823 
EnableHistory(bool)824 void wxWebViewWebKit::EnableHistory(bool)
825 {
826     // In WebKit2GTK+, history can't be disabled so do nothing here.
827 }
828 
GetBackwardHistory()829 wxVector<wxSharedPtr<wxWebViewHistoryItem> > wxWebViewWebKit::GetBackwardHistory()
830 {
831     wxVector<wxSharedPtr<wxWebViewHistoryItem> > backhist;
832     WebKitBackForwardList* history =
833         webkit_web_view_get_back_forward_list(m_web_view);
834     GList* list = webkit_back_forward_list_get_back_list(history);
835     //We need to iterate in reverse to get the order we desire
836     for(int i = g_list_length(list) - 1; i >= 0 ; i--)
837     {
838         WebKitBackForwardListItem* gtkitem = (WebKitBackForwardListItem*)g_list_nth_data(list, i);
839         wxWebViewHistoryItem* wxitem = new wxWebViewHistoryItem(
840                               webkit_back_forward_list_item_get_uri(gtkitem),
841                               webkit_back_forward_list_item_get_title(gtkitem));
842         wxitem->m_histItem = gtkitem;
843         wxSharedPtr<wxWebViewHistoryItem> item(wxitem);
844         backhist.push_back(item);
845     }
846     return backhist;
847 }
848 
GetForwardHistory()849 wxVector<wxSharedPtr<wxWebViewHistoryItem> > wxWebViewWebKit::GetForwardHistory()
850 {
851     wxVector<wxSharedPtr<wxWebViewHistoryItem> > forwardhist;
852     WebKitBackForwardList* history =
853         webkit_web_view_get_back_forward_list(m_web_view);
854     GList* list = webkit_back_forward_list_get_forward_list(history);
855     for(guint i = 0; i < g_list_length(list); i++)
856     {
857         WebKitBackForwardListItem* gtkitem = (WebKitBackForwardListItem*)g_list_nth_data(list, i);
858         wxWebViewHistoryItem* wxitem = new wxWebViewHistoryItem(
859                               webkit_back_forward_list_item_get_uri(gtkitem),
860                               webkit_back_forward_list_item_get_title(gtkitem));
861         wxitem->m_histItem = gtkitem;
862         wxSharedPtr<wxWebViewHistoryItem> item(wxitem);
863         forwardhist.push_back(item);
864     }
865     return forwardhist;
866 }
867 
LoadHistoryItem(wxSharedPtr<wxWebViewHistoryItem> item)868 void wxWebViewWebKit::LoadHistoryItem(wxSharedPtr<wxWebViewHistoryItem> item)
869 {
870     WebKitBackForwardListItem* gtkitem = (WebKitBackForwardListItem*)item->m_histItem;
871     if(gtkitem)
872     {
873         webkit_web_view_go_to_back_forward_list_item(m_web_view,
874                                                      WEBKIT_BACK_FORWARD_LIST_ITEM(gtkitem));
875     }
876 }
877 
878 extern "C" {
wxgtk_can_execute_editing_command_cb(GObject *,GAsyncResult * res,void * user_data)879 static void wxgtk_can_execute_editing_command_cb(GObject*,
880                                                  GAsyncResult *res,
881                                                  void* user_data)
882 {
883     GAsyncResult** res_out = static_cast<GAsyncResult**>(user_data);
884     g_object_ref(res);
885     *res_out = res;
886 }
887 }
888 
CanExecuteEditingCommand(const gchar * command) const889 bool wxWebViewWebKit::CanExecuteEditingCommand(const gchar* command) const
890 {
891     GAsyncResult *result = NULL;
892     webkit_web_view_can_execute_editing_command(m_web_view,
893                                                 command,
894                                                 NULL,
895                                                 wxgtk_can_execute_editing_command_cb,
896                                                 &result);
897 
898     GMainContext *main_context = g_main_context_get_thread_default();
899     while (!result)
900     {
901         g_main_context_iteration(main_context, TRUE);
902     }
903 
904     gboolean can_execute = webkit_web_view_can_execute_editing_command_finish(m_web_view,
905                                                                               result,
906                                                                               NULL);
907     g_object_unref(result);
908 
909     return can_execute != 0;
910 }
911 
CanCut() const912 bool wxWebViewWebKit::CanCut() const
913 {
914     return CanExecuteEditingCommand(WEBKIT_EDITING_COMMAND_CUT);
915 }
916 
CanCopy() const917 bool wxWebViewWebKit::CanCopy() const
918 {
919     return CanExecuteEditingCommand(WEBKIT_EDITING_COMMAND_COPY);
920 }
921 
CanPaste() const922 bool wxWebViewWebKit::CanPaste() const
923 {
924     return CanExecuteEditingCommand(WEBKIT_EDITING_COMMAND_PASTE);
925 }
926 
Cut()927 void wxWebViewWebKit::Cut()
928 {
929     webkit_web_view_execute_editing_command(m_web_view,
930                                             WEBKIT_EDITING_COMMAND_CUT);
931 }
932 
Copy()933 void wxWebViewWebKit::Copy()
934 {
935     webkit_web_view_execute_editing_command(m_web_view,
936                                             WEBKIT_EDITING_COMMAND_COPY);
937 }
938 
Paste()939 void wxWebViewWebKit::Paste()
940 {
941     webkit_web_view_execute_editing_command(m_web_view,
942                                             WEBKIT_EDITING_COMMAND_PASTE);
943 }
944 
CanUndo() const945 bool wxWebViewWebKit::CanUndo() const
946 {
947     return CanExecuteEditingCommand(WEBKIT_EDITING_COMMAND_UNDO);
948 }
949 
CanRedo() const950 bool wxWebViewWebKit::CanRedo() const
951 {
952     return CanExecuteEditingCommand(WEBKIT_EDITING_COMMAND_REDO);
953 }
954 
Undo()955 void wxWebViewWebKit::Undo()
956 {
957     webkit_web_view_execute_editing_command(m_web_view,
958                                             WEBKIT_EDITING_COMMAND_UNDO);
959 }
960 
Redo()961 void wxWebViewWebKit::Redo()
962 {
963     webkit_web_view_execute_editing_command(m_web_view,
964                                             WEBKIT_EDITING_COMMAND_REDO);
965 }
966 
GetCurrentURL() const967 wxString wxWebViewWebKit::GetCurrentURL() const
968 {
969     // FIXME: check which encoding the web kit control uses instead of
970     // assuming UTF8 (here and elsewhere too)
971     return wxString::FromUTF8(webkit_web_view_get_uri(m_web_view));
972 }
973 
974 
GetCurrentTitle() const975 wxString wxWebViewWebKit::GetCurrentTitle() const
976 {
977     return wxString::FromUTF8(webkit_web_view_get_title(m_web_view));
978 }
979 
980 
981 extern "C" {
wxgtk_web_resource_get_data_cb(GObject *,GAsyncResult * res,void * user_data)982 static void wxgtk_web_resource_get_data_cb(GObject*,
983                                            GAsyncResult *res,
984                                            void* user_data)
985 {
986     GAsyncResult** res_out = static_cast<GAsyncResult**>(user_data);
987     g_object_ref(res);
988     *res_out = res;
989 }
990 }
991 
GetPageSource() const992 wxString wxWebViewWebKit::GetPageSource() const
993 {
994     WebKitWebResource *resource = webkit_web_view_get_main_resource(m_web_view);
995     if (!resource)
996     {
997         return wxString();
998     }
999 
1000     GAsyncResult *result = NULL;
1001     webkit_web_resource_get_data(resource, NULL,
1002                                  wxgtk_web_resource_get_data_cb,
1003                                  &result);
1004 
1005     GMainContext *main_context = g_main_context_get_thread_default();
1006     while (!result)
1007     {
1008         g_main_context_iteration(main_context, TRUE);
1009     }
1010 
1011     size_t length;
1012     guchar *source = webkit_web_resource_get_data_finish(resource, result,
1013                                                          &length, NULL);
1014     if (result)
1015     {
1016         g_object_unref(result);
1017     }
1018 
1019     if (source)
1020     {
1021         wxString wxs(source, wxConvUTF8, length);
1022         free(source);
1023         return wxs;
1024     }
1025     return wxString();
1026 }
1027 
1028 
GetZoomFactor() const1029 float wxWebViewWebKit::GetZoomFactor() const
1030 {
1031     return GetWebkitZoom();
1032 }
1033 
SetZoomFactor(float zoom)1034 void wxWebViewWebKit::SetZoomFactor(float zoom)
1035 {
1036     SetWebkitZoom(zoom);
1037 }
1038 
SetZoomType(wxWebViewZoomType type)1039 void wxWebViewWebKit::SetZoomType(wxWebViewZoomType type)
1040 {
1041     WebKitSettings* settings = webkit_web_view_get_settings(m_web_view);
1042     webkit_settings_set_zoom_text_only(settings,
1043                                        type == wxWEBVIEW_ZOOM_TYPE_TEXT);
1044 }
1045 
GetZoomType() const1046 wxWebViewZoomType wxWebViewWebKit::GetZoomType() const
1047 {
1048     WebKitSettings* settings = webkit_web_view_get_settings(m_web_view);
1049     gboolean tozoom = webkit_settings_get_zoom_text_only(settings);
1050 
1051     if (tozoom)
1052         return wxWEBVIEW_ZOOM_TYPE_TEXT;
1053     else
1054         return wxWEBVIEW_ZOOM_TYPE_LAYOUT;
1055 }
1056 
CanSetZoomType(wxWebViewZoomType) const1057 bool wxWebViewWebKit::CanSetZoomType(wxWebViewZoomType) const
1058 {
1059     // this port supports all zoom types
1060     return true;
1061 }
1062 
DoSetPage(const wxString & html,const wxString & baseUri)1063 void wxWebViewWebKit::DoSetPage(const wxString& html, const wxString& baseUri)
1064 {
1065     webkit_web_view_load_html(m_web_view,
1066                               html.mb_str(wxConvUTF8),
1067                               baseUri.mb_str(wxConvUTF8));
1068 }
1069 
Print()1070 void wxWebViewWebKit::Print()
1071 {
1072     WebKitPrintOperation* printop = webkit_print_operation_new(m_web_view);
1073     webkit_print_operation_run_dialog(printop, NULL);
1074     g_object_unref(printop);
1075 }
1076 
1077 
IsBusy() const1078 bool wxWebViewWebKit::IsBusy() const
1079 {
1080     return m_busy;
1081 }
1082 
SetEditable(bool enable)1083 void wxWebViewWebKit::SetEditable(bool enable)
1084 {
1085 #if WEBKIT_CHECK_VERSION(2, 8, 0)
1086     webkit_web_view_set_editable(m_web_view, enable);
1087 #else
1088     // Not supported in older versions.
1089     wxUnusedVar(enable);
1090 #endif
1091 }
1092 
IsEditable() const1093 bool wxWebViewWebKit::IsEditable() const
1094 {
1095 #if WEBKIT_CHECK_VERSION(2, 8, 0)
1096     gboolean editable;
1097     g_object_get(m_web_view, "editable", &editable, NULL);
1098     return editable != 0;
1099 #else
1100     return false;
1101 #endif
1102 }
1103 
DeleteSelection()1104 void wxWebViewWebKit::DeleteSelection()
1105 {
1106     GDBusProxy *extension = GetExtensionProxy();
1107     if (extension)
1108     {
1109         guint64 page_id = webkit_web_view_get_page_id(m_web_view);
1110         GVariant *retval = g_dbus_proxy_call_sync(extension,
1111                                                   "DeleteSelection",
1112                                                   g_variant_new("(t)", page_id),
1113                                                   G_DBUS_CALL_FLAGS_NONE, -1,
1114                                                   NULL, NULL);
1115         if (retval)
1116         {
1117             g_variant_unref(retval);
1118         }
1119     }
1120 }
1121 
HasSelection() const1122 bool wxWebViewWebKit::HasSelection() const
1123 {
1124     GDBusProxy *extension = GetExtensionProxy();
1125     if (extension)
1126     {
1127         guint64 page_id = webkit_web_view_get_page_id(m_web_view);
1128         GVariant *retval = g_dbus_proxy_call_sync(extension,
1129                                                   "HasSelection",
1130                                                   g_variant_new("(t)", page_id),
1131                                                   G_DBUS_CALL_FLAGS_NONE, -1,
1132                                                   NULL, NULL);
1133         if (retval)
1134         {
1135             gboolean has_selection = FALSE;
1136             g_variant_get(retval, "(b)", &has_selection);
1137             g_variant_unref(retval);
1138             return has_selection != 0;
1139         }
1140     }
1141     return false;
1142 }
1143 
SelectAll()1144 void wxWebViewWebKit::SelectAll()
1145 {
1146     webkit_web_view_execute_editing_command(m_web_view,
1147                                             WEBKIT_EDITING_COMMAND_SELECT_ALL);
1148 }
1149 
GetSelectedText() const1150 wxString wxWebViewWebKit::GetSelectedText() const
1151 {
1152     GDBusProxy *extension = GetExtensionProxy();
1153     if (extension)
1154     {
1155         guint64 page_id = webkit_web_view_get_page_id(m_web_view);
1156         GVariant *retval = g_dbus_proxy_call_sync(extension,
1157                                                   "GetSelectedText",
1158                                                   g_variant_new("(t)", page_id),
1159                                                   G_DBUS_CALL_FLAGS_NONE, -1,
1160                                                   NULL, NULL);
1161         if (retval)
1162         {
1163             char *text;
1164             g_variant_get(retval, "(s)", &text);
1165             g_variant_unref(retval);
1166             return wxString(text, wxConvUTF8);
1167         }
1168     }
1169     return wxString();
1170 }
1171 
GetSelectedSource() const1172 wxString wxWebViewWebKit::GetSelectedSource() const
1173 {
1174     GDBusProxy *extension = GetExtensionProxy();
1175     if (extension)
1176     {
1177         guint64 page_id = webkit_web_view_get_page_id(m_web_view);
1178         GVariant *retval = g_dbus_proxy_call_sync(extension,
1179                                                   "GetSelectedSource",
1180                                                   g_variant_new("(t)", page_id),
1181                                                   G_DBUS_CALL_FLAGS_NONE, -1,
1182                                                   NULL, NULL);
1183         if (retval)
1184         {
1185             char *source;
1186             g_variant_get(retval, "(s)", &source);
1187             g_variant_unref(retval);
1188             return wxString(source, wxConvUTF8);
1189         }
1190     }
1191     return wxString();
1192 }
1193 
ClearSelection()1194 void wxWebViewWebKit::ClearSelection()
1195 {
1196     GDBusProxy *extension = GetExtensionProxy();
1197     if (extension)
1198     {
1199         guint64 page_id = webkit_web_view_get_page_id(m_web_view);
1200         GVariant *retval = g_dbus_proxy_call_sync(extension,
1201                                                   "ClearSelection",
1202                                                   g_variant_new("(t)", page_id),
1203                                                   G_DBUS_CALL_FLAGS_NONE, -1,
1204                                                   NULL, NULL);
1205         if (retval)
1206         {
1207             g_variant_unref(retval);
1208         }
1209     }
1210 }
1211 
GetPageText() const1212 wxString wxWebViewWebKit::GetPageText() const
1213 {
1214     GDBusProxy *extension = GetExtensionProxy();
1215     if (extension)
1216     {
1217         guint64 page_id = webkit_web_view_get_page_id(m_web_view);
1218         GVariant *retval = g_dbus_proxy_call_sync(extension,
1219                                                   "GetPageText",
1220                                                   g_variant_new("(t)", page_id),
1221                                                   G_DBUS_CALL_FLAGS_NONE, -1,
1222                                                   NULL, NULL);
1223         if (retval)
1224         {
1225             char *text;
1226             g_variant_get(retval, "(s)", &text);
1227             g_variant_unref(retval);
1228             return wxString(text, wxConvUTF8);
1229         }
1230     }
1231     return wxString();
1232 }
1233 
1234 extern "C"
1235 {
1236 
wxgtk_run_javascript_cb(GObject *,GAsyncResult * res,void * user_data)1237 static void wxgtk_run_javascript_cb(GObject *,
1238                                     GAsyncResult *res,
1239                                     void *user_data)
1240 {
1241     g_object_ref(res);
1242 
1243     GAsyncResult** res_out = static_cast<GAsyncResult**>(user_data);
1244     *res_out = res;
1245 }
1246 
1247 } // extern "C"
1248 
1249 // Run the given script synchronously and return its result in output.
RunScriptSync(const wxString & javascript,wxString * output) const1250 bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output) const
1251 {
1252     GAsyncResult *result = NULL;
1253     webkit_web_view_run_javascript(m_web_view,
1254                                    javascript.utf8_str(),
1255                                    NULL,
1256                                    wxgtk_run_javascript_cb,
1257                                    &result);
1258 
1259     GMainContext *main_context = g_main_context_get_thread_default();
1260 
1261     while ( !result )
1262         g_main_context_iteration(main_context, TRUE);
1263 
1264     wxGtkError error;
1265     wxWebKitJavascriptResult js_result
1266                              (
1267                                 webkit_web_view_run_javascript_finish
1268                                 (
1269                                     m_web_view,
1270                                     result,
1271                                     error.Out()
1272                                 )
1273                              );
1274 
1275     // Match g_object_ref() in wxgtk_run_javascript_cb()
1276     g_object_unref(result);
1277 
1278     if ( !js_result )
1279     {
1280         if ( output )
1281             *output = error.GetMessage();
1282         return false;
1283     }
1284 
1285     return wxGetStringFromJSResult(js_result, output);
1286 }
1287 
RunScript(const wxString & javascript,wxString * output) const1288 bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) const
1289 {
1290     wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount);
1291 
1292     // This string is also used as an error indicator: it's cleared if there is
1293     // no error or used in the warning message below if there is one.
1294     wxString result;
1295     if ( RunScriptSync(wrapJS.GetWrappedCode(), &result)
1296             && result == wxS("true") )
1297     {
1298         if ( RunScriptSync(wrapJS.GetOutputCode(), &result) )
1299         {
1300             if ( output )
1301                 *output = result;
1302             result.clear();
1303         }
1304 
1305         RunScriptSync(wrapJS.GetCleanUpCode());
1306     }
1307 
1308     if ( !result.empty() )
1309     {
1310         wxLogWarning(_("Error running JavaScript: %s"), result);
1311         return false;
1312     }
1313 
1314     return true;
1315 }
1316 
AddScriptMessageHandler(const wxString & name)1317 bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name)
1318 {
1319     if (!m_web_view)
1320         return false;
1321 
1322     WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager(m_web_view);
1323     g_signal_connect(ucm, wxString::Format("script-message-received::%s", name).utf8_str(),
1324                      G_CALLBACK(wxgtk_webview_webkit_script_message_received), this);
1325     bool res = webkit_user_content_manager_register_script_message_handler(ucm, name.utf8_str());
1326     if (res)
1327     {
1328         // Make webkit message handler available under common name
1329         wxString js = wxString::Format("window.%s = window.webkit.messageHandlers.%s;",
1330                 name, name);
1331         AddUserScript(js);
1332         RunScript(js);
1333     }
1334 
1335     return res;
1336 }
1337 
RemoveScriptMessageHandler(const wxString & name)1338 bool wxWebViewWebKit::RemoveScriptMessageHandler(const wxString& name)
1339 {
1340     WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager(m_web_view);
1341     webkit_user_content_manager_unregister_script_message_handler(ucm, name.utf8_str());
1342     return true;
1343 }
1344 
AddUserScript(const wxString & javascript,wxWebViewUserScriptInjectionTime injectionTime)1345 bool wxWebViewWebKit::AddUserScript(const wxString& javascript,
1346         wxWebViewUserScriptInjectionTime injectionTime)
1347 {
1348     WebKitUserScript* userScript = webkit_user_script_new(
1349         javascript.utf8_str(),
1350         WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
1351         (injectionTime == wxWEBVIEW_INJECT_AT_DOCUMENT_START) ?
1352             WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START : WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END,
1353         NULL, NULL
1354     );
1355     WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager(m_web_view);
1356     webkit_user_content_manager_add_script(ucm, userScript);
1357     webkit_user_script_unref(userScript);
1358 
1359     return true;
1360 }
1361 
RemoveAllUserScripts()1362 void wxWebViewWebKit::RemoveAllUserScripts()
1363 {
1364     WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager(m_web_view);
1365     webkit_user_content_manager_remove_all_scripts(ucm);
1366 }
1367 
RegisterHandler(wxSharedPtr<wxWebViewHandler> handler)1368 void wxWebViewWebKit::RegisterHandler(wxSharedPtr<wxWebViewHandler> handler)
1369 {
1370     m_handlerList.push_back(handler);
1371     WebKitWebContext* context = webkit_web_context_get_default();
1372     webkit_web_context_register_uri_scheme(context, handler->GetName().utf8_str(),
1373                                            (WebKitURISchemeRequestCallback)wxgtk_webview_webkit_uri_scheme_request_cb,
1374                                            this, NULL);
1375 }
1376 
EnableContextMenu(bool enable)1377 void wxWebViewWebKit::EnableContextMenu(bool enable)
1378 {
1379     wxWebView::EnableContextMenu(enable);
1380 }
1381 
Find(const wxString & text,int flags)1382 long wxWebViewWebKit::Find(const wxString& text, int flags)
1383 {
1384     WebKitFindController* findctrl = webkit_web_view_get_find_controller(m_web_view);
1385     bool newSearch = false;
1386     if(text != m_findText ||
1387        (flags & wxWEBVIEW_FIND_MATCH_CASE) != (m_findFlags & wxWEBVIEW_FIND_MATCH_CASE))
1388     {
1389         newSearch = true;
1390         //If it is a new search we need to clear existing highlights
1391         webkit_find_controller_search_finish(findctrl);
1392     }
1393 
1394     m_findFlags = flags;
1395     m_findText = text;
1396 
1397     //If the search string is empty then we clear any selection and highlight
1398     if(text.empty())
1399     {
1400         webkit_find_controller_search_finish(findctrl);
1401         ClearSelection();
1402         return wxNOT_FOUND;
1403     }
1404 
1405     bool wrap = false, forward = true;
1406     guint32 options = WEBKIT_FIND_OPTIONS_NONE;
1407     if(flags & wxWEBVIEW_FIND_WRAP)
1408     {
1409         wrap = true;
1410         options |= WEBKIT_FIND_OPTIONS_WRAP_AROUND;
1411     }
1412     if(!(flags & wxWEBVIEW_FIND_MATCH_CASE))
1413     {
1414         options |= WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE;
1415     }
1416     if(flags & wxWEBVIEW_FIND_BACKWARDS)
1417     {
1418         forward = false;
1419         options |= WEBKIT_FIND_OPTIONS_BACKWARDS;
1420     }
1421 
1422     if(newSearch)
1423     {
1424         //Initially we count the matches to know how many we have
1425         m_findCount = -1;
1426         webkit_find_controller_count_matches(findctrl,
1427                                              wxGTK_CONV(text),
1428                                              options,
1429                                              G_MAXUINT);
1430         GMainContext *main_context = g_main_context_get_thread_default();
1431         while (m_findCount == -1)
1432         {
1433             g_main_context_iteration(main_context, TRUE);
1434         }
1435         //Highlight them if needed
1436         if(flags & wxWEBVIEW_FIND_HIGHLIGHT_RESULT)
1437         {
1438             webkit_find_controller_search(findctrl,
1439                                           wxGTK_CONV(text),
1440                                           options,
1441                                           G_MAXUINT);
1442         }
1443         //In this case we return early to match IE behaviour
1444         m_findPosition = -1;
1445         return m_findCount;
1446     }
1447     else
1448     {
1449         if(forward)
1450             m_findPosition++;
1451         else
1452             m_findPosition--;
1453         if(m_findPosition < 0)
1454             m_findPosition += m_findCount;
1455         if(m_findPosition > m_findCount)
1456             m_findPosition -= m_findCount;
1457     }
1458 
1459     if(forward)
1460     {
1461         webkit_find_controller_search_next(findctrl);
1462         if(m_findPosition == m_findCount && !wrap)
1463         {
1464             return wxNOT_FOUND;
1465         }
1466     }
1467     else
1468     {
1469         webkit_find_controller_search_previous(findctrl);
1470         if(m_findPosition == -1 && !wrap)
1471         {
1472             return wxNOT_FOUND;
1473         }
1474     }
1475 
1476     return newSearch ? m_findCount : m_findPosition;
1477 }
1478 
FindClear()1479 void wxWebViewWebKit::FindClear()
1480 {
1481     m_findCount = 0;
1482     m_findFlags = 0;
1483     m_findText.clear();
1484     m_findPosition = -1;
1485 }
1486 
1487 // static
1488 wxVisualAttributes
GetClassDefaultAttributes(wxWindowVariant WXUNUSED (variant))1489 wxWebViewWebKit::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
1490 {
1491      return GetDefaultAttributesFromGTKWidget(webkit_web_view_new());
1492 }
1493 
SetupWebExtensionServer()1494 void wxWebViewWebKit::SetupWebExtensionServer()
1495 {
1496     char *address = g_strdup_printf("unix:tmpdir=%s", g_get_tmp_dir());
1497     char *guid = g_dbus_generate_guid();
1498     GDBusAuthObserver *observer = g_dbus_auth_observer_new();
1499     GError *error = NULL;
1500 
1501     g_signal_connect(observer, "authorize-authenticated-peer",
1502                      G_CALLBACK(wxgtk_authorize_authenticated_peer_cb), this);
1503 
1504     m_dbusServer = g_dbus_server_new_sync(address,
1505                                           G_DBUS_SERVER_FLAGS_NONE,
1506                                           guid,
1507                                           observer,
1508                                           NULL,
1509                                           &error);
1510 
1511     if (error)
1512     {
1513         g_warning("Failed to start web extension server on %s: %s", address, error->message);
1514         g_error_free(error);
1515     }
1516     else
1517     {
1518         g_signal_connect(m_dbusServer, "new-connection",
1519                          G_CALLBACK(wxgtk_new_connection_cb), &m_extension);
1520         g_dbus_server_start(m_dbusServer);
1521     }
1522 
1523     g_free(address);
1524     g_free(guid);
1525     g_object_unref(observer);
1526 }
1527 
GetExtensionProxy() const1528 GDBusProxy *wxWebViewWebKit::GetExtensionProxy() const
1529 {
1530     if (!m_extension)
1531     {
1532         g_warning("Web extension not found in \"%s\", "
1533                   "some wxWebView functionality will be not available",
1534                   (const char*)GetStandardWebExtensionsDir().utf8_str());
1535     }
1536     return m_extension;
1537 }
1538 
1539 #endif // wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT2
1540