GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-html-webkit2.cpp
1/********************************************************************
2 * gnc-html-webkit.c -- gnucash report renderer using webkit *
3 * *
4 * Copyright (C) 2000 Bill Gribble <grib@billgribble.com> *
5 * Copyright (C) 2001 Linas Vepstas <linas@linas.org> *
6 * Copyright (C) 2009 Phil Longstaff <plongstaff@rogers.com> *
7 * *
8 * This program is free software; you can redistribute it and/or *
9 * modify it under the terms of the GNU General Public License as *
10 * published by the Free Software Foundation; either version 2 of *
11 * the License, or (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License*
19 * along with this program; if not, contact: *
20 * *
21 * Free Software Foundation Voice: +1-617-542-5942 *
22 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
23 * Boston, MA 02110-1301, USA gnu@gnu.org *
24 ********************************************************************/
25
26#include <config.h>
27
28#include <platform.h>
29#ifdef __MINGW32__
30#define _GL_UNISTD_H //Deflect poisonous define of close in Guile's GnuLib
31#endif
32#include <libguile.h>
33#if PLATFORM(WINDOWS)
34#include <windows.h>
35#endif
36
37#include <gtk/gtk.h>
38#include <glib/gi18n.h>
39#include <glib/gstdio.h>
40#include <sys/types.h>
41#include <sys/stat.h>
42#include <cstdlib>
43#include <string.h>
44#include <cerrno>
45#include <fcntl.h>
46#include <unistd.h>
47#include <regex.h>
48
49#include <webkit2/webkit2.h>
50
51#include "Account.h"
52#include "gnc-prefs.h"
53#include "gnc-gui-query.h"
54#include "gnc-engine.h"
55#include "gnc-html.h"
56#include "gnc-html-webkit.hpp"
57#include "gnc-html-history.h"
58#include "print-session.h"
59
60
61G_DEFINE_TYPE(GncHtmlWebkit, gnc_html_webkit, GNC_TYPE_HTML )
62
63static void gnc_html_webkit_dispose( GObject* obj );
64static void gnc_html_webkit_finalize( GObject* obj );
65
66#define GNC_HTML_WEBKIT_GET_PRIVATE(o) (GNC_HTML_WEBKIT(o)->priv)
67
68#include "gnc-html-webkit-p.hpp"
69
70/* indicates the debugging module that this .o belongs to. */
71static QofLogModule log_module = GNC_MOD_HTML;
72
73/* hashes an HTML <object classid="ID"> classid to a handler function */
74extern GHashTable* gnc_html_object_handlers;
75
76/* hashes handlers for loading different URLType data */
77extern GHashTable* gnc_html_stream_handlers;
78
79/* hashes handlers for handling different URLType data */
80extern GHashTable* gnc_html_url_handlers;
81
82static char error_404_format[] = "<html><body><h3>%s</h3><p>%s</body></html>";
83static char error_404_title[] = N_("Not found");
84static char error_404_body[] = N_("The specified URL could not be loaded.");
85
86#define BASE_URI_NAME "base-uri"
87#define GNC_PREF_RPT_DFLT_ZOOM "default-zoom"
88
89static gboolean webkit_decide_policy_cb (WebKitWebView* web_view,
90 WebKitPolicyDecision *decision,
91 WebKitPolicyDecisionType decision_type,
92 gpointer user_data);
93static void webkit_mouse_target_cb (WebKitWebView* web_view,
94 WebKitHitTestResult *hit,
95 guint modifiers, gpointer data);
96static gboolean webkit_notification_cb (WebKitWebView *web_view,
97 WebKitNotification *note,
98 gpointer user_data);
99static gboolean webkit_load_failed_cb (WebKitWebView *web_view,
100 WebKitLoadEvent event,
101 gchar *uri, GError *error,
102 gpointer user_data);
103static void webkit_resource_load_started_cb (WebKitWebView *web_view,
104 WebKitWebResource *resource,
105 WebKitURIRequest *request,
106 gpointer data);
107static gchar* handle_embedded_object( GncHtmlWebkit* self, gchar* html_str );
108static void impl_webkit_show_url( GncHtml* self, URLType type,
109 const gchar* location, const gchar* label,
110 gboolean new_window_hint );
111static void impl_webkit_show_data( GncHtml* self, const gchar* data, int datalen );
112static void impl_webkit_reload( GncHtml* self, gboolean force_rebuild );
113static void impl_webkit_copy_to_clipboard( GncHtml* self );
114static gboolean impl_webkit_export_to_file( GncHtml* self, const gchar* filepath );
115static void impl_webkit_print (GncHtml* self,const gchar* jobname);
116static void impl_webkit_cancel( GncHtml* self );
117static void impl_webkit_set_parent( GncHtml* self, GtkWindow* parent );
118static void impl_webkit_default_zoom_changed(gpointer prefs, gchar *pref, gpointer user_data);
119
120static GtkWidget*
121gnc_html_webkit_webview_new (void)
122{
123 GtkWidget *view = webkit_web_view_new ();
124 WebKitSettings *webkit_settings = nullptr;
125 const char *default_font_family = nullptr;
126 GtkStyleContext *style = gtk_widget_get_style_context (view);
127 GValue val = G_VALUE_INIT;
128 GtkStateFlags state = gtk_style_context_get_state (style);
129 gtk_style_context_get_property (style, GTK_STYLE_PROPERTY_FONT,
130 state, &val);
131
132 if (G_VALUE_HOLDS_BOXED (&val))
133 {
134 const PangoFontDescription *font =
135 (const PangoFontDescription*)g_value_get_boxed (&val);
136 default_font_family = pango_font_description_get_family (font);
137 }
138/* Set default webkit settings */
139 webkit_settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));
140 g_object_set (G_OBJECT(webkit_settings),
141 "default-charset", "utf-8",
142 "allow-file-access-from-file-urls", TRUE,
143 "allow-universal-access-from-file-urls", TRUE,
144 "enable-java", FALSE,
145 "enable-page-cache", FALSE,
146 "enable-plugins", FALSE,
147 "enable-site-specific-quirks", FALSE,
148 "enable-xss-auditor", FALSE,
149 "enable-developer-extras", TRUE,
150 nullptr);
151 if (default_font_family != nullptr)
152 {
153 g_object_set (G_OBJECT (webkit_settings),
154 "default-font-family", default_font_family, nullptr);
155 }
156 g_value_unset (&val);
157 return view;
158}
159
160static void
161gnc_html_webkit_init( GncHtmlWebkit* self )
162{
163 const gpointer p = g_realloc (GNC_HTML(self)->priv, sizeof(GncHtmlWebkitPrivate));
164 auto new_priv = reinterpret_cast<GncHtmlWebkitPrivate *>(p);
165 auto priv = self->priv = new_priv;
166 GNC_HTML(self)->priv = (GncHtmlPrivate*)priv;
167
168 priv->html_string = nullptr;
169 priv->web_view = WEBKIT_WEB_VIEW (gnc_html_webkit_webview_new ());
170
171
172 /* Scale everything up */
173 gdouble zoom = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL_REPORT,
174 GNC_PREF_RPT_DFLT_ZOOM);
175 webkit_web_view_set_zoom_level (priv->web_view, zoom);
176
177
178 gtk_container_add( GTK_CONTAINER(priv->base.container),
179 GTK_WIDGET(priv->web_view) );
180
181 g_object_ref_sink( priv->base.container );
182
183 /* signals */
184 g_signal_connect (priv->web_view, "decide-policy",
185 G_CALLBACK (webkit_decide_policy_cb),
186 self);
187
188 g_signal_connect (priv->web_view, "mouse-target-changed",
189 G_CALLBACK (webkit_mouse_target_cb),
190 self);
191
192 g_signal_connect (priv->web_view, "show-notification",
193 G_CALLBACK (webkit_notification_cb),
194 self);
195
196 g_signal_connect (priv->web_view, "load-failed",
197 G_CALLBACK (webkit_load_failed_cb),
198 self);
199 g_signal_connect (priv->web_view, "resource-load-started",
200 G_CALLBACK (webkit_resource_load_started_cb),
201 self);
202 gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL_REPORT,
203 GNC_PREF_RPT_DFLT_ZOOM,
204 reinterpret_cast<gpointer>(impl_webkit_default_zoom_changed),
205 self);
206
207 LEAVE("retval %p", self);
208}
209
210static void
211gnc_html_webkit_class_init( GncHtmlWebkitClass* klass )
212{
213 GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
214 GncHtmlClass* html_class = GNC_HTML_CLASS(klass);
215
216 gobject_class->dispose = gnc_html_webkit_dispose;
217 gobject_class->finalize = gnc_html_webkit_finalize;
218
219 html_class->show_url = impl_webkit_show_url;
220 html_class->show_data = impl_webkit_show_data;
221 html_class->reload = impl_webkit_reload;
222 html_class->copy_to_clipboard = impl_webkit_copy_to_clipboard;
223 html_class->export_to_file = impl_webkit_export_to_file;
224 html_class->print = impl_webkit_print;
225 html_class->cancel = impl_webkit_cancel;
226 html_class->set_parent = impl_webkit_set_parent;
227}
228
229static void
230gnc_html_webkit_dispose( GObject* obj )
231{
232 GncHtmlWebkit* self = GNC_HTML_WEBKIT(obj);
233 GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
234
235 if ( priv->web_view != nullptr )
236 {
237 gtk_container_remove (GTK_CONTAINER(priv->base.container),
238 GTK_WIDGET(priv->web_view));
239
240 priv->web_view = nullptr;
241 }
242
243 if ( priv->html_string != nullptr )
244 {
245 g_free( priv->html_string );
246 priv->html_string = nullptr;
247 }
248
249 gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REPORT,
250 GNC_PREF_RPT_DFLT_ZOOM,
251 reinterpret_cast<gpointer>(impl_webkit_default_zoom_changed),
252 obj);
253
254 G_OBJECT_CLASS(gnc_html_webkit_parent_class)->dispose( obj );
255}
256
257static void
258gnc_html_webkit_finalize( GObject* obj )
259{
260 GncHtmlWebkit* self = GNC_HTML_WEBKIT(obj);
261
262 self->priv = nullptr;
263
264 G_OBJECT_CLASS(gnc_html_webkit_parent_class)->finalize( obj );
265}
266
267/*****************************************************************************/
268
269static char*
270extract_base_name(URLType type, const gchar* path)
271{
272 constexpr gchar machine_rexp[] = "^(//[^/]*)/*(/.*)?$";
273 constexpr gchar path_rexp[] = "^/*(.*)/+([^/]*)$";
274 regex_t compiled_m, compiled_p;
275 constexpr size_t MATCH_LEN = 4;
276 regmatch_t match[MATCH_LEN];
277 gchar * machine = nullptr, * location = nullptr, * base = nullptr;
278 gchar * basename = nullptr;
279
280 DEBUG(" ");
281 if (!path) return nullptr;
282
283 regcomp(&compiled_m, machine_rexp, REG_EXTENDED);
284 regcomp(&compiled_p, path_rexp, REG_EXTENDED);
285
286 if (!g_strcmp0 (type, URL_TYPE_HTTP) ||
287 !g_strcmp0 (type, URL_TYPE_SECURE) ||
288 !g_strcmp0 (type, URL_TYPE_FTP))
289 {
290
291 /* step 1: split the machine name away from the path
292 * components */
293 if (!regexec(&compiled_m, path, MATCH_LEN, match, 0))
294 {
295 /* $1 is the machine name */
296 if (match[1].rm_so != -1)
297 {
298 machine = g_strndup(path + match[1].rm_so,
299 match[1].rm_eo - match[1].rm_so);
300 }
301 /* $2 is the path */
302 if (match[2].rm_so != -1)
303 {
304 location = g_strndup(path + match[2].rm_so,
305 match[2].rm_eo - match[2].rm_so);
306 }
307 }
308 }
309 else
310 {
311 location = g_strdup(path);
312 }
313 /* step 2: split up the path into prefix and file components */
314 if (location)
315 {
316 if (!regexec(&compiled_p, location, 4, match, 0))
317 {
318 if (match[1].rm_so != -1)
319 {
320 base = g_strndup(location + match[1].rm_so,
321 match[1].rm_eo - match[1].rm_so);
322 }
323 }
324 }
325
326 regfree(&compiled_m);
327 regfree(&compiled_p);
328
329 if (machine)
330 {
331 if (base && (strlen(base) > 0))
332 {
333 basename = g_strconcat(machine, "/", base, "/", nullptr);
334 }
335 else
336 {
337 basename = g_strconcat(machine, "/", nullptr);
338 }
339 }
340 else
341 {
342 if (base && (strlen(base) > 0))
343 {
344 basename = g_strdup(base);
345 }
346 }
347
348 g_free(machine);
349 g_free(base);
350 g_free(location);
351 return basename;
352}
353
354static gboolean
355http_allowed()
356{
357 return TRUE;
358}
359
360static gboolean
361https_allowed()
362{
363 return TRUE;
364}
365
366static gchar*
367handle_embedded_object( GncHtmlWebkit* self, gchar* html_str )
368{
369 // Find the <object> tag and get the classid from it. This will provide the correct
370 // object callback handler. Pass the <object> entity text to the handler. What should
371 // come back is embedded image information.
372 gchar* remainder_str = html_str;
373 gchar* object_tag;
374 gchar* end_object_tag;
375 gchar* object_contents;
376 gchar* html_str_start = nullptr;
377 gchar* html_str_middle;
378 gchar* html_str_result = nullptr;
379 gchar* classid_start;
380 gchar* classid_end;
381 gchar* classid_str;
382 gchar* new_chunk;
383 GncHTMLObjectCB h;
384
385 object_tag = g_strstr_len( remainder_str, -1, "<object classid=" );
386 while (object_tag)
387 {
388
389 classid_start = object_tag + strlen( "<object classid=" ) + 1;
390 classid_end = g_strstr_len( classid_start, -1, "\"" );
391 classid_str = g_strndup( classid_start, (classid_end - classid_start) );
392
393 end_object_tag = g_strstr_len( object_tag, -1, "</object>" );
394 if ( end_object_tag == nullptr )
395 {
396 /* Hmmm... no object end tag
397 Return the original html string because we can't properly parse it */
398 g_free (classid_str);
399 g_free (html_str_result);
400 return g_strdup (html_str);
401 }
402 end_object_tag += strlen( "</object>" );
403 object_contents = g_strndup( object_tag, (end_object_tag - object_tag) );
404
405 const gpointer p = g_hash_table_lookup( gnc_html_object_handlers, classid_str );
406 h = reinterpret_cast<GncHTMLObjectCB>(p);
407 if ( h != nullptr )
408 {
409 (void)h( GNC_HTML(self), object_contents, &html_str_middle );
410 }
411 else
412 {
413 html_str_middle = g_strdup_printf( "No handler found for classid \"%s\"", classid_str );
414 }
415
416 html_str_start = html_str_result;
417 new_chunk = g_strndup (remainder_str, (object_tag - remainder_str));
418 if (!html_str_start)
419 html_str_result = g_strconcat (new_chunk, html_str_middle, nullptr);
420 else
421 html_str_result = g_strconcat (html_str_start, new_chunk, html_str_middle, nullptr);
422
423 g_free( html_str_start );
424 g_free( new_chunk );
425 g_free( html_str_middle );
426
427 remainder_str = end_object_tag;
428 object_tag = g_strstr_len( remainder_str, -1, "<object classid=" );
429 }
430
431 if (html_str_result)
432 {
433 html_str_start = html_str_result;
434 html_str_result = g_strconcat (html_str_start, remainder_str, nullptr);
435 g_free (html_str_start);
436 }
437 else
438 html_str_result = g_strdup (remainder_str);
439
440 return html_str_result;
441}
442
443/********************************************************************
444 * load_to_stream : actually do the work of loading the HTML
445 * or binary data referenced by a URL and feeding it into the webkit
446 * widget.
447 ********************************************************************/
448
449static gboolean
450load_to_stream( GncHtmlWebkit* self, URLType type,
451 const gchar* location, const gchar* label )
452{
453 gchar* fdata = nullptr;
454 int fdata_len = 0;
455 GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
456
457 DEBUG( "type %s, location %s, label %s", type ? type : "(null)",
458 location ? location : "(null)", label ? label : "(null)");
459
460 g_return_val_if_fail( self != nullptr, FALSE );
461
462 if ( gnc_html_stream_handlers != nullptr )
463 {
464 const gpointer p = g_hash_table_lookup( gnc_html_stream_handlers, type );
465 GncHTMLStreamCB stream_handler = reinterpret_cast<GncHTMLStreamCB>(p);
466 if ( stream_handler )
467 {
468 GncHtml *weak_html = GNC_HTML(self);
469
470 g_object_add_weak_pointer(G_OBJECT(self),
471 (gpointer*)(&weak_html));
472 bool ok = stream_handler( location, &fdata, &fdata_len );
473
474 if (!weak_html) // will be nullptr if self has been destroyed
475 {
476 g_free (fdata);
477 return FALSE;
478 }
479 else
480 {
481 g_object_remove_weak_pointer(G_OBJECT(self),
482 (gpointer*)(&weak_html));
483 }
484
485 if ( ok )
486 {
487 fdata = fdata ? fdata : g_strdup( "" );
488
489 // Until webkitgtk supports download requests,
490 // look for "<object classid=" indicating the
491 // beginning of an embedded graph. If found,
492 // handle it
493 if ( g_strstr_len( fdata, -1, "<object classid=" ) != nullptr )
494 {
495 gchar *new_fdata = handle_embedded_object( self, fdata );
496 g_free( fdata );
497 fdata = new_fdata;
498 }
499
500 // Save a copy for export purposes
501 if ( priv->html_string != nullptr )
502 {
503 g_free( priv->html_string );
504 }
505 priv->html_string = g_strdup( fdata );
506 impl_webkit_show_data( GNC_HTML(self), fdata, strlen(fdata) );
507 }
508 else
509 {
510 fdata = fdata ? fdata :
511 g_strdup_printf( error_404_format,
512 _(error_404_title), _(error_404_body) );
513 webkit_web_view_load_html (priv->web_view, fdata,
514 BASE_URI_NAME);
515 }
516
517 g_free( fdata );
518
519 if ( label )
520 {
521 while ( gtk_events_pending() )
522 {
523 gtk_main_iteration();
524 }
525 /* No action required: Webkit jumps to the anchor on its own. */
526 }
527 return TRUE;
528 }
529 }
530
531 do
532 {
533 if ( !g_strcmp0( type, URL_TYPE_SECURE ) ||
534 !g_strcmp0( type, URL_TYPE_HTTP ) )
535 {
536
537 if ( !g_strcmp0( type, URL_TYPE_SECURE ) )
538 {
539 if ( !https_allowed() )
540 {
541 gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
542 _("Secure HTTP access is disabled. "
543 "You can enable it in the Network section of "
544 "the Preferences dialog."));
545 break;
546 }
547 }
548
549 if ( !http_allowed() )
550 {
551 gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
552 _("Network HTTP access is disabled. "
553 "You can enable it in the Network section of "
554 "the Preferences dialog."));
555 }
556 else
557 {
558 gnc_build_url( type, location, label );
559 }
560 }
561 else
562 {
563 PWARN( "load_to_stream for inappropriate type\n"
564 "\turl = '%s#%s'\n",
565 location ? location : "(null)",
566 label ? label : "(null)" );
567 fdata = g_strdup_printf( error_404_format,
568 _(error_404_title), _(error_404_body) );
569 webkit_web_view_load_html (priv->web_view, fdata, BASE_URI_NAME);
570 g_free( fdata );
571 }
572 }
573 while ( false );
574 return TRUE;
575}
576
577static gboolean
578perform_navigation_policy (WebKitWebView *web_view,
579 WebKitNavigationPolicyDecision *decision,
580 GncHtml *self)
581{
582 gchar *location = nullptr, *label = nullptr;
583 bool ignore = false;
584 WebKitNavigationAction *action =
585 webkit_navigation_policy_decision_get_navigation_action (decision);
586 if (webkit_navigation_action_get_navigation_type (action) !=
587 WEBKIT_NAVIGATION_TYPE_LINK_CLICKED)
588 {
589 webkit_policy_decision_use ((WebKitPolicyDecision*)decision);
590 return TRUE;
591 }
592 auto req = webkit_navigation_action_get_request (action);
593 const gchar *uri = webkit_uri_request_get_uri (req);
594 const gchar *scheme = gnc_html_parse_url (self, uri, &location, &label);
595 if (strcmp (scheme, URL_TYPE_FILE) != 0)
596 {
597 impl_webkit_show_url (self, scheme, location, label, FALSE);
598 ignore = true;
599 }
600 g_free (location);
601 g_free (label);
602 if (ignore)
603 webkit_policy_decision_ignore ((WebKitPolicyDecision*)decision);
604 else
605 webkit_policy_decision_use ((WebKitPolicyDecision*)decision);
606 return TRUE;
607}
608
609static gboolean
610webkit_decide_policy_cb (WebKitWebView *web_view,
611 WebKitPolicyDecision *decision,
612 WebKitPolicyDecisionType decision_type,
613 gpointer user_data)
614{
615/* This turns out to be the signal to intercept for handling a link-click. */
616 if (decision_type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION)
617 {
618 webkit_policy_decision_use (decision);
619 return TRUE;
620 }
621 return perform_navigation_policy (
622 web_view, (WebKitNavigationPolicyDecision*) decision,
623 GNC_HTML (user_data));
624}
625
626static void
627webkit_mouse_target_cb (WebKitWebView *web_view, WebKitHitTestResult *hit,
628 guint modifiers, gpointer user_data)
629{
630 if (!webkit_hit_test_result_context_is_link (hit))
631 return;
632
633 auto self = static_cast<GncHtmlWebkit*>(user_data);
634 auto priv = GNC_HTML_WEBKIT_GET_PRIVATE (self);
635 gchar *uri = g_strdup (webkit_hit_test_result_get_link_uri (hit));
636 g_free (priv->base.current_link);
637 priv->base.current_link = uri;
638 if (priv->base.flyover_cb)
639 {
640 (priv->base.flyover_cb) (GNC_HTML (self), uri,
641 priv->base.flyover_cb_data);
642 }
643}
644
645static gboolean
646webkit_notification_cb (WebKitWebView* web_view, WebKitNotification *note,
647 gpointer user_data)
648{
649 GncHtmlWebkit *self = (GncHtmlWebkit*)user_data;
650 g_return_val_if_fail (self != nullptr, FALSE);
651 g_return_val_if_fail (note != nullptr, FALSE);
652
653 auto top = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (web_view)));
654 auto dialog = gtk_message_dialog_new (top, GTK_DIALOG_MODAL,
655 GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
656 "%s\n%s",
657 webkit_notification_get_title (note),
658 webkit_notification_get_body (note));
659 gtk_dialog_run (GTK_DIALOG (dialog));
660 gtk_widget_destroy (dialog);
661 return TRUE;
662}
663
664static gboolean
665webkit_load_failed_cb (WebKitWebView *web_view, WebKitLoadEvent event,
666 gchar *uri, GError *error, gpointer user_data)
667{
668 PERR ("WebKit load of %s failed due to %s\n", uri, error->message);
669 return FALSE;
670}
671
672static void
673webkit_resource_load_failed_cb (WebKitWebResource *resource,
674 GError *error,
675 gpointer data)
676{
677 WebKitURIResponse *response = webkit_web_resource_get_response (resource);
678 const gchar * uri = webkit_web_resource_get_uri (resource);
679 PERR ("Load of resource at %s failed with error %s and status code %d.\n",
680 uri, error->message, webkit_uri_response_get_status_code (response));
681}
682
683static void
684webkit_resource_load_finished_cb (WebKitWebResource *resource, gpointer data)
685{
686 DEBUG ("Load of resource %s completed.\n", webkit_web_resource_get_uri(resource));
687}
688
689static void
690webkit_resource_load_started_cb (WebKitWebView *web_view,
691 WebKitWebResource *resource,
692 WebKitURIRequest *request,
693 gpointer data)
694{
695 DEBUG ("Load of resource %s begun.\n", webkit_web_resource_get_uri(resource));
696 g_signal_connect (resource, "failed",
697 G_CALLBACK (webkit_resource_load_failed_cb),
698 data);
699 g_signal_connect (resource, "finished",
700 G_CALLBACK (webkit_resource_load_finished_cb),
701 data);
702}
703
704/********************************************************************
705 * gnc_html_open_scm
706 * insert some scheme-generated HTML
707 ********************************************************************/
708
709static void
710gnc_html_open_scm( GncHtmlWebkit* self, const gchar * location,
711 const gchar * label, int newwin )
712{
713 PINFO("location='%s'", location ? location : "(null)");
714}
715
716
717/********************************************************************
718 * gnc_html_show_data
719 * display some HTML that the creator of the gnc-html got from
720 * somewhere.
721 ********************************************************************/
722
723static void
724impl_webkit_show_data( GncHtml* self, const gchar* data, int datalen )
725{
726 constexpr char TEMPLATE_REPORT_FILE_NAME[] = "gnc-report-XXXXXX.html";
727 g_return_if_fail( self != nullptr );
728 g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
729
730 ENTER( "datalen %d, data %20.20s", datalen, data );
731
732 auto priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
733
734 /* Export the HTML to a file and load the file URI. On Linux, this seems to get around some
735 security problems (otherwise, it can complain that embedded images aren't permitted to be
736 viewed because they are local resources). On Windows, this allows the embedded images to
737 be viewed (maybe for the same reason as on Linux, but I haven't found where it puts those
738 messages. */
739 gchar *filename = g_build_filename(g_get_tmp_dir(), TEMPLATE_REPORT_FILE_NAME, (gchar *)nullptr);
740 int fd = g_mkstemp( filename );
741 impl_webkit_export_to_file( self, filename );
742 close( fd );
743 gchar *uri = g_strdup_printf( "file://%s", filename );
744 g_free(filename);
745 DEBUG("Loading uri '%s'", uri);
746 webkit_web_view_load_uri( priv->web_view, uri );
747 g_free( uri );
748
749 LEAVE("");
750}
751
752/********************************************************************
753 * gnc_html_show_url
754 *
755 * open a URL. This is called when the user clicks a link or
756 * for the creator of the gnc_html window to explicitly request
757 * a URL.
758 ********************************************************************/
759
760static void
761impl_webkit_show_url( GncHtml* self, URLType type,
762 const gchar* location, const gchar* label,
763 gboolean new_window_hint )
764{
765 GncHTMLUrlCB url_handler = nullptr;
766 bool new_window = false;
767 bool stream_loaded = false;
768
769 g_return_if_fail( self != nullptr );
770 g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
771 g_return_if_fail( location != nullptr );
772
773 auto priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
774
775 /* make sure it's OK to show this URL type in this window */
776 if ( new_window_hint == 0 )
777 {
778 if ( priv->base.urltype_cb )
779 {
780 new_window = !((priv->base.urltype_cb)( type ));
781 }
782 }
783 else
784 {
785 new_window = true;
786 }
787
788 if ( !new_window )
789 {
790 gnc_html_cancel( GNC_HTML(self) );
791 }
792
793 if ( gnc_html_url_handlers )
794 {
795 const gpointer p = g_hash_table_lookup( gnc_html_url_handlers, type );
796 url_handler = reinterpret_cast<GncHTMLUrlCB>(p);
797 }
798
799 if ( url_handler )
800 {
801 GNCURLResult result;
802
803 result.load_to_stream = FALSE;
804 result.url_type = type;
805 result.location = nullptr;
806 result.label = nullptr;
807 result.base_type = URL_TYPE_FILE;
808 result.base_location = nullptr;
809 result.error_message = nullptr;
810 result.parent = GTK_WINDOW (priv->base.parent);
811
812 bool ok = url_handler( location, label, new_window, &result );
813 if ( !ok )
814 {
815 if ( result.error_message )
816 {
817 gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s", result.error_message );
818 }
819 else
820 {
821 /* %s is a URL (some location somewhere). */
822 gnc_error_dialog (GTK_WINDOW (priv->base.parent), _("There was an error accessing %s."), location );
823 }
824
825 if ( priv->base.load_cb )
826 {
827 priv->base.load_cb( GNC_HTML(self), result.url_type,
828 location, label, priv->base.load_cb_data );
829 }
830 }
831 else if ( result.load_to_stream )
832 {
833 const char *new_location = result.location ? result.location : location;
834 const char *new_label = result.label ? result.label : label;
835 auto hnode = gnc_html_history_node_new( result.url_type, new_location, new_label );
836
837 gnc_html_history_append( priv->base.history, hnode );
838
839 g_free( priv->base.base_location );
840 priv->base.base_type = result.base_type;
841 priv->base.base_location =
842 g_strdup( extract_base_name( result.base_type, new_location ) );
843 DEBUG( "resetting base location to %s",
844 priv->base.base_location ? priv->base.base_location : "(null)" );
845
846 stream_loaded = load_to_stream( GNC_HTML_WEBKIT(self),
847 result.url_type,
848 new_location, new_label );
849
850 if ( stream_loaded && priv->base.load_cb != nullptr )
851 {
852 priv->base.load_cb( GNC_HTML(self), result.url_type,
853 new_location, new_label, priv->base.load_cb_data );
854 }
855 }
856
857 g_free( result.location );
858 g_free( result.label );
859 g_free( result.base_location );
860 g_free( result.error_message );
861
862 return;
863 }
864
865 if ( g_strcmp0( type, URL_TYPE_SCHEME ) == 0 )
866 {
867 gnc_html_open_scm( GNC_HTML_WEBKIT(self), location, label, new_window );
868
869 }
870 else if ( g_strcmp0( type, URL_TYPE_JUMP ) == 0 )
871 {
872 /* Webkit jumps to the anchor on its own */
873 }
874 else if ( g_strcmp0( type, URL_TYPE_SECURE ) == 0 ||
875 g_strcmp0( type, URL_TYPE_HTTP ) == 0 ||
876 g_strcmp0( type, URL_TYPE_FILE ) == 0 )
877 {
878
879 do
880 {
881 if ( g_strcmp0( type, URL_TYPE_SECURE ) == 0 )
882 {
883 if ( !https_allowed() )
884 {
885 gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
886 _("Secure HTTP access is disabled. "
887 "You can enable it in the Network section of "
888 "the Preferences dialog.") );
889 break;
890 }
891 }
892
893 if ( g_strcmp0( type, URL_TYPE_HTTP ) == 0 )
894 {
895 if ( !http_allowed() )
896 {
897 gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
898 _("Network HTTP access is disabled. "
899 "You can enable it in the Network section of "
900 "the Preferences dialog.") );
901 break;
902 }
903 }
904
905 priv->base.base_type = type;
906
907 if ( priv->base.base_location != nullptr ) g_free( priv->base.base_location );
908 priv->base.base_location = extract_base_name( type, location );
909
910 /* FIXME : handle new_window = 1 */
911 gnc_html_history_append( priv->base.history,
912 gnc_html_history_node_new( type, location, label ) );
913 stream_loaded = load_to_stream( GNC_HTML_WEBKIT(self),
914 type, location, label );
915
916 }
917 while ( false );
918 }
919 else
920 {
921 PERR( "URLType %s not supported.", type );
922 }
923
924 if ( stream_loaded && priv->base.load_cb != nullptr )
925 {
926 (priv->base.load_cb)( GNC_HTML(self), type, location, label, priv->base.load_cb_data );
927 }
928}
929
930
931/********************************************************************
932 * gnc_html_reload
933 * reload the current page
934 * if force_rebuild is TRUE, the report is recreated, if FALSE, report
935 * is reloaded by webkit
936 ********************************************************************/
937
938static void
939impl_webkit_reload( GncHtml* self, gboolean force_rebuild )
940{
941 g_return_if_fail( self != nullptr );
942 g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
943
944 auto priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
945
946 if ( force_rebuild )
947 {
948 gnc_html_history_node *n = gnc_html_history_get_current( priv->base.history );
949 if ( n != nullptr )
950 gnc_html_show_url( self, n->type, n->location, n->label, 0 );
951 }
952 else
953 webkit_web_view_reload( priv->web_view );
954}
955
956
957/********************************************************************
958 * gnc_html_new
959 * create and set up a new webkit widget.
960 ********************************************************************/
961
962GncHtml*
963gnc_html_webkit_new( void ) noexcept
964{
965 auto self = static_cast<GncHtmlWebkit*>(g_object_new( GNC_TYPE_HTML_WEBKIT, nullptr ));
966 return GNC_HTML(self);
967}
968
969/********************************************************************
970 * gnc_html_cancel
971 * cancel any outstanding HTML fetch requests.
972 ********************************************************************/
973
974static gboolean
975webkit_cancel_helper(gpointer key, gpointer value, gpointer user_data)
976{
977 g_free(key);
978 g_list_free((GList *)value);
979 return TRUE;
980}
981
982static void
983impl_webkit_cancel( GncHtml* self )
984{
985 g_return_if_fail( self != nullptr );
986 g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
987
988 auto priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
989
990 g_hash_table_foreach_remove( priv->base.request_info, webkit_cancel_helper, nullptr );
991}
992
993static void
994impl_webkit_copy_to_clipboard( GncHtml* self )
995{
996 g_return_if_fail( self != nullptr );
997 g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
998
999 auto priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1000 webkit_web_view_execute_editing_command (priv->web_view,
1001 WEBKIT_EDITING_COMMAND_COPY);
1002}
1003
1004/**************************************************************
1005 * gnc_html_export_to_file
1006 *
1007 * @param self GncHtmlWebkit object
1008 * @param filepath Where to write the HTML
1009 * @return TRUE if successful, FALSE if unsuccessful
1010 **************************************************************/
1011static gboolean
1012impl_webkit_export_to_file( GncHtml* self, const char *filepath )
1013{
1014 g_return_val_if_fail( self != nullptr, FALSE );
1015 g_return_val_if_fail( GNC_IS_HTML_WEBKIT(self), FALSE );
1016 g_return_val_if_fail( filepath != nullptr, FALSE );
1017
1018 auto priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1019 if ( priv->html_string == nullptr )
1020 {
1021 return FALSE;
1022 }
1023 FILE *fh = g_fopen( filepath, "w" );
1024 if ( fh != nullptr )
1025 {
1026 gint len = strlen( priv->html_string );
1027 gint written = fwrite( priv->html_string, 1, len, fh );
1028 fclose (fh);
1029
1030 if ( written != len )
1031 {
1032 return FALSE;
1033 }
1034
1035 return TRUE;
1036 }
1037 else
1038 {
1039 return FALSE;
1040 }
1041}
1042
1043/* The webkit1 comment was
1044 * If printing on WIN32, in order to prevent the font from being tiny, (see bug
1045 * #591177), A GtkPrintOperation object needs to be created so that the unit can
1046 * be set, and then webkit_web_frame_print_full() needs to be called to use that
1047 * GtkPrintOperation. On other platforms (specifically linux - not sure about
1048 * MacOSX), the version of webkit may not contain the function
1049 * webkit_web_frame_print_full(), so webkit_web_frame_print() is called instead
1050 * (the font size problem doesn't show up on linux).
1051 *
1052 * Webkit2 exposes only a very simple WebKitPrintOperation API. In order to
1053 * implement the above if it proves still to be necessary we'll have to use
1054 * GtkPrintOperation instead, passing it the results of
1055 * webkit_web_view_get_snapshot for each page.
1056 */
1057static void
1058impl_webkit_print (GncHtml* self,const gchar* jobname)
1059{
1060 g_return_if_fail (self != nullptr);
1061 g_return_if_fail (GNC_IS_HTML_WEBKIT (self));
1062
1063 auto priv = GNC_HTML_WEBKIT_GET_PRIVATE (self);
1064 auto op = webkit_print_operation_new (priv->web_view);
1065 gchar *basename = g_path_get_basename(jobname);
1066 auto print_settings = gtk_print_settings_new();
1067 webkit_print_operation_set_print_settings(op, print_settings);
1068 gchar *export_filename = g_strdup(jobname);
1069 g_free(basename);
1070 gtk_print_settings_set(print_settings,
1071 GTK_PRINT_SETTINGS_OUTPUT_BASENAME,
1072 export_filename);
1073 webkit_print_operation_set_print_settings(op, print_settings);
1074 // Open a print dialog
1075 auto top = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (priv->web_view)));
1076 auto print_response = webkit_print_operation_run_dialog (op, top);
1077 if (print_response == WEBKIT_PRINT_OPERATION_RESPONSE_PRINT)
1078 {
1079 // Get the newly updated print settings
1080 g_object_unref(print_settings);
1081 print_settings = g_object_ref(webkit_print_operation_get_print_settings(op));
1082 }
1083 g_free(export_filename);
1084 g_object_unref (op);
1085 g_object_unref (print_settings);
1086}
1087
1088static void
1089impl_webkit_set_parent( GncHtml* self, GtkWindow* parent )
1090{
1091 g_return_if_fail( self != nullptr );
1092 g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
1093
1094 auto priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1095 priv->base.parent = GTK_WIDGET(parent);
1096}
1097
1098static void
1099impl_webkit_default_zoom_changed(gpointer prefs, gchar *pref, gpointer user_data)
1100{
1101 g_return_if_fail(user_data != nullptr);
1102
1103 GncHtmlWebkit* self = GNC_HTML_WEBKIT(user_data);
1104 GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1105 gdouble zoom = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_RPT_DFLT_ZOOM);
1106 webkit_web_view_set_zoom_level (priv->web_view, zoom);
1107}
Account handling public routines.
All type declarations for the whole Gnucash engine.
Generic api to store and retrieve preferences.
#define PINFO(format, args...)
Print an informational note.
Definition qoflog.h:256
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition qoflog.h:282
#define PERR(format, args...)
Log a serious error.
Definition qoflog.h:244
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250
#define ENTER(format, args...)
Print a function entry debugging message.
Definition qoflog.h:272
void gnc_prefs_remove_cb_by_func(const gchar *group, const gchar *pref_name, gpointer func, gpointer user_data)
Remove a function that was registered for a callback when the given preference changed.
gdouble gnc_prefs_get_float(const gchar *group, const gchar *pref_name)
Get an float value from the preferences backend.
gulong gnc_prefs_register_cb(const char *group, const gchar *pref_name, gpointer func, gpointer user_data)
Register a callback that gets triggered when the given preference changes.
Functions for printing.