1 /**
2 * @file liferea_web_extension.c Control WebKit2 via DBUS from Liferea
3 *
4 * Copyright (C) 2016 Leiaz <leiaz@free.fr>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #include <webkit2/webkit-web-extension.h>
22 #define WEBKIT_DOM_USE_UNSTABLE_API
23 #include <webkitdom/WebKitDOMDOMWindowUnstable.h>
24
25 #include "liferea_web_extension.h"
26 #include "liferea_web_extension_names.h"
27
28 struct _LifereaWebExtension {
29 GObject parent;
30
31 GDBusConnection *connection;
32 WebKitWebExtension *webkit_extension;
33 GArray *pending_pages_created;
34 gboolean initialized;
35
36 GSettings *liferea_settings;
37 };
38
39 struct _LifereaWebExtensionClass {
40 GObjectClass parent_class;
41 };
42
43 G_DEFINE_TYPE (LifereaWebExtension, liferea_web_extension, G_TYPE_OBJECT)
44
45 static const char introspection_xml[] =
46 "<node>"
47 " <interface name='net.sf.liferea.WebExtension'>"
48 " <method name='ScrollPageDown'>"
49 " <arg type='t' name='page_id' direction='in'/>"
50 " <arg type='b' name='scrolled' direction='out'/>"
51 " </method>"
52 " <signal name='PageCreated'>"
53 " <arg type='t' name='page_id' direction='out'/>"
54 " </signal>"
55 " </interface>"
56 "</node>";
57
58 static void
liferea_web_extension_dispose(GObject * object)59 liferea_web_extension_dispose (GObject *object)
60 {
61 LifereaWebExtension *extension = LIFEREA_WEB_EXTENSION (object);
62
63 g_clear_object (&extension->connection);
64 g_clear_object (&extension->webkit_extension);
65 g_clear_object (&extension->liferea_settings);
66 }
67
68 static void
liferea_web_extension_class_init(LifereaWebExtensionClass * klass)69 liferea_web_extension_class_init (LifereaWebExtensionClass *klass)
70 {
71 GObjectClass *object_class = G_OBJECT_CLASS (klass);
72 object_class->dispose = liferea_web_extension_dispose;
73 }
74
75 static void
liferea_web_extension_init(LifereaWebExtension * self)76 liferea_web_extension_init (LifereaWebExtension *self)
77 {
78 self->webkit_extension = NULL;
79 self->connection = NULL;
80 self->pending_pages_created = NULL;
81 self->initialized = FALSE;
82 self->liferea_settings = g_settings_new ("net.sf.liferea");
83 }
84
85 static WebKitDOMDOMWindow*
liferea_web_extension_get_dom_window(LifereaWebExtension * self,guint64 page_id)86 liferea_web_extension_get_dom_window (LifereaWebExtension *self, guint64 page_id)
87 {
88 WebKitWebPage *page;
89 WebKitDOMDocument *document;
90 WebKitDOMDOMWindow *window;
91
92 page = webkit_web_extension_get_page (self->webkit_extension, page_id);
93 document = webkit_web_page_get_dom_document (page);
94 window = webkit_dom_document_get_default_view (document);
95
96 return window;
97 }
98
99 /*
100 * \returns TRUE if scrolling happened, FALSE if the end was reached
101 */
102 static gboolean
liferea_web_extension_scroll_page_down(LifereaWebExtension * self,guint64 page_id)103 liferea_web_extension_scroll_page_down (LifereaWebExtension *self, guint64 page_id)
104 {
105 glong old_scroll_y, new_scroll_y, increment;
106 WebKitDOMDOMWindow *window;
107
108 window = liferea_web_extension_get_dom_window (self, page_id);
109
110 old_scroll_y = webkit_dom_dom_window_get_scroll_y (window);
111 increment = webkit_dom_dom_window_get_inner_height (window);
112 webkit_dom_dom_window_scroll_by (window, 0, increment);
113 new_scroll_y = webkit_dom_dom_window_get_scroll_y (window);
114
115 return (new_scroll_y > old_scroll_y);
116 }
117
118 static gboolean
on_authorize_authenticated_peer(GDBusAuthObserver * observer,GIOStream * stream,GCredentials * credentials,gpointer extension)119 on_authorize_authenticated_peer (GDBusAuthObserver *observer,
120 GIOStream *stream,
121 GCredentials *credentials,
122 gpointer extension)
123 {
124 gboolean authorized = FALSE;
125 GCredentials *own_credentials = NULL;
126 GError *error = NULL;
127
128 if (!credentials) {
129 g_warning ("No credentials received from Liferea.\n");
130 return FALSE;
131 }
132
133 own_credentials = g_credentials_new ();
134
135 if (g_credentials_is_same_user (credentials, own_credentials, &error)) {
136 authorized = TRUE;
137 } else {
138 g_warning ("Error authorizing connection to Liferea : %s\n", error->message);
139 g_error_free (error);
140 }
141 g_object_unref (own_credentials);
142
143 return authorized;
144 }
145
146 static void
handle_dbus_method_call(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)147 handle_dbus_method_call (GDBusConnection *connection,
148 const gchar *sender,
149 const gchar *object_path,
150 const gchar *interface_name,
151 const gchar *method_name,
152 GVariant *parameters,
153 GDBusMethodInvocation *invocation,
154 gpointer user_data)
155 {
156 if (g_strcmp0 (method_name, "ScrollPageDown") == 0) {
157 guint64 page_id;
158 gboolean scrolled;
159
160 g_variant_get (parameters, "(t)", &page_id);
161 scrolled = liferea_web_extension_scroll_page_down (LIFEREA_WEB_EXTENSION (user_data), page_id);
162 g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", scrolled));
163 }
164 }
165
166 static const GDBusInterfaceVTable interface_vtable = {
167 handle_dbus_method_call,
168 NULL,
169 NULL
170 };
171
172 static void
liferea_web_extension_emit_page_created(LifereaWebExtension * extension,guint64 page_id)173 liferea_web_extension_emit_page_created (LifereaWebExtension *extension, guint64 page_id)
174 {
175 g_dbus_connection_emit_signal (
176 extension->connection,
177 NULL,
178 LIFEREA_WEB_EXTENSION_OBJECT_PATH,
179 LIFEREA_WEB_EXTENSION_INTERFACE_NAME,
180 "PageCreated",
181 g_variant_new ("(t)", page_id),
182 NULL);
183 }
184
185 static void
liferea_web_extension_queue_page_created(LifereaWebExtension * extension,guint64 page_id)186 liferea_web_extension_queue_page_created (LifereaWebExtension *extension, guint64 page_id)
187 {
188 if (!extension->pending_pages_created) {
189 extension->pending_pages_created = g_array_new (FALSE, FALSE, sizeof (guint64));
190 }
191
192 g_array_append_val (extension->pending_pages_created, page_id);
193 }
194
195 static void
liferea_web_extension_emit_pending_pages_created(LifereaWebExtension * extension)196 liferea_web_extension_emit_pending_pages_created (LifereaWebExtension *extension)
197 {
198 guint i;
199
200 if (!extension->pending_pages_created)
201 return;
202
203 for (i = 0;i<extension->pending_pages_created->len;++i) {
204 guint64 page_id = g_array_index (extension->pending_pages_created, guint64, i);
205 liferea_web_extension_emit_page_created (extension, page_id);
206 }
207 g_array_free (extension->pending_pages_created, TRUE);
208 extension->pending_pages_created = NULL;
209 }
210
211 static gboolean
on_send_request(WebKitWebPage * web_page,WebKitURIRequest * request,WebKitURIResponse * redirected_response,gpointer web_extension)212 on_send_request (WebKitWebPage *web_page,
213 WebKitURIRequest *request,
214 WebKitURIResponse *redirected_response,
215 gpointer web_extension)
216 {
217 SoupMessageHeaders *headers = webkit_uri_request_get_http_headers (request);
218 gboolean do_not_track;
219
220 do_not_track = g_settings_get_boolean (
221 LIFEREA_WEB_EXTENSION (web_extension)->liferea_settings,
222 "do-not-track");
223
224 if (do_not_track && headers) {
225 soup_message_headers_append (headers, "DNT", "1");
226 }
227
228 return FALSE;
229 }
230
231 static void
on_page_created(WebKitWebExtension * webkit_extension,WebKitWebPage * web_page,gpointer extension)232 on_page_created (WebKitWebExtension *webkit_extension,
233 WebKitWebPage *web_page,
234 gpointer extension)
235 {
236 guint64 page_id;
237
238 g_signal_connect (
239 web_page,
240 "send-request",
241 G_CALLBACK (on_send_request),
242 extension
243 );
244
245 page_id = webkit_web_page_get_id (web_page);
246 if (LIFEREA_WEB_EXTENSION (extension)->connection) {
247 liferea_web_extension_emit_page_created (LIFEREA_WEB_EXTENSION (extension), page_id);
248 } else {
249 liferea_web_extension_queue_page_created (LIFEREA_WEB_EXTENSION (extension), page_id);
250 }
251 }
252
253 static void
on_dbus_connection_created(GObject * source_object,GAsyncResult * result,gpointer user_data)254 on_dbus_connection_created (GObject *source_object,
255 GAsyncResult *result,
256 gpointer user_data)
257 {
258 GDBusNodeInfo *introspection_data = NULL;
259 GDBusConnection *connection = NULL;
260 guint registration_id = 0;
261 GError *error = NULL;
262 LifereaWebExtension *extension = LIFEREA_WEB_EXTENSION (user_data);
263
264 introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
265
266 connection = g_dbus_connection_new_for_address_finish (result, &error);
267 if (error) {
268 g_warning ("Extension failed to connect : %s", error->message);
269 g_error_free (error);
270 return;
271 }
272
273 registration_id = g_dbus_connection_register_object (connection,
274 LIFEREA_WEB_EXTENSION_OBJECT_PATH,
275 introspection_data->interfaces[0],
276 &interface_vtable,
277 extension,
278 NULL,
279 &error);
280
281 g_dbus_node_info_unref (introspection_data);
282 if (!registration_id) {
283 g_warning ("Failed to register web extension object: %s\n", error->message);
284 g_error_free (error);
285 g_object_unref (connection);
286 return;
287 }
288
289 extension->connection = connection;
290 liferea_web_extension_emit_pending_pages_created (extension);
291 }
292
293 static gpointer
liferea_web_extension_new(gpointer data)294 liferea_web_extension_new (gpointer data)
295 {
296 return g_object_new (LIFEREA_TYPE_WEB_EXTENSION, NULL);
297 }
298
299 LifereaWebExtension *
liferea_web_extension_get(void)300 liferea_web_extension_get (void)
301 {
302 static GOnce init_once = G_ONCE_INIT;
303
304 g_once (&init_once, liferea_web_extension_new, NULL);
305
306 return init_once.retval;
307 }
308
309 void
liferea_web_extension_initialize(LifereaWebExtension * extension,WebKitWebExtension * webkit_extension,const gchar * server_address)310 liferea_web_extension_initialize (LifereaWebExtension *extension,
311 WebKitWebExtension *webkit_extension,
312 const gchar *server_address)
313 {
314
315 if (extension->initialized)
316 return;
317
318 g_signal_connect (
319 webkit_extension,
320 "page-created",
321 G_CALLBACK (on_page_created),
322 extension);
323
324 GDBusAuthObserver *observer;
325
326 extension->initialized = TRUE;
327 extension->webkit_extension = g_object_ref (webkit_extension);
328
329 observer = g_dbus_auth_observer_new ();
330
331 g_signal_connect (
332 observer,
333 "authorize-authenticated-peer",
334 G_CALLBACK (on_authorize_authenticated_peer),
335 extension);
336
337 g_dbus_connection_new_for_address (
338 server_address,
339 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
340 observer,
341 NULL,
342 (GAsyncReadyCallback)on_dbus_connection_created,
343 extension);
344
345 g_object_unref (observer);
346 }
347