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