1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk/webview_webkit2_extension.cpp
3 // Purpose:     GTK WebKit2 extension for web view component
4 // Author:      Scott Talbert
5 // Copyright:   (c) 2017 Scott Talbert
6 // Licence:     wxWindows licence
7 /////////////////////////////////////////////////////////////////////////////
8 
9 #include "wx/defs.h"
10 #include "wx/gtk/private/webview_webkit2_extension.h"
11 #include <webkit2/webkit-web-extension.h>
12 #define WEBKIT_DOM_USE_UNSTABLE_API
13 #include <webkitdom/WebKitDOMDOMSelection.h>
14 #include <webkitdom/WebKitDOMDOMWindowUnstable.h>
15 
16 // We can't easily avoid deprecation warnings about many WebKit functions, e.g.
17 // webkit_dom_document_get_default_view() and just about everything related to
18 // the selection, so for now just disable the warnings as we can't do anything
19 // about them anyhow.
20 wxGCC_WARNING_SUPPRESS(deprecated-declarations)
21 
22 static const char introspection_xml[] =
23   "<node>"
24   " <interface name='org.wxwidgets.wxGTK.WebExtension'>"
25   "  <method name='ClearSelection'>"
26   "   <arg type='t' name='page_id' direction='in'/>"
27   "  </method>"
28   "  <method name='DeleteSelection'>"
29   "   <arg type='t' name='page_id' direction='in'/>"
30   "  </method>"
31   "  <method name='GetPageSource'>"
32   "   <arg type='t' name='page_id' direction='in'/>"
33   "   <arg type='s' name='source' direction='out'/>"
34   "  </method>"
35   "  <method name='GetPageText'>"
36   "   <arg type='t' name='page_id' direction='in'/>"
37   "   <arg type='s' name='text' direction='out'/>"
38   "  </method>"
39   "  <method name='GetSelectedSource'>"
40   "   <arg type='t' name='page_id' direction='in'/>"
41   "   <arg type='s' name='source' direction='out'/>"
42   "  </method>"
43   "  <method name='GetSelectedText'>"
44   "   <arg type='t' name='page_id' direction='in'/>"
45   "   <arg type='s' name='text' direction='out'/>"
46   "  </method>"
47   "  <method name='HasSelection'>"
48   "   <arg type='t' name='page_id' direction='in'/>"
49   "   <arg type='b' name='has_selection' direction='out'/>"
50   "  </method>"
51   " </interface>"
52   "</node>";
53 
54 class wxWebViewWebKitExtension;
55 
56 extern "C" {
57 static gboolean
58 wxgtk_webview_authorize_authenticated_peer_cb(GDBusAuthObserver *observer,
59                                               GIOStream *stream,
60                                               GCredentials *credentials,
61                                               wxWebViewWebKitExtension *extension);
62 static void
63 wxgtk_webview_dbus_connection_created_cb(GObject *source_object,
64                                          GAsyncResult *result,
65                                          void* user_data);
66 } // extern "C"
67 
68 static wxWebViewWebKitExtension *gs_extension = NULL;
69 
70 class wxWebViewWebKitExtension
71 {
72 public:
73     wxWebViewWebKitExtension(WebKitWebExtension *webkit_extension, const char* server_address);
74     void ClearSelection(GVariant *parameters, GDBusMethodInvocation *invocation);
75     void DeleteSelection(GVariant *parameters, GDBusMethodInvocation *invocation);
76     void GetPageSource(GVariant *parameters, GDBusMethodInvocation *invocation);
77     void GetPageText(GVariant *parameters, GDBusMethodInvocation *invocation);
78     void GetSelectedSource(GVariant *parameters, GDBusMethodInvocation *invocation);
79     void GetSelectedText(GVariant *parameters, GDBusMethodInvocation *invocation);
80     void HasSelection(GVariant *parameters, GDBusMethodInvocation *invocation);
81     void SetDBusConnection(GDBusConnection *dbusConnection);
82 
83 private:
84     WebKitWebPage* GetWebPageOrReturnError(GVariant *parameters, GDBusMethodInvocation *invocation);
85     void ReturnDBusStringValue(GDBusMethodInvocation *invocation, gchar *value);
86 
87     GDBusConnection *m_dbusConnection;
88     WebKitWebExtension *m_webkitExtension;
89 };
90 
wxWebViewWebKitExtension(WebKitWebExtension * extension,const char * server_address)91 wxWebViewWebKitExtension::wxWebViewWebKitExtension(WebKitWebExtension *extension, const char* server_address)
92 {
93     m_webkitExtension = (WebKitWebExtension*)g_object_ref(extension);
94 
95     GDBusAuthObserver *observer = g_dbus_auth_observer_new();
96     g_signal_connect(observer, "authorize-authenticated-peer",
97                      G_CALLBACK(wxgtk_webview_authorize_authenticated_peer_cb),
98                      this);
99 
100     g_dbus_connection_new_for_address(server_address,
101                                       G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
102                                       observer,
103                                       NULL,
104                                       wxgtk_webview_dbus_connection_created_cb,
105                                       this);
106     g_object_unref(observer);
107 }
108 
GetWebPageOrReturnError(GVariant * parameters,GDBusMethodInvocation * invocation)109 WebKitWebPage* wxWebViewWebKitExtension::GetWebPageOrReturnError(GVariant *parameters, GDBusMethodInvocation *invocation)
110 {
111     guint64 page_id;
112     g_variant_get(parameters, "(t)", &page_id);
113     WebKitWebPage *web_page = webkit_web_extension_get_page(m_webkitExtension,
114                                                             page_id);
115     if (!web_page)
116     {
117         g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
118                                               G_DBUS_ERROR_INVALID_ARGS,
119                                               "Invalid page ID: %" G_GUINT64_FORMAT, page_id);
120     }
121     return web_page;
122 }
123 
GetSelectedSource(GVariant * parameters,GDBusMethodInvocation * invocation)124 void wxWebViewWebKitExtension::GetSelectedSource(GVariant *parameters,
125                                                  GDBusMethodInvocation *invocation)
126 {
127     WebKitWebPage *web_page = GetWebPageOrReturnError(parameters, invocation);
128     if (!web_page)
129     {
130         return;
131     }
132 
133     WebKitDOMDocument *doc = webkit_web_page_get_dom_document(web_page);
134     WebKitDOMDOMWindow *win = webkit_dom_document_get_default_view(doc);
135     WebKitDOMDOMSelection *sel = webkit_dom_dom_window_get_selection(win);
136     g_object_unref(win);
137     if (!sel)
138     {
139         ReturnDBusStringValue(invocation, NULL);
140         return;
141     }
142     WebKitDOMRange *range = webkit_dom_dom_selection_get_range_at(sel, 0, NULL);
143     if (!range)
144     {
145         ReturnDBusStringValue(invocation, NULL);
146         return;
147     }
148     WebKitDOMElement *div = webkit_dom_document_create_element(doc, "div",
149                                                                NULL);
150     WebKitDOMDocumentFragment *clone = webkit_dom_range_clone_contents(range,
151                                                                        NULL);
152     webkit_dom_node_append_child(&div->parent_instance,
153                                  &clone->parent_instance, NULL);
154     WebKitDOMElement *html = (WebKitDOMElement*)div;
155 #if WEBKIT_CHECK_VERSION(2, 8, 0)
156     gchar *text = webkit_dom_element_get_inner_html(html);
157 #else
158     gchar *text = webkit_dom_html_element_get_inner_html(WEBKIT_DOM_HTML_ELEMENT(html));
159 #endif
160     g_object_unref(range);
161 
162     ReturnDBusStringValue(invocation, text);
163 }
164 
GetPageSource(GVariant * parameters,GDBusMethodInvocation * invocation)165 void wxWebViewWebKitExtension::GetPageSource(GVariant *parameters,
166                                              GDBusMethodInvocation *invocation)
167 {
168     WebKitWebPage *web_page = GetWebPageOrReturnError(parameters, invocation);
169     if (!web_page)
170     {
171         return;
172     }
173 
174     WebKitDOMDocument *doc = webkit_web_page_get_dom_document(web_page);
175     WebKitDOMElement *body = webkit_dom_document_get_document_element(doc);
176 #if WEBKIT_CHECK_VERSION(2, 8, 0)
177     gchar *source = webkit_dom_element_get_outer_html(body);
178 #else
179     gchar *source =
180         webkit_dom_html_element_get_outer_html(WEBKIT_DOM_HTML_ELEMENT(body));
181 #endif
182     g_dbus_method_invocation_return_value(invocation,
183                                           g_variant_new("(s)", source ? source : ""));
184 }
185 
GetPageText(GVariant * parameters,GDBusMethodInvocation * invocation)186 void wxWebViewWebKitExtension::GetPageText(GVariant *parameters,
187                                            GDBusMethodInvocation *invocation)
188 {
189     WebKitWebPage *web_page = GetWebPageOrReturnError(parameters, invocation);
190     if (!web_page)
191     {
192         return;
193     }
194 
195     WebKitDOMDocument *doc = webkit_web_page_get_dom_document(web_page);
196     WebKitDOMHTMLElement *body = webkit_dom_document_get_body(doc);
197     gchar *text = webkit_dom_html_element_get_inner_text(body);
198     g_dbus_method_invocation_return_value(invocation,
199                                           g_variant_new("(s)", text));
200 }
201 
GetSelectedText(GVariant * parameters,GDBusMethodInvocation * invocation)202 void wxWebViewWebKitExtension::GetSelectedText(GVariant *parameters,
203                                                GDBusMethodInvocation *invocation)
204 {
205     WebKitWebPage *web_page = GetWebPageOrReturnError(parameters, invocation);
206     if (!web_page)
207     {
208         return;
209     }
210 
211     WebKitDOMDocument *doc = webkit_web_page_get_dom_document(web_page);
212     WebKitDOMDOMWindow *win = webkit_dom_document_get_default_view(doc);
213     WebKitDOMDOMSelection *sel = webkit_dom_dom_window_get_selection(win);
214     g_object_unref(win);
215     if (!sel)
216     {
217         ReturnDBusStringValue(invocation, NULL);
218         return;
219     }
220     WebKitDOMRange *range = webkit_dom_dom_selection_get_range_at(sel, 0, NULL);
221     if (!range)
222     {
223         ReturnDBusStringValue(invocation, NULL);
224         return;
225     }
226     gchar *text = webkit_dom_range_get_text(range);
227     g_object_unref(range);
228 
229     ReturnDBusStringValue(invocation, text);
230 }
231 
ClearSelection(GVariant * parameters,GDBusMethodInvocation * invocation)232 void wxWebViewWebKitExtension::ClearSelection(GVariant *parameters,
233                                               GDBusMethodInvocation *invocation)
234 {
235     WebKitWebPage *web_page = GetWebPageOrReturnError(parameters, invocation);
236     if (!web_page)
237     {
238         return;
239     }
240 
241     WebKitDOMDocument *doc = webkit_web_page_get_dom_document(web_page);
242     WebKitDOMDOMWindow *win = webkit_dom_document_get_default_view(doc);
243     WebKitDOMDOMSelection *sel = webkit_dom_dom_window_get_selection(win);
244     g_object_unref(win);
245     if (sel)
246     {
247         webkit_dom_dom_selection_remove_all_ranges(sel);
248     }
249 
250     g_dbus_method_invocation_return_value(invocation, NULL);
251 }
252 
HasSelection(GVariant * parameters,GDBusMethodInvocation * invocation)253 void wxWebViewWebKitExtension::HasSelection(GVariant *parameters,
254                                             GDBusMethodInvocation *invocation)
255 {
256     WebKitWebPage *web_page = GetWebPageOrReturnError(parameters, invocation);
257     if (!web_page)
258     {
259         return;
260     }
261 
262     WebKitDOMDocument *doc = webkit_web_page_get_dom_document(web_page);
263     WebKitDOMDOMWindow *win = webkit_dom_document_get_default_view(doc);
264     WebKitDOMDOMSelection *sel = webkit_dom_dom_window_get_selection(win);
265     g_object_unref(win);
266     gboolean has_selection = FALSE;
267     if (WEBKIT_DOM_IS_DOM_SELECTION(sel))
268     {
269         if (!webkit_dom_dom_selection_get_is_collapsed(sel))
270         {
271             has_selection = TRUE;
272         }
273     }
274     g_dbus_method_invocation_return_value(invocation,
275                                           g_variant_new("(b)", has_selection));
276 }
277 
DeleteSelection(GVariant * parameters,GDBusMethodInvocation * invocation)278 void wxWebViewWebKitExtension::DeleteSelection(GVariant *parameters,
279                                                GDBusMethodInvocation *invocation)
280 {
281     WebKitWebPage *web_page = GetWebPageOrReturnError(parameters, invocation);
282     if (!web_page)
283     {
284         return;
285     }
286 
287     WebKitDOMDocument *doc = webkit_web_page_get_dom_document(web_page);
288     WebKitDOMDOMWindow *win = webkit_dom_document_get_default_view(doc);
289     WebKitDOMDOMSelection *sel = webkit_dom_dom_window_get_selection(win);
290     g_object_unref(win);
291     if (sel)
292     {
293         webkit_dom_dom_selection_delete_from_document(sel);
294     }
295 
296     g_dbus_method_invocation_return_value(invocation, NULL);
297 }
298 
ReturnDBusStringValue(GDBusMethodInvocation * invocation,gchar * value)299 void wxWebViewWebKitExtension::ReturnDBusStringValue(GDBusMethodInvocation *invocation, gchar *value)
300 {
301     g_dbus_method_invocation_return_value(invocation,
302                                           g_variant_new("(s)", value ? value : ""));
303 }
304 
SetDBusConnection(GDBusConnection * dbusConnection)305 void wxWebViewWebKitExtension::SetDBusConnection(GDBusConnection *dbusConnection)
306 {
307     m_dbusConnection = dbusConnection;
308 }
309 
310 extern "C" {
311 static void
wxgtk_webview_handle_method_call(GDBusConnection *,const char *,const char *,const char * interface_name,const char * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)312 wxgtk_webview_handle_method_call(GDBusConnection*,
313                                  const char* /* sender */,
314                                  const char* /* object_path */,
315                                  const char *interface_name,
316                                  const char *method_name,
317                                  GVariant *parameters,
318                                  GDBusMethodInvocation *invocation,
319                                  gpointer user_data)
320 {
321     if (g_strcmp0(interface_name, WXGTK_WEB_EXTENSION_INTERFACE) != 0)
322     {
323         return;
324     }
325 
326     wxWebViewWebKitExtension *extension = (wxWebViewWebKitExtension*)user_data;
327 
328     if (g_strcmp0(method_name, "ClearSelection") == 0)
329     {
330         extension->ClearSelection(parameters, invocation);
331     }
332     else if (g_strcmp0(method_name, "DeleteSelection") == 0)
333     {
334         extension->DeleteSelection(parameters, invocation);
335     }
336     else if (g_strcmp0(method_name, "GetPageSource") == 0)
337     {
338         extension->GetPageSource(parameters, invocation);
339     }
340     else if (g_strcmp0(method_name, "GetPageText") == 0)
341     {
342         extension->GetPageText(parameters, invocation);
343     }
344     else if (g_strcmp0(method_name, "GetSelectedSource") == 0)
345     {
346         extension->GetSelectedSource(parameters, invocation);
347     }
348     else if (g_strcmp0(method_name, "GetSelectedText") == 0)
349     {
350         extension->GetSelectedText(parameters, invocation);
351     }
352     else if (g_strcmp0(method_name, "HasSelection") == 0)
353     {
354         extension->HasSelection(parameters, invocation);
355     }
356 }
357 } // extern "C"
358 
359 static const GDBusInterfaceVTable interface_vtable = {
360     wxgtk_webview_handle_method_call,
361     NULL,
362     NULL
363 };
364 
365 static
366 gboolean
wxgtk_webview_dbus_peer_is_authorized(GCredentials * peer_credentials)367 wxgtk_webview_dbus_peer_is_authorized(GCredentials *peer_credentials)
368 {
369     static GCredentials *own_credentials = g_credentials_new();
370     GError *error = NULL;
371 
372     if (peer_credentials && g_credentials_is_same_user(peer_credentials, own_credentials, &error))
373     {
374         return TRUE;
375     }
376 
377     if (error)
378     {
379         g_warning("Failed to authorize web extension connection: %s", error->message);
380         g_error_free(error);
381     }
382     return FALSE;
383 }
384 
385 extern "C" {
386 static gboolean
wxgtk_webview_authorize_authenticated_peer_cb(GDBusAuthObserver *,GIOStream *,GCredentials * credentials,wxWebViewWebKitExtension *)387 wxgtk_webview_authorize_authenticated_peer_cb(GDBusAuthObserver*,
388                                               GIOStream*,
389                                               GCredentials *credentials,
390                                               wxWebViewWebKitExtension*)
391 {
392     return wxgtk_webview_dbus_peer_is_authorized(credentials);
393 }
394 
395 static void
wxgtk_webview_dbus_connection_created_cb(GObject *,GAsyncResult * result,void * user_data)396 wxgtk_webview_dbus_connection_created_cb(GObject*,
397                                          GAsyncResult *result,
398                                          void* user_data)
399 {
400     static GDBusNodeInfo *introspection_data =
401         g_dbus_node_info_new_for_xml(introspection_xml, NULL);
402 
403     GError *error = NULL;
404     GDBusConnection *connection =
405         g_dbus_connection_new_for_address_finish(result, &error);
406     if (error)
407     {
408         g_warning("Failed to connect to UI process: %s", error->message);
409         g_error_free(error);
410         return;
411     }
412 
413     wxWebViewWebKitExtension* extension = static_cast<wxWebViewWebKitExtension*>(user_data);
414 
415     guint registration_id =
416         g_dbus_connection_register_object(connection,
417                                           WXGTK_WEB_EXTENSION_OBJECT_PATH,
418                                           introspection_data->interfaces[0],
419                                           &interface_vtable,
420                                           extension,
421                                           NULL,
422                                           &error);
423     if (!registration_id)
424     {
425         g_warning ("Failed to register web extension object: %s\n", error->message);
426         g_error_free (error);
427         g_object_unref (connection);
428         return;
429     }
430 
431     extension->SetDBusConnection(connection);
432 }
433 
434 WXEXPORT void
webkit_web_extension_initialize_with_user_data(WebKitWebExtension * webkit_extension,GVariant * user_data)435 webkit_web_extension_initialize_with_user_data (WebKitWebExtension *webkit_extension,
436                                                 GVariant           *user_data)
437 {
438     const char *server_address;
439 
440     g_variant_get (user_data, "(&s)", &server_address);
441 
442     gs_extension = new wxWebViewWebKitExtension(webkit_extension,
443                                                 server_address);
444 }
445 } // extern "C"
446