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