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