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