/*
 *  Copyright (C) 2000, 2001, 2002 Marco Pesenti Gritti
 *  Copyright (C) 2002 Tommi Komulainen
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "gtkhtml-embed.h"
#include "gul-gobject-misc.h"

#include <libgtkhtml/view/htmlview.h>
#include <libgnomevfs/gnome-vfs-async-ops.h>
#include <gtk/gtkmessagedialog.h>
#include <string.h>

#define BUFFER_SIZE	2048

typedef struct _Connection Connection;


static void
gtkhtml_embed_class_init (GtkhtmlEmbedClass *klass);
static void
gtkhtml_embed_init (GtkhtmlEmbed *gs);
static void
gtkhtml_embed_finalize (GObject *object);
static void
galeon_embed_init (GaleonEmbedClass *embed_class);

static void
impl_get_capabilities (GaleonEmbed *embed,
                       EmbedCapabilities *caps);
static gresult 
impl_load_url (GaleonEmbed *embed, 
               const char *url);
static gresult 
impl_stop_load (GaleonEmbed *embed);
static gresult 
impl_can_go_back (GaleonEmbed *embed);
static gresult 
impl_can_go_forward (GaleonEmbed *embed);
static gresult 
impl_can_go_up (GaleonEmbed *embed);
static gresult 
impl_get_go_up_list (GaleonEmbed *embed, GSList **l);
static gresult 
impl_go_back (GaleonEmbed *embed);
static gresult  
impl_go_forward (GaleonEmbed *embed);
static gresult
impl_go_up (GaleonEmbed *embed);
static gresult 
impl_render_data (GaleonEmbed *embed, 
                  const char *data,
                  guint32 len,
                  const char *base_uri, 
                  const char *mime_type);
static gresult
impl_open_stream (GaleonEmbed *embed,
                  const char *base_uri,
                  const char *mime_type);
static gresult
impl_append_data (GaleonEmbed *embed,
                  const char *data, 
                  guint32 len);
static gresult
impl_close_stream (GaleonEmbed *embed);
static gresult
impl_get_title (GaleonEmbed *embed,
                char **title);
static gresult 
impl_get_location (GaleonEmbed *embed, 
                   gboolean toplevel,
                   gboolean requested,
                   char **location);
static gresult 
impl_reload (GaleonEmbed *embed, 
             EmbedReloadFlags flags);
static gresult
impl_copy_page (GaleonEmbed *dest,
                GaleonEmbed *source,
                EmbedDisplayType display_type);
static gresult 
impl_grab_focus (GaleonEmbed *embed);
static gresult
impl_get_favicon_location (GaleonEmbed *embed,
                           char **url);
static gresult
impl_get_link_tags (GaleonEmbed *embed,
                    const char *link_type,
                    GList **tags);
static gresult
impl_zoom_set (GaleonEmbed *embed, 
               int zoom, 
               gboolean reflow);
static gresult
impl_zoom_get (GaleonEmbed *embed,
               int *zoom);
static gresult 
impl_selection_can_cut (GaleonEmbed *embed);
static gresult 
impl_selection_can_copy (GaleonEmbed *embed);
static gresult 
impl_can_paste (GaleonEmbed *embed);
static gresult
impl_selection_cut (GaleonEmbed *embed);
static gresult
impl_selection_copy (GaleonEmbed *embed);
static gresult
impl_paste (GaleonEmbed *embed);
static gresult
impl_shistory_count  (GaleonEmbed *embed,
                      int *count);
static gresult
impl_shistory_get_nth (GaleonEmbed *embed, 
                       int nth,
                       gboolean is_relative,
                       char **url,
                       char **title);
static gresult
impl_shistory_get_pos (GaleonEmbed *embed,
                       int *pos);
static gresult
impl_shistory_go_nth (GaleonEmbed *embed, 
                      int nth);
static gboolean
impl_shistory_copy (GaleonEmbed *source,
                    GaleonEmbed *dest);
static gresult
impl_shistory_clear (GaleonEmbed *source);

static gresult
impl_scroll (GaleonEmbed *embed, 
             EmbedScrollDirection direction);
static gresult
impl_get_security_level (GaleonEmbed *embed, 
                         EmbedSecurityLevel level,
                         char **description);
static gresult
impl_find (GaleonEmbed *embed,
           EmbedFindInfo *info);
static gresult
impl_print (GaleonEmbed *embed, 
            EmbedPrintInfo *info);
static gresult
impl_print_preview_close (GaleonEmbed *embed);
static gresult
impl_print_preview_num_pages (GaleonEmbed *embed,
                              gint *retNum);
static gresult
impl_print_preview_navigate (GaleonEmbed *embed,
                             EmbedPrintPreviewNavType navType,
                             gint pageNum);

static gresult
impl_set_charset (GaleonEmbed *embed,
                  const char *charset);
static gresult 
impl_select_all (GaleonEmbed *embed);

static void
gtkhtml_embed_load_uri (GtkhtmlEmbed *embed, GnomeVFSURI *uri);
static gboolean
gtkhtml_embed_set_base (GtkhtmlEmbed *embed, const gchar *text_url);

/* HtmlDocument callbacks */
static gboolean
request_url_cb (HtmlDocument *document, const char *text_url,
		HtmlStream *stream, GtkhtmlEmbed *embed);
static void
link_clicked_cb (HtmlDocument *document, const gchar *text_url,
		 GtkhtmlEmbed *embed);
static void
stream_cancel_cb (HtmlStream *stream, gpointer user_data, Connection *conn);

/* GNOME VFS connections */
struct _Connection
{
	GnomeVFSAsyncHandle	*handle;
	GtkhtmlEmbed		*embed;

	/* the document if we're loading something as user requested, or NULL
	 * if we're loading something embedded in a page, such as images */
	HtmlDocument		*document;

	HtmlStream		*stream;
};

static void
connection_free (Connection *conn);

static void
gtkhtml_embed_add_connection (GtkhtmlEmbed *embed, Connection *conn);
static void
gtkhtml_embed_remove_connection (GtkhtmlEmbed *embed, Connection *conn);
static void
gtkhtml_embed_kill_connections (GtkhtmlEmbed *embed);

/* GNOME VFS callbacks */
static void
vfs_open_callback (GnomeVFSAsyncHandle *handle, GnomeVFSResult result,
		   Connection *conn);
static void
vfs_read_callback (GnomeVFSAsyncHandle *handle, GnomeVFSResult result,
		   gpointer buffer, GnomeVFSFileSize bytes_requested,
		   GnomeVFSFileSize bytes_read, Connection *conn);
static void
vfs_close_callback (GnomeVFSAsyncHandle *handle, GnomeVFSResult result,
		    Connection *conn);

struct GtkhtmlEmbedPrivate
{
	HtmlView	*view;
	HtmlDocument	*document;

	/* base URI of the current page, used to resolve relative URIs */
	GnomeVFSURI	*base_uri;

	GSList		*connection_list;	
};

static GtkScrolledWindowClass *parent_class = NULL;

MAKE_GET_TYPE_IFACE (gtkhtml_embed, "GtkhtmlEmbed", GtkhtmlEmbed,
		     gtkhtml_embed_class_init, gtkhtml_embed_init, GTK_TYPE_SCROLLED_WINDOW,
		     galeon_embed_init, GALEON_EMBED_TYPE);

static void
galeon_embed_init (GaleonEmbedClass *embed_class)
{
	embed_class->get_capabilities = impl_get_capabilities;
	embed_class->load_url = impl_load_url; 
	embed_class->stop_load = impl_stop_load;
	embed_class->can_go_back = impl_can_go_back;
	embed_class->can_go_forward =impl_can_go_forward;
	embed_class->can_go_up = impl_can_go_up;
	embed_class->get_go_up_list = impl_get_go_up_list;
	embed_class->go_back = impl_go_back;
	embed_class->go_forward = impl_go_forward;
	embed_class->go_up = impl_go_up;
	embed_class->render_data = impl_render_data;
	embed_class->open_stream = impl_open_stream;
	embed_class->append_data = impl_append_data;
	embed_class->close_stream = impl_close_stream;
	embed_class->get_title = impl_get_title;
	embed_class->get_location = impl_get_location;
	embed_class->reload = impl_reload;
	embed_class->copy_page = impl_copy_page;
	embed_class->grab_focus = impl_grab_focus;
	embed_class->get_favicon_location = impl_get_favicon_location;
	embed_class->get_link_tags = impl_get_link_tags;
	embed_class->zoom_set = impl_zoom_set;
	embed_class->zoom_get = impl_zoom_get;
	embed_class->selection_can_cut = impl_selection_can_cut;
	embed_class->selection_can_copy = impl_selection_can_copy;
	embed_class->can_paste = impl_can_paste;
	embed_class->selection_cut = impl_selection_cut;
	embed_class->selection_copy = impl_selection_copy;
	embed_class->paste = impl_paste;
	embed_class->shistory_count = impl_shistory_count;
	embed_class->shistory_get_nth = impl_shistory_get_nth;
	embed_class->shistory_get_pos = impl_shistory_get_pos;
	embed_class->shistory_go_nth = impl_shistory_go_nth;
	embed_class->shistory_copy = impl_shistory_copy;
	embed_class->shistory_clear = impl_shistory_clear;
	embed_class->scroll = impl_scroll;
	embed_class->get_security_level = impl_get_security_level;
	embed_class->find = impl_find;
	embed_class->print = impl_print;
	embed_class->set_charset = impl_set_charset;
	embed_class->select_all = impl_select_all;
	embed_class->print_preview_close = impl_print_preview_close;
	embed_class->print_preview_num_pages = impl_print_preview_num_pages;
	embed_class->print_preview_navigate = impl_print_preview_navigate;
}

static void
gtkhtml_embed_class_init (GtkhtmlEmbedClass *klass)
{
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
	
        parent_class = (GtkScrolledWindowClass *) g_type_class_peek_parent (klass);
	
        object_class->finalize = gtkhtml_embed_finalize;
}

static void
gtkhtml_embed_init (GtkhtmlEmbed *embed)
{
        embed->priv = g_new0 (GtkhtmlEmbedPrivate, 1);

	/* HtmlDocument */
	embed->priv->document = html_document_new ();
	g_signal_connect (G_OBJECT (embed->priv->document), "request_url",
			  G_CALLBACK (request_url_cb), embed);
	g_signal_connect (G_OBJECT (embed->priv->document), "link_clicked",
			  G_CALLBACK (link_clicked_cb), embed);

	/* HtmlView */
	embed->priv->view = HTML_VIEW (html_view_new ());
	html_view_set_document (embed->priv->view,
				embed->priv->document);
	gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (embed),
			                     gtk_layout_get_hadjustment 
					      (GTK_LAYOUT (embed->priv->view)));
	gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (embed),
			                     gtk_layout_get_vadjustment 
					      (GTK_LAYOUT (embed->priv->view)));
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (embed),
			                GTK_POLICY_AUTOMATIC,
					GTK_POLICY_AUTOMATIC);

	gtk_container_add (GTK_CONTAINER (embed), GTK_WIDGET (embed->priv->view));
	gtk_widget_show (GTK_WIDGET (embed->priv->view));
}

static void
gtkhtml_embed_finalize (GObject *object)
{
        GtkhtmlEmbed *embed;

        g_return_if_fail (object != NULL);
        g_return_if_fail (IS_GTKHTML_EMBED (object));

	embed = GTKHTML_EMBED (object);

        g_return_if_fail (embed->priv != NULL);

	gtkhtml_embed_kill_connections (embed);
	if (embed->priv->base_uri) gnome_vfs_uri_unref (embed->priv->base_uri);

        g_free (embed->priv);

        G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
impl_get_capabilities (GaleonEmbed *embed,
                       EmbedCapabilities *caps)
{
	*caps = 0;
}

static gresult 
impl_load_url (GaleonEmbed *embed, 
               const char *url)
{
	if (strchr (url, ':') == NULL)
	{
		/* no protocol in url, use http */
		char *http_url = g_strconcat("http://", url, NULL);
		link_clicked_cb (NULL, http_url, GTKHTML_EMBED (embed));
		g_free (http_url);
	}
	else
	{
		link_clicked_cb (NULL, url, GTKHTML_EMBED (embed));
	}
	return G_OK;
}

static gresult 
impl_stop_load (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult 
impl_can_go_back (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult 
impl_can_go_forward (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult 
impl_can_go_up (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult 
impl_get_go_up_list (GaleonEmbed *embed, GSList **l)
{
	return G_NOT_IMPLEMENTED;
}

static gresult 
impl_go_back (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult  
impl_go_forward (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_go_up (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_render_data (GaleonEmbed *embed, 
                  const char *data,
                  guint32 len,
                  const char *base_uri, 
                  const char *mime_type)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_open_stream (GaleonEmbed *embed,
                  const char *base_uri,
                  const char *mime_type)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_append_data (GaleonEmbed *embed,
                  const char *data, 
                  guint32 len)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_close_stream (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_get_title (GaleonEmbed *embed,
                char **title)
{
	return G_NOT_IMPLEMENTED;
}

static gresult 
impl_get_location (GaleonEmbed *ge, 
                   gboolean toplevel,
                   gboolean requested,
                   char **location)
{
	GtkhtmlEmbed *embed = GTKHTML_EMBED (ge);

	if (embed->priv->base_uri == NULL)
	{
		*location = g_strdup ("");
		return G_OK;
	}

	*location = gnome_vfs_uri_to_string (embed->priv->base_uri,
			                     GNOME_VFS_URI_HIDE_NONE);
	return G_OK;
}

static gresult 
impl_reload (GaleonEmbed *embed, 
             EmbedReloadFlags flags)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_copy_page (GaleonEmbed *dest,
                GaleonEmbed *source,
                EmbedDisplayType display_type)
{
	return G_NOT_IMPLEMENTED;
}

static gresult 
impl_grab_focus (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_get_favicon_location (GaleonEmbed *embed,
                           char **url)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_get_link_tags (GaleonEmbed *embed,
                    const char *link_type,
                    GList **tags)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_zoom_set (GaleonEmbed *embed, 
               int zoom, 
               gboolean reflow)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_zoom_get (GaleonEmbed *embed,
               int *zoom)
{
	return G_NOT_IMPLEMENTED;
}

static gresult 
impl_selection_can_cut (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult 
impl_selection_can_copy (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult 
impl_can_paste (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_selection_cut (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_selection_copy (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_paste (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_shistory_count  (GaleonEmbed *embed,
                      int *count)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_shistory_get_nth (GaleonEmbed *embed, 
                       int nth,
                       gboolean is_relative,
                       char **url,
                       char **title)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_shistory_get_pos (GaleonEmbed *embed,
                       int *pos)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_shistory_go_nth (GaleonEmbed *embed, 
                      int nth)
{
	return G_NOT_IMPLEMENTED;
}

static gboolean
impl_shistory_copy (GaleonEmbed *source,
                    GaleonEmbed *dest)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_shistory_clear (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_scroll (GaleonEmbed *embed, 
             EmbedScrollDirection direction)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_get_security_level (GaleonEmbed *embed, 
                         EmbedSecurityLevel level,
                         char **description)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_find (GaleonEmbed *embed,
           EmbedFindInfo *info)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_print (GaleonEmbed *embed, 
            EmbedPrintInfo *info)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_print_preview_close (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_print_preview_num_pages (GaleonEmbed *embed,
                              gint *retNum)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_print_preview_navigate (GaleonEmbed *embed,
                             EmbedPrintPreviewNavType navType,
                             gint pageNum)
{
	return G_NOT_IMPLEMENTED;
}

static gresult
impl_set_charset (GaleonEmbed *embed,
                  const char *charset)
{
	return G_NOT_IMPLEMENTED;
}

static gresult 
impl_select_all (GaleonEmbed *embed)
{
	return G_NOT_IMPLEMENTED;
}

static GnomeVFSURI *
gtkhtml_embed_resolve_relative (GtkhtmlEmbed *embed, const char *text_url)
{
	GnomeVFSURI *uri;

	g_return_val_if_fail (text_url != NULL, NULL);

	if (embed->priv->base_uri != NULL)
	{
		uri = gnome_vfs_uri_resolve_relative (embed->priv->base_uri,
				                      text_url);
	}
	else
	{
		uri = gnome_vfs_uri_new (text_url);
	}

	return uri;
}

static gboolean
request_url_cb (HtmlDocument *document, const char *text_url,
		              HtmlStream *stream, GtkhtmlEmbed *embed)
{
	Connection *conn;
	GnomeVFSURI *uri;

	uri = gtkhtml_embed_resolve_relative (embed, text_url);
	g_return_val_if_fail (uri != NULL, FALSE);

	conn = g_new0 (Connection, 1);
	conn->stream = stream;

	gtkhtml_embed_add_connection (embed, conn);

	gnome_vfs_async_open_uri (&conn->handle, uri, GNOME_VFS_OPEN_READ,
			          GNOME_VFS_PRIORITY_DEFAULT,
				  (GnomeVFSAsyncOpenCallback)vfs_open_callback,
				  conn);
	gnome_vfs_uri_unref (uri);
	html_stream_set_cancel_func (stream, 
			             (HtmlStreamCancelFunc)stream_cancel_cb, 
				     conn);

	return TRUE;
}

static void
link_clicked_cb (HtmlDocument *document, const gchar *text_url,
		               GtkhtmlEmbed *embed)
{
	if (gtkhtml_embed_set_base (embed, text_url))
	{
		/* same document, jump to anchor */
		const gchar *anchor = gnome_vfs_uri_get_fragment_identifier (embed->priv->base_uri);
		if (anchor != NULL)
		{
			html_view_jump_to_anchor (embed->priv->view, anchor);
		}
	}
	else
	{
		GnomeVFSURI *uri;

		gtkhtml_embed_kill_connections (embed);

		uri = gtkhtml_embed_resolve_relative (embed, text_url);

		/* FIXME error in URI? show dialog? */
		g_return_if_fail (uri != NULL); 

		gtkhtml_embed_load_uri (embed, uri);
		gnome_vfs_uri_unref (uri);
	}
}

static void
stream_cancel_cb (HtmlStream *stream, gpointer user_data, Connection *conn)
{
	gnome_vfs_async_cancel (conn->handle);
	gtkhtml_embed_remove_connection (conn->embed, conn);
	connection_free (conn);
}

/* Return true if the new URI is on the same page as previously */
static gboolean
gtkhtml_embed_set_base (GtkhtmlEmbed *embed, const gchar *text_url)
{
	gchar *old_url, *new_url;
	gboolean equal;
	GnomeVFSURI *uri;

	if (embed->priv->base_uri == NULL)
	{
		embed->priv->base_uri = gnome_vfs_uri_new (text_url);
		g_signal_emit_by_name (embed, "ge_location");
		return FALSE;
	}
	
	uri = gnome_vfs_uri_resolve_relative (embed->priv->base_uri, text_url);
	old_url = gnome_vfs_uri_to_string (embed->priv->base_uri,
			                   GNOME_VFS_URI_HIDE_FRAGMENT_IDENTIFIER);
	new_url = gnome_vfs_uri_to_string (uri,
			                   GNOME_VFS_URI_HIDE_FRAGMENT_IDENTIFIER);

	equal = (strcmp (old_url, new_url) == 0);

	g_free (old_url);
	g_free (new_url);

	gnome_vfs_uri_unref (embed->priv->base_uri);
	embed->priv->base_uri = uri;
	g_signal_emit_by_name (embed, "ge_location");

	return equal;
}

static void
gtkhtml_embed_add_connection (GtkhtmlEmbed *embed, Connection *conn)
{
	g_assert (conn->stream == NULL || conn->document == NULL);

	conn->embed = embed;
	embed->priv->connection_list =
		g_slist_prepend (embed->priv->connection_list, conn);
}

static void
gtkhtml_embed_remove_connection (GtkhtmlEmbed *embed, Connection *conn)
{
	embed->priv->connection_list =
		g_slist_remove (embed->priv->connection_list, conn);
}

static void
gtkhtml_embed_kill_connections (GtkhtmlEmbed *embed)
{
	GSList *tmp;

	for (tmp = embed->priv->connection_list; tmp != NULL; tmp = g_slist_next (tmp))
	{
		Connection *conn = tmp->data;
		gnome_vfs_async_cancel (conn->handle);
		connection_free (conn);
	}
	g_slist_free (embed->priv->connection_list);
	embed->priv->connection_list = NULL;
}

static void
connection_free (Connection *conn)
{
	if (conn->stream != NULL) html_stream_close (conn->stream);
	g_free (conn);
}

static void
gtkhtml_embed_load_uri (GtkhtmlEmbed *embed, GnomeVFSURI *uri)
{
	Connection *conn;
	
	conn = g_new0 (Connection, 1);
	conn->document = embed->priv->document;
	
	gtkhtml_embed_add_connection (embed, conn);

	gnome_vfs_async_open_uri (&conn->handle, uri, GNOME_VFS_OPEN_READ,
			          GNOME_VFS_PRIORITY_DEFAULT,
				  (GnomeVFSAsyncOpenCallback)vfs_open_callback,
				  conn);
}

static void
vfs_open_callback (GnomeVFSAsyncHandle *handle, GnomeVFSResult result,
		   Connection *conn)
{
	if (result == GNOME_VFS_OK)
	{
		gchar *buffer;

		if (conn->document != NULL)
		{
			html_document_clear (conn->document);
			gtk_adjustment_set_value 
			 (gtk_scrolled_window_get_vadjustment
			  (GTK_SCROLLED_WINDOW (conn->embed)), 0);

			/* FIXME hook up some gnome-vfs callback to get HTTP headers */
			html_document_open_stream (conn->document, "text/html");

			/* avoid need for special cases in vfs_read_callback */
			conn->stream = conn->document->current_stream;
		}

		buffer = g_malloc (BUFFER_SIZE);
		gnome_vfs_async_read (handle, buffer, BUFFER_SIZE,
				      (GnomeVFSAsyncReadCallback)vfs_read_callback,
				      conn);
	}
	else
	{
		if (conn->document != NULL)
		{
			GtkWidget *parent = gtk_widget_get_toplevel 
				            (GTK_WIDGET (conn->embed));
			GtkWidget *dialog = gtk_message_dialog_new 
				            (GTK_WINDOW (parent),
					     GTK_DIALOG_MODAL,
					     GTK_MESSAGE_ERROR,
					     GTK_BUTTONS_OK,
					     "%s",
					     gnome_vfs_result_to_string (result));
			gtk_dialog_run (GTK_DIALOG (dialog));
			gtk_widget_destroy (dialog);
		}

		gtkhtml_embed_remove_connection (conn->embed, conn);
		connection_free (conn);
	}
}

static void
vfs_read_callback (GnomeVFSAsyncHandle *handle, GnomeVFSResult result,
		   gpointer buffer, GnomeVFSFileSize bytes_requested,
		   GnomeVFSFileSize bytes_read, Connection *conn)
{
	if (result == GNOME_VFS_OK)
	{
		html_stream_write (conn->stream, buffer, bytes_read);
		gnome_vfs_async_read (handle, buffer, bytes_requested,
				      (GnomeVFSAsyncReadCallback)vfs_read_callback,
				      conn);
	}
	else
	{
		gnome_vfs_async_close (handle,
				       (GnomeVFSAsyncCloseCallback)vfs_close_callback,
				       conn);
		g_free (buffer);
	}
}

static void
vfs_close_callback (GnomeVFSAsyncHandle *handle, GnomeVFSResult result,
		    Connection *conn)
{
	if (conn->document != NULL)
	{
		conn->stream = NULL;
		html_document_close_stream (conn->document);
	}
	gtkhtml_embed_remove_connection (conn->embed, conn);
	connection_free (conn);
}

