/*
 *  Copyright © 2000-2004 Marco Pesenti Gritti
 *  Copyright © 2003, 2004 Christian Persch
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *  $Id$
 */

#include "mozilla-config.h"
#include "config.h"

#include <nsStringAPI.h>

#include <gtkmozembed.h>
#include <nsIDOMKeyEvent.h>
#include <nsIDOMMouseEvent.h>
#include <nsIRequest.h>
#include <nsIURI.h>
#include <nsIWebNavigation.h>
#include <nsIWebProgressListener.h>
#include <nsMemory.h>

#include <glib/gi18n.h>
#include <libgnomevfs/gnome-vfs.h>
#include <libgnomevfs/gnome-vfs-uri.h>

#include "EphyBrowser.h"
#include "EphyUtils.h"
#include "EventContext.h"

#include "ephy-base-embed.h"
#include "ephy-command-manager.h"
#include "ephy-debug.h"
#include "ephy-embed-shell.h"
#include "ephy-embed-single.h"
#include "ephy-embed-utils.h"
#include "ephy-favicon-cache.h"
#include "ephy-history.h"
#include "ephy-string.h"
#include "mozilla-embed-event.h"

#include "mozilla-embed.h"

static void	mozilla_embed_class_init	(MozillaEmbedClass *klass);
static void	mozilla_embed_init		(MozillaEmbed *gs);
static void	mozilla_embed_destroy		(GtkObject *object);
static void	mozilla_embed_finalize		(GObject *object);
static void	mozilla_embed_dispose		(GObject *object);
static void	ephy_embed_iface_init		(EphyEmbedIface *iface);

static void mozilla_embed_location_changed_cb	(GtkMozEmbed *embed,
						 MozillaEmbed *membed);
static void mozilla_embed_net_state_all_cb	(GtkMozEmbed *embed,
						 const char *aURI,
						 gint state,
						 guint status,
						 MozillaEmbed *membed);
static gboolean mozilla_embed_dom_mouse_click_cb(GtkMozEmbed *embed,
						 gpointer dom_event,
						 MozillaEmbed *membed);
static gboolean mozilla_embed_dom_mouse_down_cb	(GtkMozEmbed *embed,
						 gpointer dom_event, 
						 MozillaEmbed *membed);
static gboolean mozilla_embed_dom_key_press_cb	(GtkMozEmbed *embed,
						 gpointer dom_event, 
						 MozillaEmbed *membed);
static void mozilla_embed_new_window_cb		(GtkMozEmbed *embed, 
						 GtkMozEmbed **newEmbed,
						 guint chrome_mask,
						 MozillaEmbed *membed);
static void mozilla_embed_security_change_cb	(GtkMozEmbed *embed, 
						 gpointer request,
						 PRUint32 state,
						 MozillaEmbed *membed);
static void mozilla_embed_document_type_cb	(EphyEmbed *embed,
						 EphyEmbedDocumentType type,
						 MozillaEmbed *membed);
static void mozilla_embed_zoom_change_cb	(EphyEmbed *embed,
						 float zoom,
						 MozillaEmbed *membed);
static void mozilla_embed_title_change_cb	(GtkMozEmbed *embed,
						 MozillaEmbed *membed);
static void mozilla_embed_link_message_cb	(GtkMozEmbed *embed,
						 MozillaEmbed *membed);
static void mozilla_embed_set_title		(MozillaEmbed *embed,
						 char *title);
static void mozilla_embed_set_loading_title	(MozillaEmbed *embed,
						 const char *title,
						 gboolean is_address);
static void mozilla_embed_icon_cache_changed_cb (EphyFaviconCache *cache,
						 const char *address,
						 MozillaEmbed *embed);
static void mozilla_embed_set_icon_address	(MozillaEmbed *embed,
						 const char *address);
static void mozilla_embed_favicon_cb		(MozillaEmbed *membed,
						 const char *address,
						 GtkMozEmbed *embed);
static gboolean mozilla_embed_open_uri_cb	(EphyEmbed *embed,
						 const char *uri,
						 MozillaEmbed *membed);
static void  mozilla_embed_file_monitor_cancel	(MozillaEmbed *embed);
#if 0
static guint popup_blocker_n_hidden		(MozillaEmbed *embed);
static gboolean	mozilla_embed_get_popups_allowed (MozillaEmbed *embed);
static void mozilla_embed_set_popups_allowed	 (MozillaEmbed *embed,
						  gboolean allowed);
#endif
static void mozilla_embed_file_monitor_cancel	(MozillaEmbed *embed);
static void impl_set_typed_address		(EphyEmbed *embed,
						 const char *address,
						 EphyEmbedAddressExpire expire);

static EphyEmbedSecurityLevel mozilla_embed_security_level (PRUint32 state);

#define MOZILLA_EMBED_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), MOZILLA_TYPE_EMBED, MozillaEmbedPrivate))

typedef enum
{
	MOZILLA_EMBED_LOAD_STARTED,
	MOZILLA_EMBED_LOAD_REDIRECTING,
	MOZILLA_EMBED_LOAD_LOADING,
	MOZILLA_EMBED_LOAD_STOPPED
} MozillaEmbedLoadState;

#define MAX_HIDDEN_POPUPS	5
#define MAX_TITLE_LENGTH	512 /* characters */
#define RELOAD_DELAY		250 /* ms */
#define RELOAD_DELAY_MAX_TICKS	40  /* RELOAD_DELAY * RELOAD_DELAY_MAX_TICKS = 10 s */

struct MozillaEmbedPrivate
{
	EphyBrowser *browser;
	GtkMozEmbed *moz_embed;
	MozillaEmbedLoadState load_state;

	EphyEmbedAddressExpire address_expire;
	/* guint address_expire : 2; ? */
	EphyEmbedSecurityLevel security_level;
	/* guint security_level : 3; ? */
	EphyEmbedDocumentType document_type;
	EphyEmbedNavigationFlags nav_flags;
	float zoom;

	/* Flags */
	guint is_blank : 1;
	guint is_loading : 1;
	guint is_setting_zoom : 1;

	gint8 load_percent;
	char *address;
	char *typed_address;
	char *title;
	char *loading_title;
	int cur_requests;
	int total_requests;
	char *status_message;
	char *link_message;
	char *icon_address;
	GdkPixbuf *icon;

	/* File watch */
	GnomeVFSMonitorHandle *monitor;
	guint reload_scheduled_id;
	guint reload_delay_ticks;

	GSList *hidden_popups;
	GSList *shown_popups;
};

#define WINDOWWATCHER_CONTRACTID "@mozilla.org/embedcomp/window-watcher;1"

enum
{
	PROP_0,
	PROP_ADDRESS,
	PROP_DOCUMENT_TYPE,
	PROP_HIDDEN_POPUP_COUNT,
	PROP_ICON,
	PROP_ICON_ADDRESS,
	PROP_LINK_MESSAGE,
	PROP_LOAD_PROGRESS,
	PROP_LOAD_STATUS,
	PROP_NAVIGATION,
	PROP_POPUPS_ALLOWED,
	PROP_SECURITY,
	PROP_STATUS_MESSAGE,
	PROP_TITLE,
	PROP_TYPED_ADDRESS,
	PROP_ZOOM
};

typedef struct
{
	char *url;
	char *name;
	char *features;
} PopupInfo;

static void
impl_manager_do_command (EphyCommandManager *manager,
			 const char *command) 
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(manager)->priv;

	mpriv->browser->DoCommand (command);
}

static gboolean
impl_manager_can_do_command (EphyCommandManager *manager,
			     const char *command) 
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(manager)->priv;
	nsresult rv;
	PRBool enabled;

	rv = mpriv->browser->GetCommandState (command, &enabled);

	return NS_SUCCEEDED (rv) ? enabled : FALSE;
}

static void
ephy_command_manager_iface_init (EphyCommandManagerIface *iface)
{
	iface->do_command = impl_manager_do_command;
	iface->can_do_command = impl_manager_can_do_command;
}

G_DEFINE_TYPE_WITH_CODE (MozillaEmbed, mozilla_embed, EPHY_TYPE_BASE_EMBED,
			 G_IMPLEMENT_INTERFACE (EPHY_TYPE_EMBED,
						ephy_embed_iface_init)
			 G_IMPLEMENT_INTERFACE (EPHY_TYPE_COMMAND_MANAGER,
						ephy_command_manager_iface_init))
	
static void
mozilla_embed_grab_focus (GtkWidget *widget)
{
	GtkWidget *child;

	child = gtk_bin_get_child (GTK_BIN (widget));

	if (child != NULL)
	{
		gtk_widget_grab_focus (child);
	}
	else
	{
		g_warning ("Need to realize the embed before grabbing focus!\n");
	}
}

static void
impl_close (EphyEmbed *embed) 
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED (embed)->priv;

	mpriv->browser->Close ();
}

static void
mozilla_embed_realize (GtkWidget *widget)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED (widget)->priv;

	GTK_WIDGET_CLASS (mozilla_embed_parent_class)->realize (widget);

	gtk_widget_realize (GTK_WIDGET (mpriv->moz_embed));
	/* Initialise our helper class */
	nsresult rv;
	rv = mpriv->browser->Init (EPHY_EMBED (widget));
	if (NS_FAILED (rv))
	{
		g_warning ("EphyBrowser initialization failed for %p\n", widget);
		return;
	}
}

static GObject *
mozilla_embed_constructor (GType type, guint n_construct_properties,
			   GObjectConstructParam *construct_params)
{
	g_object_ref (embed_shell);

	/* we depend on single because of mozilla initialization */
	ephy_embed_shell_get_embed_single (embed_shell);

	return G_OBJECT_CLASS (mozilla_embed_parent_class)->constructor (type, n_construct_properties,
									 construct_params);
}

static void
mozilla_embed_get_property (GObject *object,
			    guint prop_id,
			    GValue *value,
			    GParamSpec *pspec)
{
	MozillaEmbed *embed = MOZILLA_EMBED (object);
	MozillaEmbedPrivate *priv = embed->priv;
	
	switch (prop_id)
	{
	case PROP_ADDRESS:
		g_value_set_string (value, priv->address);
		break;
	case PROP_DOCUMENT_TYPE:
		g_value_set_enum (value, priv->document_type);
		break;
	case PROP_HIDDEN_POPUP_COUNT:
		g_value_set_int (value, 0);
	  //		    g_value_set_int (value, popup_blocker_n_hidden (embed));
		break;
	case PROP_ICON:
	       g_value_set_object (value, priv->icon);
	       break;
	case PROP_ICON_ADDRESS:
	       g_value_set_string (value, priv->icon_address);
	       break;
	case PROP_LINK_MESSAGE:
		g_value_set_string (value, priv->link_message);
		break;
	case PROP_LOAD_PROGRESS:
		g_value_set_int (value, priv->load_percent);
		break;
	case PROP_LOAD_STATUS:
		g_value_set_boolean (value, priv->is_loading);
		break;
	case PROP_NAVIGATION:
		g_value_set_flags (value, priv->nav_flags);
		break;
	case PROP_POPUPS_ALLOWED:
		g_value_set_boolean (value, FALSE);
	  //	g_value_set_boolean (value, mozilla_embed_get_popups_allowed (embed));
		break;
	case PROP_SECURITY:
		g_value_set_enum (value, priv->security_level);
		break;
	case PROP_STATUS_MESSAGE:
		g_value_set_string (value, priv->status_message);
		break;
	case PROP_TITLE:
		g_value_set_string (value, priv->title);
		break;
	case PROP_TYPED_ADDRESS:
		g_value_set_string (value, priv->typed_address);
		break;
	case PROP_ZOOM:
		g_value_set_float (value, priv->zoom);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}

}

static void
mozilla_embed_set_property (GObject *object,
			    guint prop_id,
			    const GValue *value,
			    GParamSpec *pspec)
{
	switch (prop_id)
	{
	case PROP_ICON_ADDRESS:
		mozilla_embed_set_icon_address (MOZILLA_EMBED (object), g_value_get_string (value));
		break;
	case PROP_TYPED_ADDRESS:
		impl_set_typed_address (EPHY_EMBED (object), g_value_get_string (value),
					EPHY_EMBED_ADDRESS_EXPIRE_NOW);
		break;
	case PROP_POPUPS_ALLOWED:
	  //mozilla_embed_set_popups_allowed (MOZILLA_EMBED (object), g_value_get_boolean (value));
		break;
	case PROP_ADDRESS:
	case PROP_DOCUMENT_TYPE:
	case PROP_HIDDEN_POPUP_COUNT:
	case PROP_ICON:
	case PROP_LOAD_PROGRESS:
	case PROP_LOAD_STATUS:
	case PROP_LINK_MESSAGE:
	case PROP_NAVIGATION:
	case PROP_SECURITY:
	case PROP_STATUS_MESSAGE:
	case PROP_ZOOM:
		/* read only */
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
mozilla_embed_class_init (MozillaEmbedClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (klass); 
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); 

	object_class->constructor = mozilla_embed_constructor;
	object_class->finalize = mozilla_embed_finalize;
	object_class->dispose = mozilla_embed_dispose;
	object_class->get_property = mozilla_embed_get_property;
	object_class->set_property = mozilla_embed_set_property;

	gtk_object_class->destroy = mozilla_embed_destroy;

	widget_class->grab_focus = mozilla_embed_grab_focus;
	widget_class->realize = mozilla_embed_realize;

	g_object_class_override_property (object_class, PROP_DOCUMENT_TYPE, "document-type");
	g_object_class_override_property (object_class, PROP_SECURITY, "security-level");
	g_object_class_override_property (object_class, PROP_ZOOM, "zoom");
	g_object_class_override_property (object_class, PROP_LOAD_PROGRESS, "load-progress");
	g_object_class_override_property (object_class, PROP_LOAD_STATUS, "load-status");
	g_object_class_override_property (object_class, PROP_NAVIGATION, "navigation");
	g_object_class_override_property (object_class, PROP_ADDRESS, "address");
	g_object_class_override_property (object_class, PROP_TYPED_ADDRESS, "typed-address");
	g_object_class_override_property (object_class, PROP_TITLE, "title");
	g_object_class_override_property (object_class, PROP_STATUS_MESSAGE, "status-message");
	g_object_class_override_property (object_class, PROP_LINK_MESSAGE, "link-message");
	g_object_class_override_property (object_class, PROP_ICON, "icon");
	g_object_class_override_property (object_class, PROP_ICON_ADDRESS, "icon-address");
	g_object_class_override_property (object_class, PROP_POPUPS_ALLOWED, "popups-allowed");
	g_object_class_override_property (object_class, PROP_HIDDEN_POPUP_COUNT, "hidden-popup-count");

	g_type_class_add_private (object_class, sizeof(MozillaEmbedPrivate));
}

static void
mozilla_embed_init (MozillaEmbed *membed)
{
	EphyFaviconCache *cache;
	MozillaEmbedPrivate *priv;
	GtkWidget *embed;

	membed->priv = MOZILLA_EMBED_GET_PRIVATE (membed);
	priv = membed->priv;

	embed = gtk_moz_embed_new ();
	priv->moz_embed = GTK_MOZ_EMBED (embed);
	gtk_container_add (GTK_CONTAINER (membed), embed);
	gtk_widget_show (embed);

	priv->browser = new EphyBrowser ();

	g_signal_connect_object (embed, "location",
				 G_CALLBACK (mozilla_embed_location_changed_cb),
				 membed, (GConnectFlags) 0);
	g_signal_connect_object (embed, "net_state_all",
				 G_CALLBACK (mozilla_embed_net_state_all_cb),
				 membed, (GConnectFlags) 0);
	g_signal_connect_object (embed, "dom_mouse_click",
				 G_CALLBACK (mozilla_embed_dom_mouse_click_cb),
				 membed, (GConnectFlags) 0);
	g_signal_connect_object (embed, "dom_mouse_down",
				 G_CALLBACK (mozilla_embed_dom_mouse_down_cb),
				 membed, (GConnectFlags) 0);
	g_signal_connect_object (embed, "dom-key-press",
				 G_CALLBACK (mozilla_embed_dom_key_press_cb),
				 membed, (GConnectFlags) 0);
	g_signal_connect_object (embed, "new_window",
				 G_CALLBACK (mozilla_embed_new_window_cb),
				 membed, (GConnectFlags) 0);
	g_signal_connect_object (embed, "security_change",
				 G_CALLBACK (mozilla_embed_security_change_cb),
				 membed, (GConnectFlags) 0);
	g_signal_connect_object (membed, "ge_document_type",
				 G_CALLBACK (mozilla_embed_document_type_cb),
				 membed, (GConnectFlags) 0);
	g_signal_connect_object (membed, "ge_zoom_change",
				 G_CALLBACK (mozilla_embed_zoom_change_cb),
				 embed, (GConnectFlags) 0);
	g_signal_connect_object (embed, "title",
				 G_CALLBACK (mozilla_embed_title_change_cb),
				 membed, (GConnectFlags) 0);
	g_signal_connect_object (embed, "link_message",
				 G_CALLBACK (mozilla_embed_link_message_cb),
				 membed, (GConnectFlags)0);
	g_signal_connect_object (membed, "ge_favicon",
				 G_CALLBACK (mozilla_embed_favicon_cb),
				 embed, (GConnectFlags)0);
	g_signal_connect_object (embed, "open_uri",
				 G_CALLBACK (mozilla_embed_open_uri_cb),
				 membed,(GConnectFlags) 0);

	cache = EPHY_FAVICON_CACHE
		(ephy_embed_shell_get_favicon_cache (embed_shell));
	g_signal_connect_object (G_OBJECT (cache), "changed",
				 G_CALLBACK (mozilla_embed_icon_cache_changed_cb),
				 membed, (GConnectFlags)0);

	priv->document_type = EPHY_EMBED_DOCUMENT_HTML;
	priv->security_level = EPHY_EMBED_STATE_IS_UNKNOWN;
	priv->zoom = 1.0;
	priv->is_setting_zoom = FALSE;
	priv->load_percent = 0;
	priv->is_loading = FALSE;
	priv->typed_address = NULL;
	priv->address = NULL;
	priv->address_expire = EPHY_EMBED_ADDRESS_EXPIRE_NOW;
	priv->title = NULL;
	priv->loading_title = NULL;
	priv->is_blank = TRUE;
	priv->total_requests = 0;
	priv->cur_requests = 0;
	priv->icon_address = NULL;
	priv->icon = NULL;
	priv->status_message = NULL;
	priv->link_message = NULL;
}

gpointer
_mozilla_embed_get_ephy_browser (MozillaEmbed *embed)
{
	g_return_val_if_fail (embed->priv->browser != NULL, NULL);
	
	return embed->priv->browser;
}

static void
mozilla_embed_destroy (GtkObject *object)
{
	MozillaEmbed *embed = MOZILLA_EMBED (object);

	if (embed->priv->browser)
	{
		embed->priv->browser->Destroy();
	}
	
	GTK_OBJECT_CLASS (mozilla_embed_parent_class)->destroy (object);
}

static void
mozilla_embed_dispose (GObject *object)
{
	mozilla_embed_file_monitor_cancel (MOZILLA_EMBED (object));

	G_OBJECT_CLASS (mozilla_embed_parent_class)->dispose (object);
}

static void
mozilla_embed_finalize (GObject *object)
{
	MozillaEmbed *embed = MOZILLA_EMBED (object);

	if (embed->priv->browser)
	{
		delete embed->priv->browser;
		embed->priv->browser = nsnull;
	}

	if (embed->priv->icon != NULL)
	{
		g_object_unref (embed->priv->icon);
		embed->priv->icon = NULL;
	}

#if 0
	popups_manager_reset (embed);
#endif

	g_free (embed->priv->icon_address);
	g_free (embed->priv->address);
	g_free (embed->priv->typed_address);
	g_free (embed->priv->title);
	g_free (embed->priv->loading_title);
	g_free (embed->priv->status_message);
	g_free (embed->priv->link_message);

	G_OBJECT_CLASS (mozilla_embed_parent_class)->finalize (object);

	g_object_unref (embed_shell);
}

#if 0
static void
ephy_tab_content_change_cb (EphyEmbed *embed, const char *address, EphyTab *tab)
{
	popups_manager_reset (tab);
	g_object_notify (G_OBJECT (tab), "popups-allowed");
}

static void
ephy_tab_new_window_cb (EphyEmbed *embed,
			EphyEmbed *new_embed,
			EphyTab *tab)
{
	EphyWindow *window;

	g_return_if_fail (new_embed != NULL);

	window = EPHY_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (new_embed)));
	g_return_if_fail (window != NULL);

	popups_manager_add_window (tab, window);
}

static void
ephy_tab_popup_blocked_cb (EphyEmbed *embed,
			   const char *url,
			   const char *name,
			   const char *features,
			   EphyTab *tab)
{
	popups_manager_add (tab, url, name, features);
}

static void
popups_manager_free_info (PopupInfo *popup)
{
	g_free (popup->url);
	g_free (popup->name);
	g_free (popup->features);
	g_free (popup);
}

static void
popups_manager_add (MozillaEmbed *embed,
		    const char *url,
		    const char *name,
		    const char *features)
{
	MozillaEmbedPrivate *priv = embed->priv;
	PopupInfo *popup;

	LOG ("popups_manager_add: embed %p, url %s, features %s",
	     embed, url, features);

	popup = g_new0 (PopupInfo, 1);

	popup->url = (url == NULL) ? NULL : g_strdup (url);
	popup->name = g_strdup (name);
	popup->features = g_strdup (features);

	priv->hidden_popups = g_slist_prepend (priv->hidden_popups, popup);

	if (popup_blocker_n_hidden (embed) > MAX_HIDDEN_POPUPS) /* bug #160863 */
	{
		/* Remove the oldest popup */
		GSList *l = embed->priv->hidden_popups;

		while (l->next->next != NULL)
		{
			l = l->next;
		}

		popup = (PopupInfo *) l->next->data;
		popups_manager_free_info (popup);

		l->next = NULL;
	}
	else
	{
		g_object_notify (G_OBJECT (embed), "hidden-popup-count");
	}
}

static gboolean
popups_manager_remove_window (MozillaEmbed *embed,
			      EphyWindow *window)
{
	embed->priv->shown_popups = g_slist_remove (embed->priv->shown_popups,
						    window);

	return FALSE;
}

static void
disconnect_popup (EphyWindow *window,
		  MozillaEmbed *embed)
{
	g_signal_handlers_disconnect_by_func
		(window, G_CALLBACK (popups_manager_remove_window), embed);
}

static void
popups_manager_add_window (MozillaEmbed *embed,
			   EphyWindow *window)
{
	LOG ("popups_manager_add_window: embed %p, window %p", embed, window);

	embed->priv->shown_popups = g_slist_prepend
		(embed->priv->shown_popups, window);

	g_signal_connect_swapped (window, "destroy",
				  G_CALLBACK (popups_manager_remove_window),
				  embed);
}

static gboolean
mozilla_embed_get_popups_allowed (MozillaEmbed *embed)
{
	EphyPermissionManager *permission_manager;
	EphyPermission response;
	EphyEmbed *embed;
	char *location;
	gboolean allow;

	permission_manager = EPHY_PERMISSION_MANAGER
		(ephy_embed_shell_get_embed_single (embed_shell));
	g_return_val_if_fail (EPHY_IS_PERMISSION_MANAGER (permission_manager),
			      FALSE);

	location = ephy_embed_get_location (embed, TRUE);
	if (location == NULL) return FALSE; /* FALSE, TRUE… same thing */

	response = ephy_permission_manager_test_permission
		(permission_manager, location, EPT_POPUP);

	switch (response)
	{
		case EPHY_PERMISSION_ALLOWED:
			allow = TRUE;
			break;
		case EPHY_PERMISSION_DENIED:
			allow = FALSE;
			break;
		case EPHY_PERMISSION_DEFAULT:
		default:
			allow = eel_gconf_get_boolean
				(CONF_SECURITY_ALLOW_POPUPS);
			break;
	}

	g_free (location);

	LOG ("mozilla_embed_get_popups_allowed: embed %p, allowed: %d", embed, allow);

	return allow;
}

static void
popups_manager_show (PopupInfo *popup,
		     MozillaEmbed *embed)
{
	EphyEmbed *embed;
	EphyEmbedSingle *single;

	/* Only show popup with non NULL url */
	if (popup->url != NULL)
	{
		embed = ephy_embed_get_embed (embed);

		single = EPHY_EMBED_SINGLE
			(ephy_embed_shell_get_embed_single (embed_shell));

		ephy_embed_single_open_window (single, embed, popup->url,
				popup->name, popup->features);
	}
	popups_manager_free_info (popup);
}

static void
popups_manager_show_all (MozillaEmbed *embed)
{
	LOG ("popup_blocker_show_all: embed %p", embed);

	g_slist_foreach (embed->priv->hidden_popups,
			 (GFunc) popups_manager_show, embed);
	g_slist_free (embed->priv->hidden_popups);
	embed->priv->hidden_popups = NULL;

	g_object_notify (G_OBJECT (embed), "hidden-popup-count");
}

static char *
popups_manager_new_window_info (EphyWindow *window)
{
	MozillaEmbed *embed;
	EphyEmbedChrome chrome;
	gboolean is_popup;
	char *features;

	g_object_get (window, "chrome", &chrome, "is-popup", &is_popup, NULL);
	g_return_val_if_fail (is_popup, g_strdup (""));

	embed = ephy_window_get_active_embed (window);
	g_return_val_if_fail (embed != NULL, g_strdup (""));

	features = g_strdup_printf
		("width=%d,height=%d,menubar=%d,status=%d,toolbar=%d",
		 embed->priv->width, embed->priv->height,
		 (chrome & EPHY_EMBED_CHROME_MENUBAR) > 0,
		 (chrome & EPHY_EMBED_CHROME_STATUSBAR) > 0,
		 (chrome & EPHY_EMBED_CHROME_TOOLBAR) > 0);

	return features;
}

static void
popups_manager_hide (EphyWindow *window,
		     MozillaEmbed *parent_embed)
{
	EphyEmbed *embed;
	char *location;
	char *features;

	embed = ephy_window_get_active_embed (window);
	g_return_if_fail (EPHY_IS_EMBED (embed));

	location = ephy_embed_get_location (embed, TRUE);
	if (location == NULL) return;

	features = popups_manager_new_window_info (window);

	popups_manager_add (parent_embed, location, "" /* FIXME? maybe _blank? */, features);

	gtk_widget_destroy (GTK_WIDGET (window));

	g_free (location);
	g_free (features);
}

static void
popups_manager_hide_all (MozillaEmbed *embed)
{
	LOG ("popup_blocker_hide_all: embed %p", embed);

	g_slist_foreach (embed->priv->shown_popups,
			 (GFunc) popups_manager_hide, embed);
	g_slist_free (embed->priv->shown_popups);
	embed->priv->shown_popups = NULL;
}

static void
mozilla_embed_set_popups_allowed (MozillaEmbed *embed,
				  gboolean allowed)
{
	char *location;
	EphyEmbed *embed;
	EphyPermissionManager *manager;
	EphyPermission permission;

	embed = ephy_embed_get_embed (embed);

	location = ephy_embed_get_location (embed, TRUE);
	g_return_if_fail (location != NULL);

	manager = EPHY_PERMISSION_MANAGER
		(ephy_embed_shell_get_embed_single (embed_shell));
	g_return_if_fail (EPHY_IS_PERMISSION_MANAGER (manager));

	permission = allowed ? EPHY_PERMISSION_ALLOWED
			     : EPHY_PERMISSION_DENIED;

	ephy_permission_manager_add_permission (manager, location, EPT_POPUP, permission);

	if (allowed)
	{
		popups_manager_show_all (embed);
	}
	else
	{
		popups_manager_hide_all (embed);
	}

	g_free (location);
}

static guint
popup_blocker_n_hidden (MozillaEmbed *embed)
{
	return g_slist_length (embed->priv->hidden_popups);
}

static void
popups_manager_reset (MozillaEmbed *embed)
{
	g_slist_foreach (embed->priv->hidden_popups,
			 (GFunc) popups_manager_free_info, NULL);
	g_slist_free (embed->priv->hidden_popups);
	embed->priv->hidden_popups = NULL;

	g_slist_foreach (embed->priv->shown_popups,
			 (GFunc) disconnect_popup, embed);
	g_slist_free (embed->priv->shown_popups);
	embed->priv->shown_popups = NULL;

	g_object_notify (G_OBJECT (embed), "hidden-popup-count");
}
#endif

static void
impl_load_url (EphyEmbed *embed, 
	       const char *url)
{
	GtkMozEmbed *moz_embed = MOZILLA_EMBED (embed)->priv->moz_embed;
	gtk_moz_embed_load_url (moz_embed, url);
}

static char * impl_get_location (EphyEmbed *embed, gboolean toplevel);

static void
impl_load (EphyEmbed *embed, 
	   const char *url,
	   EphyEmbedLoadFlags flags,
	   EphyEmbed *preview_embed)
{
	EphyBrowser *browser;

	browser = MOZILLA_EMBED(embed)->priv->browser;
	g_return_if_fail (browser != NULL);

	nsCOMPtr<nsIURI> uri;
	if (preview_embed != NULL)
	{
		EphyBrowser *pbrowser;

		pbrowser = MOZILLA_EMBED(preview_embed)->priv->browser;
		if (pbrowser != NULL)
		{
			pbrowser->GetDocumentURI (getter_AddRefs (uri));
		}
	}

#ifdef HAVE_GECKO_1_8_1
	if (flags & EPHY_EMBED_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP)
	{
		browser->LoadURI (url, nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP, uri);	
	}
	else
#endif /* HAVE_GECKO_1_8_1 */
	{
		browser->LoadURI (url, nsIWebNavigation::LOAD_FLAGS_NONE, uri);	
	}
}

static void
impl_stop_load (EphyEmbed *embed)
{
	GtkMozEmbed *moz_embed = MOZILLA_EMBED (embed)->priv->moz_embed;
	gtk_moz_embed_stop_load (moz_embed);	
}

static gboolean
impl_can_go_back (EphyEmbed *embed)
{
	GtkMozEmbed *moz_embed = MOZILLA_EMBED (embed)->priv->moz_embed;
	return gtk_moz_embed_can_go_back (moz_embed);
}

static gboolean
impl_can_go_forward (EphyEmbed *embed)
{
	GtkMozEmbed *moz_embed = MOZILLA_EMBED (embed)->priv->moz_embed;
	return gtk_moz_embed_can_go_forward (moz_embed);
}

static gboolean
mozilla_embed_get_uri_parent (MozillaEmbed *membed,
			      const char *aUri,
			      nsCString &aParent)
{
	nsresult rv;
	nsCString encoding;
	rv = membed->priv->browser->GetEncoding (encoding);
	if (NS_FAILED (rv)) return FALSE;

	nsCOMPtr<nsIURI> uri;
	rv = EphyUtils::NewURI (getter_AddRefs(uri), nsCString(aUri), encoding.get());
	if (NS_FAILED(rv) || !uri) return FALSE;

	/* Don't support going 'up' with chrome url's, mozilla handily
	 * fixes them up for us, so it doesn't work properly, see
	 * rdf/chrome/src/nsChromeProtocolHandler.cpp::NewURI()
	 * (the Canonify() call)
	 */
	nsCString scheme;
	rv = uri->GetScheme (scheme);
	if (NS_FAILED(rv) || !scheme.Length()) return FALSE;
	if (strcmp (scheme.get(), "chrome") == 0) return FALSE;

	nsCString path;
	rv = uri->GetPath(path);
	if (NS_FAILED(rv) || !path.Length()) return FALSE;
	if (strcmp (path.get (), "/") == 0) return FALSE;

	const char *slash = strrchr (path.BeginReading(), '/');
	if (!slash) return FALSE;

	if (slash[1] == '\0')
	{
		/* ends with a slash - a directory, go to parent */
		rv = uri->Resolve (nsCString(".."), aParent);
	}
	else
	{
		/* it's a file, go to the directory */
		rv = uri->Resolve (nsCString("."), aParent);
	}

	return NS_SUCCEEDED (rv);
}

static gboolean
impl_can_go_up (EphyEmbed *embed)
{
	MozillaEmbed *membed = MOZILLA_EMBED (embed);
	char *address;
	gboolean result;

	address = ephy_embed_get_location (embed, TRUE);
	if (address == NULL) return FALSE;

	nsCString parent;
	result = mozilla_embed_get_uri_parent (membed, address, parent);
	g_free (address);

	return result;
}

static GSList *
impl_get_go_up_list (EphyEmbed *embed)
{
	MozillaEmbed *membed = MOZILLA_EMBED (embed);
	GSList *l = NULL;
	char *address, *s;

	address = ephy_embed_get_location (embed, TRUE);
	if (address == NULL) return NULL;

	s = address;
	nsCString parent;
	while (mozilla_embed_get_uri_parent (membed, s, parent))
	{
		s = g_strdup (parent.get());
		l = g_slist_prepend (l, s);
	}

	g_free (address);

	return g_slist_reverse (l);
}

static void
impl_go_back (EphyEmbed *embed)
{
	GtkMozEmbed *moz_embed = MOZILLA_EMBED (embed)->priv->moz_embed;
	gtk_moz_embed_go_back (moz_embed);
}
		
static void
impl_go_forward (EphyEmbed *embed)
{
	GtkMozEmbed *moz_embed = MOZILLA_EMBED (embed)->priv->moz_embed;
	gtk_moz_embed_go_forward (moz_embed);
}

static void
impl_go_up (EphyEmbed *embed)
{
	MozillaEmbed *membed = MOZILLA_EMBED (embed);
	char *uri;

	uri = ephy_embed_get_location (embed, TRUE);
	if (uri == NULL) return;

	gboolean rv;
	nsCString parent_uri;
	rv = mozilla_embed_get_uri_parent (membed, uri, parent_uri);
	g_free (uri);

	g_return_if_fail (rv != FALSE);

	ephy_embed_load_url (embed, parent_uri.get ());
}

static const char *
impl_get_title (EphyEmbed *embed)
{
	return MOZILLA_EMBED (embed)->priv->title;
}

static char *
impl_get_js_status (EphyEmbed *embed)
{
	GtkMozEmbed *moz_embed = MOZILLA_EMBED (embed)->priv->moz_embed;
	return gtk_moz_embed_get_js_status (moz_embed);
}

static char *
impl_get_location (EphyEmbed *embed, 
		   gboolean toplevel)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;
	nsresult rv;

	nsCOMPtr<nsIURI> uri;
	if (toplevel)
	{
		rv = mpriv->browser->GetDocumentURI (getter_AddRefs (uri));
	}
	else
	{
		rv = mpriv->browser->GetTargetDocumentURI (getter_AddRefs (uri));
	}

	if (NS_FAILED (rv)) return NULL;

	nsCOMPtr<nsIURI> furi;
	rv = uri->Clone (getter_AddRefs (furi));
	/* Some nsIURI impls return NS_OK even though they didn't put anything in the outparam!! */
	if (NS_FAILED (rv) || !furi) furi.swap(uri);

	/* Hide password part */
	nsCString user;
	furi->GetUsername (user);
	furi->SetUserPass (user);

	nsCString url;
	furi->GetSpec (url);

	return url.Length() ? g_strdup (url.get()) : NULL;
}

static void
impl_reload (EphyEmbed *embed, 
	     gboolean force)
{
	GtkMozEmbed *moz_embed = MOZILLA_EMBED (embed)->priv->moz_embed;
	guint32 mflags = GTK_MOZ_EMBED_FLAG_RELOADNORMAL;

	if (force)
	{
		mflags = GTK_MOZ_EMBED_FLAG_RELOADBYPASSPROXYANDCACHE;
	}

	gtk_moz_embed_reload (moz_embed, mflags);
}

static void
impl_set_zoom (EphyEmbed *embed, 
	       float zoom) 
{
	EphyBrowser *browser;
	nsresult rv;

	g_return_if_fail (zoom > 0.0);

	browser = MOZILLA_EMBED(embed)->priv->browser;
	g_return_if_fail (browser != NULL);

	rv = browser->SetZoom (zoom);

	if (NS_SUCCEEDED (rv))
	{
		g_signal_emit_by_name (embed, "ge_zoom_change", zoom);
	}
}

static float
impl_get_zoom (EphyEmbed *embed)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;
	float f;

	nsresult rv;	
	rv = mpriv->browser->GetZoom (&f);
	
	if (NS_SUCCEEDED (rv))
	{
		return f;
	}

	return 1.0;
}

static void
impl_scroll_lines (EphyEmbed *embed,
		   int num_lines)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;

	mpriv->browser->ScrollLines (num_lines);
}

static void
impl_scroll_pages (EphyEmbed *embed,
		   int num_pages)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;

	mpriv->browser->ScrollPages (num_pages);
}

static void
impl_scroll_pixels (EphyEmbed *embed,
		    int dx,
		    int dy)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;

	mpriv->browser->ScrollPixels (dx, dy);
}

static int
impl_shistory_n_items (EphyEmbed *embed)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;
	nsresult rv;
	int count, index;

	rv = mpriv->browser->GetSHInfo (&count, &index);

	return NS_SUCCEEDED(rv) ? count : 0;
}

static void
impl_shistory_get_nth (EphyEmbed *embed, 
		       int nth,
		       gboolean is_relative,
		       char **aUrl,
		       char **aTitle)
{
	nsresult rv;
	nsCString url;
	PRUnichar *title;
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;

	if (is_relative)
	{
		nth += ephy_embed_shistory_get_pos (embed);
	}
	
	rv = mpriv->browser->GetSHUrlAtIndex(nth, url);

	*aUrl = (NS_SUCCEEDED (rv) && url.Length()) ? g_strdup(url.get()) : NULL;

	rv = mpriv->browser->GetSHTitleAtIndex(nth, &title);

	if (title)
	{
		nsCString cTitle;
		NS_UTF16ToCString (nsString(title),
				   NS_CSTRING_ENCODING_UTF8, cTitle);
		*aTitle = g_strdup (cTitle.get());
		nsMemory::Free (title);
	}
	else
	{
		*aTitle = NULL;
	}
}

static int
impl_shistory_get_pos (EphyEmbed *embed)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;
	nsresult rv;
	int count, index;

	rv = mpriv->browser->GetSHInfo (&count, &index);

	return NS_SUCCEEDED(rv) ? index : 0;
}

static void
impl_shistory_go_nth (EphyEmbed *embed, 
		      int nth)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;

	mpriv->browser->GoToHistoryIndex (nth);
}

static void
impl_shistory_copy (EphyEmbed *source,
		    EphyEmbed *dest,
		    gboolean copy_back,
		    gboolean copy_forward,
		    gboolean copy_current)
{
	MozillaEmbedPrivate *spriv = MOZILLA_EMBED(source)->priv;
	MozillaEmbedPrivate *dpriv = MOZILLA_EMBED(dest)->priv;

	spriv->browser->CopySHistory(dpriv->browser, copy_back,
				     copy_forward, copy_current);
}

static void
impl_get_security_level (EphyEmbed *embed,
			 EphyEmbedSecurityLevel *level,
			 char **description)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED (embed)->priv;

	if (level) *level = EPHY_EMBED_STATE_IS_UNKNOWN;
	if (description) *description = NULL;

	nsresult rv;
	PRUint32 state;
	nsCString desc;
	rv = mpriv->browser->GetSecurityInfo (&state, desc);
	if (NS_FAILED (rv)) return;

	if (level) *level = mozilla_embed_security_level (state);
	if (description) *description = g_strdup (desc.get());
}

static void
impl_show_page_certificate (EphyEmbed *embed)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED (embed)->priv;

	mpriv->browser->ShowCertificate ();
}
	
static void
impl_print (EphyEmbed *embed)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;
 
	mpriv->browser->Print ();
}

static void
impl_set_print_preview_mode (EphyEmbed *embed, gboolean preview_mode)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;

	mpriv->browser->SetPrintPreviewMode (preview_mode);
}

static int
impl_print_preview_n_pages (EphyEmbed *embed)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;
	nsresult rv;
	int num;

	rv = mpriv->browser->PrintPreviewNumPages(&num);

	return NS_SUCCEEDED (rv) ? num : 0;
}

static void
impl_print_preview_navigate (EphyEmbed *embed,
			     EphyEmbedPrintPreviewNavType type,
			     int page)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;

	mpriv->browser->PrintPreviewNavigate(type, page);
}

static void
impl_set_encoding (EphyEmbed *embed,
		   const char *encoding)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;
	nsresult rv;
	nsCString currEnc;

	g_return_if_fail (encoding != NULL);

	rv = mpriv->browser->GetEncoding (currEnc);
	if (NS_FAILED (rv)) return;

	if (strcmp (currEnc.get(), encoding) != 0 ||
	    encoding[0] == '\0' && !ephy_embed_has_automatic_encoding (embed))
	{
		rv = mpriv->browser->ForceEncoding (encoding);
		if (NS_FAILED (rv)) return;
	}

	gtk_moz_embed_reload (mpriv->moz_embed,
			      GTK_MOZ_EMBED_FLAG_RELOADCHARSETCHANGE);
}

static char *
impl_get_encoding (EphyEmbed *embed)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;
	nsresult rv;
	nsCString encoding;

	rv = mpriv->browser->GetEncoding (encoding);

	if (NS_FAILED (rv) || !encoding.Length())
	{
		return NULL;
	}

	return g_strdup (encoding.get());
}

static gboolean
impl_has_automatic_encoding (EphyEmbed *embed)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;
	nsresult rv;
	nsCString encoding;

	rv = mpriv->browser->GetForcedEncoding (encoding);

	if (NS_FAILED (rv) || !encoding.Length())
	{
		return TRUE;
	}

	return FALSE;
}

static gboolean
impl_has_modified_forms (EphyEmbed *embed)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;
	nsresult rv;

	PRBool modified;
	rv = mpriv->browser->GetHasModifiedForms (&modified);

	return NS_SUCCEEDED (rv) ? modified : FALSE;
}

static EphyEmbedDocumentType
impl_get_document_type (EphyEmbed *embed)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;

	return mpriv->document_type;
}

static int
impl_get_load_percent (EphyEmbed *embed)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;

	return mpriv->load_percent;
}

static void
mozilla_embed_set_load_percent (MozillaEmbed *embed, int percent)
{
       MozillaEmbedPrivate *mpriv = embed->priv;

       if (percent != mpriv->load_percent)
       {
	       mpriv->load_percent = percent;

	       g_object_notify (G_OBJECT (embed), "load-progress");
       }
}

static gboolean
impl_get_load_status (EphyEmbed *embed)
{
	MozillaEmbedPrivate *mpriv = MOZILLA_EMBED(embed)->priv;

	return mpriv->is_loading;
}

static void
mozilla_embed_set_load_status (MozillaEmbed *embed, gboolean status)
{
       MozillaEmbedPrivate *mpriv = embed->priv;
       guint is_loading;

       is_loading = status != FALSE;

       if (is_loading != mpriv->is_loading)
       {
	       mpriv->is_loading = is_loading;

	       g_object_notify (G_OBJECT (embed), "load-status");
       }
}

static void
mozilla_embed_update_navigation_flags (MozillaEmbed *membed)
{
	MozillaEmbedPrivate *priv = membed->priv;
	EphyEmbed *embed = EPHY_EMBED (membed);
	guint flags = 0;

	if (impl_can_go_up (embed))
	{
		flags |= EPHY_EMBED_NAV_UP;
	}

	if (impl_can_go_back (embed))
	{
		flags |= EPHY_EMBED_NAV_BACK;
	}

	if (impl_can_go_forward (embed))
	{
		flags |= EPHY_EMBED_NAV_FORWARD;
	}

	if (priv->nav_flags != (EphyEmbedNavigationFlags)flags)
	{
		priv->nav_flags = (EphyEmbedNavigationFlags)flags;

		g_object_notify (G_OBJECT (embed), "navigation");
	}
}

static EphyEmbedNavigationFlags
impl_get_navigation_flags (EphyEmbed *embed)
{
	MozillaEmbedPrivate *priv = MOZILLA_EMBED (embed)->priv;
	return priv->nav_flags;
}

static const char*
impl_get_typed_address (EphyEmbed *embed)
{
	return MOZILLA_EMBED (embed)->priv->typed_address;
}

static void
impl_set_typed_address (EphyEmbed *embed,
			const char *address,
			EphyEmbedAddressExpire expire)
{
       MozillaEmbedPrivate *priv = MOZILLA_EMBED (embed)->priv;
 
       g_free (priv->typed_address);
       priv->typed_address = g_strdup (address);

       if (expire == EPHY_EMBED_ADDRESS_EXPIRE_CURRENT &&
	   !priv->is_loading)
       {
	       priv->address_expire = EPHY_EMBED_ADDRESS_EXPIRE_NOW;
       }
       else
       {
	       priv->address_expire = expire;
       }

       g_object_notify (G_OBJECT (embed), "typed-address");
}

static const char*
impl_get_address (EphyEmbed *embed)
{
	MozillaEmbedPrivate *priv = MOZILLA_EMBED (embed)->priv;
  
	return priv->address ? priv->address : "about:blank";
}

static const char*
impl_get_status_message (EphyEmbed *embed)
{
	MozillaEmbedPrivate *priv = MOZILLA_EMBED (embed)->priv;

	if (priv->link_message && priv->link_message[0] != '\0')
	{
		return priv->link_message;
	}
	else if (priv->status_message)
	{
		return priv->status_message;
	}
	else
	{
		return NULL;
	}
}

static const char*
impl_get_link_message (EphyEmbed *embed)
{
	MozillaEmbedPrivate *priv = MOZILLA_EMBED (embed)->priv;

	return priv->link_message;
}

static gboolean
impl_get_is_blank (EphyEmbed *embed)
{
	MozillaEmbedPrivate *priv = MOZILLA_EMBED (embed)->priv;

	return priv->is_blank;
}

static const char*
impl_get_loading_title (EphyEmbed *embed)
{
	MozillaEmbedPrivate *priv = MOZILLA_EMBED (embed)->priv;

	return priv->loading_title;
}

static const char*
impl_get_icon_address (EphyEmbed *embed)
{
	MozillaEmbedPrivate *priv = MOZILLA_EMBED (embed)->priv;

	return priv->icon_address;
}

static GdkPixbuf*
impl_get_icon (EphyEmbed *embed)
{
	MozillaEmbedPrivate *priv = MOZILLA_EMBED (embed)->priv;

	return priv->icon;
}

static void
mozilla_embed_set_address (MozillaEmbed *embed, char *address)
{
	MozillaEmbedPrivate *priv = embed->priv;
	GObject *object = G_OBJECT (embed);
	
	g_free (priv->address);
	priv->address = address;

	priv->is_blank = address == NULL ||
			 strcmp (address, "about:blank") == 0;

	if (priv->is_loading &&
	    priv->address_expire == EPHY_EMBED_ADDRESS_EXPIRE_NOW &&
	    priv->typed_address != NULL)
	{
		g_free (priv->typed_address);
		priv->typed_address = NULL;

		g_object_notify (object, "typed-address");
	}

	g_object_notify (object, "address");
}

static void
mozilla_embed_file_monitor_cancel (MozillaEmbed *embed)
{
	MozillaEmbedPrivate *priv = embed->priv;

	if (priv->monitor != NULL)
	{
		LOG ("Cancelling file monitor");

		gnome_vfs_monitor_cancel (priv->monitor);
		priv->monitor = NULL;
	}

	if (priv->reload_scheduled_id != 0)
	{
		LOG ("Cancelling scheduled reload");

		g_source_remove (priv->reload_scheduled_id);
		priv->reload_scheduled_id = 0;
	}

	priv->reload_delay_ticks = 0;
}

static gboolean
ephy_file_monitor_reload_cb (MozillaEmbed *embed)
{
	MozillaEmbedPrivate *priv = embed->priv;

	if (priv->reload_delay_ticks > 0)
	{
		priv->reload_delay_ticks--;

		/* Run again */
		return TRUE;
	}

	if (priv->is_loading)
	{
		/* Wait a bit to reload if we're still loading! */
		priv->reload_delay_ticks = RELOAD_DELAY_MAX_TICKS / 2;

		/* Run again */
		return TRUE;
	}

	priv->reload_scheduled_id = 0;

	//LOG ("Reloading file '%s'", impl_get_address (embed));

	impl_reload (EPHY_EMBED (embed), TRUE);

	/* don't run again */
	return FALSE;
}

static void
mozilla_embed_file_monitor_cb (GnomeVFSMonitorHandle *handle,
			       const gchar *monitor_uri,
			       const gchar *info_uri,
			       GnomeVFSMonitorEventType event_type,
			       MozillaEmbed *embed)
{
	gboolean uri_is_directory;
	gboolean should_reload;
	char* local_path;
	MozillaEmbedPrivate *priv = embed->priv;

	LOG ("File '%s' has changed, scheduling reload", monitor_uri);

	local_path = gnome_vfs_get_local_path_from_uri(monitor_uri);
	uri_is_directory = g_file_test(local_path, G_FILE_TEST_IS_DIR);
	g_free(local_path);

	switch (event_type)
	{
		/* These events will always trigger a reload: */
		case GNOME_VFS_MONITOR_EVENT_CHANGED:
		case GNOME_VFS_MONITOR_EVENT_CREATED:
			should_reload = TRUE;
			break;

		/* These events will only trigger a reload for directories: */
		case GNOME_VFS_MONITOR_EVENT_DELETED:
		case GNOME_VFS_MONITOR_EVENT_METADATA_CHANGED:
			should_reload = uri_is_directory;
			break;

		/* These events don't trigger a reload: */
		case GNOME_VFS_MONITOR_EVENT_STARTEXECUTING:
		case GNOME_VFS_MONITOR_EVENT_STOPEXECUTING:
		default:
			should_reload = FALSE;
			break;
	}

	if (should_reload) {
		/* We make a lot of assumptions here, but basically we know
		 * that we just have to reload, by construction.
		 * Delay the reload a little bit so we don't endlessly
		 * reload while a file is written.
		 */
		if (priv->reload_delay_ticks == 0)
		{
			priv->reload_delay_ticks = 1;
		}
		else
		{
			/* Exponential backoff */
			priv->reload_delay_ticks = MIN (priv->reload_delay_ticks * 2,
					RELOAD_DELAY_MAX_TICKS);
		}

		if (priv->reload_scheduled_id == 0)
		{
			priv->reload_scheduled_id =
				g_timeout_add (RELOAD_DELAY,
						(GSourceFunc) ephy_file_monitor_reload_cb, embed);
		}
	}
}

static void
mozilla_embed_update_file_monitor (MozillaEmbed *embed,
				   const gchar *address)
{
	MozillaEmbedPrivate *priv = embed->priv;
	GnomeVFSMonitorHandle *handle = NULL;
	gboolean local;
	char* local_path;
	GnomeVFSMonitorType monitor_type;

	if (priv->monitor != NULL &&
	    priv->address != NULL && address != NULL &&
	    strcmp (priv->address, address) == 0)
	
	{
		/* same address, no change needed */
		return;
	}

	mozilla_embed_file_monitor_cancel (embed);

	local = g_str_has_prefix (address, "file://");
	if (local == FALSE) return;
	
	local_path = gnome_vfs_get_local_path_from_uri(address);
	monitor_type = g_file_test(local_path, G_FILE_TEST_IS_DIR)
		? GNOME_VFS_MONITOR_DIRECTORY
		: GNOME_VFS_MONITOR_FILE;
	g_free(local_path);

	if (gnome_vfs_monitor_add (&handle, address,
				   monitor_type,
				   (GnomeVFSMonitorCallback) mozilla_embed_file_monitor_cb,
				   embed) == GNOME_VFS_OK)
	{
		LOG ("Installed monitor for file '%s'", address);

		priv->monitor = handle;
	}
}

static void
mozilla_embed_set_link_message (MozillaEmbed *embed,
				char *link_message)
{
	MozillaEmbedPrivate *priv = embed->priv;
	
	g_free (priv->link_message);
	
	priv->link_message = ephy_embed_utils_link_message_parse (link_message);

	g_object_notify (G_OBJECT (embed), "status-message");
	g_object_notify (G_OBJECT (embed), "link-message");
}

static void
mozilla_embed_location_changed_cb (GtkMozEmbed *embed, 
				   MozillaEmbed *membed)
{
	char *location;
	GObject *object = G_OBJECT (membed);

	location = gtk_moz_embed_get_location (embed);
	g_signal_emit_by_name (membed, "ge_location", location);

	g_object_freeze_notify (object);

	/* do this up here so we still have the old address around */
	mozilla_embed_update_file_monitor (membed, location);

	/* Do not expose about:blank to the user, an empty address
	   bar will do better */
	if (location == NULL || location[0] == '\0' ||
	    strcmp (location, "about:blank") == 0)
	{
		mozilla_embed_set_address (membed, NULL);
		mozilla_embed_set_title (membed, NULL);
	}
	else
	{
		char *embed_address;

		/* we do this to get rid of an eventual password in the URL */
		embed_address = impl_get_location (EPHY_EMBED (membed), TRUE);
		mozilla_embed_set_address (membed, embed_address);
		mozilla_embed_set_loading_title (membed, embed_address, TRUE);
	}

	g_free (location);

	mozilla_embed_set_link_message (membed, NULL);
	mozilla_embed_set_icon_address (membed, NULL);
	mozilla_embed_update_navigation_flags (membed);

	g_object_notify (object, "title");

	g_object_thaw_notify (object);
}

static void
mozilla_embed_link_message_cb	    (GtkMozEmbed *embed,
				     MozillaEmbed *membed)
{
	char *link_message = gtk_moz_embed_get_link_message (embed);
	mozilla_embed_set_link_message (membed, link_message);
	g_free (link_message);
}

static void
mozilla_embed_load_icon (MozillaEmbed *embed)
{
	MozillaEmbedPrivate *priv = embed->priv;
	EphyEmbedShell *shell;
	EphyFaviconCache *cache;

	if (priv->icon_address == NULL || priv->icon != NULL) return;

	shell = ephy_embed_shell_get_default ();
	cache = EPHY_FAVICON_CACHE (ephy_embed_shell_get_favicon_cache (shell));

	/* ephy_favicon_cache_get returns a reference already */
	priv->icon = ephy_favicon_cache_get (cache, priv->icon_address);

	g_object_notify (G_OBJECT (embed), "icon");
}

static void
mozilla_embed_icon_cache_changed_cb (EphyFaviconCache *cache,
				     const char *address,
				     MozillaEmbed *embed)
{
	MozillaEmbedPrivate *priv = embed->priv;

	g_return_if_fail (address != NULL);

	/* is this for us? */
	if (priv->icon_address != NULL &&
	    strcmp (priv->icon_address, address) == 0)
	{
		mozilla_embed_load_icon (embed);
	}
}

static void
mozilla_embed_set_icon_address (MozillaEmbed *embed,
				const char *address)
{
	GObject *object = G_OBJECT (embed);
	MozillaEmbedPrivate *priv = embed->priv;
	/*	EphyBookmarks *eb;*/
	EphyHistory *history;

	g_free (priv->icon_address);
	priv->icon_address = g_strdup (address);

	if (priv->icon != NULL)
	{
		g_object_unref (priv->icon);
		priv->icon = NULL;

		g_object_notify (object, "icon");
	}

	if (priv->icon_address)
	{
		/* FIXME: we need to put this somewhere inside src?/
		history = EPHY_HISTORY
			(ephy_embed_shell_get_global_history (embed_shell));
		ephy_history_set_icon (history, priv->address,
				       priv->icon_address);

		eb = ephy_shell_get_bookmarks (ephy_shell);
		ephy_bookmarks_set_icon (eb, priv->address,
		priv->icon_address);*/

		mozilla_embed_load_icon (embed);
	}

	g_object_notify (object, "icon-address");
}

static void
mozilla_embed_favicon_cb (MozillaEmbed *membed,
			  const char *address,
			  GtkMozEmbed *embed)
{
	mozilla_embed_set_icon_address (membed, address);
}

static gboolean
address_has_web_scheme (const char *address)
{
	gboolean has_web_scheme;

	if (address == NULL) return FALSE;

	has_web_scheme = (g_str_has_prefix (address, "http:") ||
			  g_str_has_prefix (address, "https:") ||
			  g_str_has_prefix (address, "ftp:") ||
			  g_str_has_prefix (address, "file:") ||
			  g_str_has_prefix (address, "data:") ||
			  g_str_has_prefix (address, "about:") ||
			  g_str_has_prefix (address, "gopher:"));

	return has_web_scheme;
}

static void
mozilla_embed_restore_zoom_level (MozillaEmbed *membed, const char *address)
{
	MozillaEmbedPrivate *priv = membed->priv;

	/* restore zoom level */
	if (address_has_web_scheme (address))
	{
		EphyHistory *history;
		EphyNode *host;
		GValue value = { 0, };
		float zoom = 1.0, current_zoom;

		history = EPHY_HISTORY
			(ephy_embed_shell_get_global_history (embed_shell));
		host = ephy_history_get_host (history, address);

		if (host != NULL && ephy_node_get_property
				     (host, EPHY_NODE_HOST_PROP_ZOOM, &value))
		{
			zoom = g_value_get_float (&value);
			g_value_unset (&value);
		}

		current_zoom = ephy_embed_get_zoom (EPHY_EMBED (membed));
		if (zoom != current_zoom)
		{
			priv->is_setting_zoom = TRUE;
			ephy_embed_set_zoom (EPHY_EMBED (membed), zoom);
			priv->is_setting_zoom = FALSE;
		}
	}
}

static void
update_load_state (MozillaEmbed *membed, gint state)
{
	MozillaEmbedPrivate *priv = membed->priv;
	GtkMozEmbed *moz_embed = priv->moz_embed;

	if (state & GTK_MOZ_EMBED_FLAG_IS_DOCUMENT &&
	    state & (GTK_MOZ_EMBED_FLAG_START | GTK_MOZ_EMBED_FLAG_STOP))
	{
		g_signal_emit_by_name (membed, "ge-document-type",
				       priv->browser->GetDocumentType ());
	}

	if (state & GTK_MOZ_EMBED_FLAG_RESTORING &&
	    priv->load_state == MOZILLA_EMBED_LOAD_STARTED)
	{
		priv->load_state = MOZILLA_EMBED_LOAD_LOADING;

		char *address;
		address = gtk_moz_embed_get_location (moz_embed);
		g_signal_emit_by_name (membed, "ge-content-change", address);
		mozilla_embed_restore_zoom_level (membed, address);
		g_free (address);
	}

	if (state & GTK_MOZ_EMBED_FLAG_IS_NETWORK)
	{
		if (state & GTK_MOZ_EMBED_FLAG_START)
		{
			priv->load_state = MOZILLA_EMBED_LOAD_STARTED;
		}
		else if (state & GTK_MOZ_EMBED_FLAG_STOP)
		{
			priv->load_state = MOZILLA_EMBED_LOAD_STOPPED;
		}
	}
	else if (state & GTK_MOZ_EMBED_FLAG_START &&
		 state & GTK_MOZ_EMBED_FLAG_IS_REQUEST)
	{
		if (priv->load_state == MOZILLA_EMBED_LOAD_REDIRECTING)
		{
			priv->load_state = MOZILLA_EMBED_LOAD_STARTED;
		}
		else if (priv->load_state != MOZILLA_EMBED_LOAD_LOADING)
		{
			priv->load_state = MOZILLA_EMBED_LOAD_LOADING;

			char *address;
			address = gtk_moz_embed_get_location (moz_embed);
			g_signal_emit_by_name (membed, "ge_content_change", address);
			mozilla_embed_restore_zoom_level (membed, address);
			g_free (address);
		}
	}
	else if (state & GTK_MOZ_EMBED_FLAG_REDIRECTING &&
		 priv->load_state == MOZILLA_EMBED_LOAD_STARTED)
	{
		priv->load_state = MOZILLA_EMBED_LOAD_REDIRECTING;
	}
}

static void
update_net_state_message (MozillaEmbed *embed, const char *uri, EphyEmbedNetState flags)
{
	GnomeVFSURI *vfs_uri = NULL;
	const char *msg = NULL;
	const char *host = NULL;

	if (uri != NULL)
	{
		vfs_uri = gnome_vfs_uri_new (uri);
	}

	if (vfs_uri != NULL)
	{
		host = gnome_vfs_uri_get_host_name (vfs_uri);
	}

	if (host == NULL || host[0] == '\0') goto out;

	/* IS_REQUEST and IS_NETWORK can be both set */
	if (flags & EPHY_EMBED_STATE_IS_REQUEST)
	{
		if (flags & EPHY_EMBED_STATE_REDIRECTING)
		{
			msg = _("Redirecting to “%s”…");
		}
		else if (flags & EPHY_EMBED_STATE_TRANSFERRING)
		{
			msg = _("Transferring data from “%s”…");
		}
		else if (flags & EPHY_EMBED_STATE_NEGOTIATING)
		{
			msg = _("Waiting for authorization from “%s”…");
		}
	}

	if (flags & EPHY_EMBED_STATE_IS_NETWORK)
	{
		if (flags & EPHY_EMBED_STATE_START)
		{
			msg = _("Loading “%s”…");
		}
	}

	if ((flags & EPHY_EMBED_STATE_IS_NETWORK) &&
	    (flags & EPHY_EMBED_STATE_STOP))
	{
		g_free (embed->priv->status_message);
		embed->priv->status_message = NULL;
		g_object_notify (G_OBJECT (embed), "status-message");

	}
	else if (msg != NULL)
	{
		g_free (embed->priv->status_message);
		g_free (embed->priv->loading_title);
		embed->priv->status_message = g_strdup_printf (msg, host);
		embed->priv->loading_title = g_strdup_printf (msg, host);
		g_object_notify (G_OBJECT (embed), "status-message");
		g_object_notify (G_OBJECT (embed), "title");
	}

out:
	if (vfs_uri != NULL)
	{
		gnome_vfs_uri_unref (vfs_uri);
	}
}

static int
build_load_percent (int requests_done, int requests_total)
{
	int percent= 0;

	if (requests_total > 0)
	{
		percent = (requests_done * 100) / requests_total;

		/* Mozilla sometimes report more done requests than
		   total requests. Their progress widget clamp the value */
		percent = CLAMP (percent, 0, 100);
	}

	return percent;
}

static void
build_progress_from_requests (MozillaEmbed *embed, EphyEmbedNetState state)
{
	int load_percent;

	if (state & EPHY_EMBED_STATE_IS_REQUEST)
	{
		if (state & EPHY_EMBED_STATE_START)
		{
			embed->priv->total_requests ++;
		}
		else if (state & EPHY_EMBED_STATE_STOP)
		{
			embed->priv->cur_requests ++;
		}

		load_percent = build_load_percent (embed->priv->cur_requests,
						   embed->priv->total_requests);

		mozilla_embed_set_load_percent (embed, load_percent);
	}
}

static void
ensure_page_info (MozillaEmbed *embed, const char *address)
{
	MozillaEmbedPrivate *priv = embed->priv;

	if ((priv->address == NULL || priv->address[0] == '\0') &&
	    priv->address_expire == EPHY_EMBED_ADDRESS_EXPIRE_NOW)
	{
		mozilla_embed_set_address (embed, g_strdup (address));
	}

	/* FIXME huh?? */
	if (priv->title == NULL || priv->title[0] == '\0')
	{
		mozilla_embed_set_title (embed, NULL);
	}
}

static void
update_embed_from_net_state (MozillaEmbed *embed,
			     const char *uri,
			     EphyEmbedNetState state)
{
	MozillaEmbedPrivate *priv = embed->priv;

	update_net_state_message (embed, uri, state);

	if (state & EPHY_EMBED_STATE_IS_NETWORK)
	{
		if (state & EPHY_EMBED_STATE_START)
		{
			GObject *object = G_OBJECT (embed);

			g_object_freeze_notify (object);

			priv->total_requests = 0;
			priv->cur_requests = 0;

			mozilla_embed_set_load_percent (embed, 0);
			mozilla_embed_set_load_status (embed, TRUE);

			ensure_page_info (embed, uri);

			g_object_notify (object, "title");

			g_object_thaw_notify (object);
		}
		else if (state & EPHY_EMBED_STATE_STOP)
		{
			GObject *object = G_OBJECT (embed);

			g_object_freeze_notify (object);

			mozilla_embed_set_load_percent (embed, 100);
			mozilla_embed_set_load_status (embed, FALSE);

			g_free (priv->loading_title);
			priv->loading_title = NULL;

			priv->address_expire = EPHY_EMBED_ADDRESS_EXPIRE_NOW;

			g_object_notify (object, "title");

			g_object_thaw_notify (object);
		}

		mozilla_embed_update_navigation_flags (embed);
	}

	build_progress_from_requests (embed, state);
}

static void
mozilla_embed_net_state_all_cb (GtkMozEmbed *embed, const char *aURI,
				gint state, guint status, 
				MozillaEmbed *membed)
{
	EphyEmbedNetState estate = EPHY_EMBED_STATE_UNKNOWN;
	int i;

	struct
	{
		guint state;
		EphyEmbedNetState embed_state;
	}
	conversion_map [] =
	{
		{ GTK_MOZ_EMBED_FLAG_START, EPHY_EMBED_STATE_START },
		{ GTK_MOZ_EMBED_FLAG_STOP, EPHY_EMBED_STATE_STOP },
		{ GTK_MOZ_EMBED_FLAG_REDIRECTING, EPHY_EMBED_STATE_REDIRECTING },
		{ GTK_MOZ_EMBED_FLAG_TRANSFERRING, EPHY_EMBED_STATE_TRANSFERRING },
		{ GTK_MOZ_EMBED_FLAG_NEGOTIATING, EPHY_EMBED_STATE_NEGOTIATING },
		{ GTK_MOZ_EMBED_FLAG_IS_REQUEST, EPHY_EMBED_STATE_IS_REQUEST },
		{ GTK_MOZ_EMBED_FLAG_IS_DOCUMENT, EPHY_EMBED_STATE_IS_DOCUMENT },
		{ GTK_MOZ_EMBED_FLAG_IS_NETWORK, EPHY_EMBED_STATE_IS_NETWORK },
		{ GTK_MOZ_EMBED_FLAG_RESTORING, EPHY_EMBED_STATE_RESTORING },
		{ 0, EPHY_EMBED_STATE_UNKNOWN }
	};

	for (i = 0; conversion_map[i].state != 0; i++)
	{
		if (state & conversion_map[i].state)
		{
			estate = (EphyEmbedNetState) (estate | conversion_map[i].embed_state);	
		}
	}

	update_load_state (membed, state);
	update_embed_from_net_state (membed, aURI, (EphyEmbedNetState)estate);
	
	g_signal_emit_by_name (membed, "ge_net_state", aURI, /* FIXME: (gulong) */ estate);
}

static gboolean
mozilla_embed_emit_mouse_signal (MozillaEmbed *embed,
				 gpointer dom_event, 
				 const char *signal_name)
{
	MozillaEmbedPrivate *mpriv = embed->priv;
	MozillaEmbedEvent *info;
	EventContext event_context;
	gint return_value = FALSE;
	nsresult rv;

	if (dom_event == NULL) return FALSE;

	nsCOMPtr<nsIDOMMouseEvent> ev = static_cast<nsIDOMMouseEvent*>(dom_event);
	NS_ENSURE_TRUE (ev, FALSE);
	nsCOMPtr<nsIDOMEvent> dev = do_QueryInterface (ev);
	NS_ENSURE_TRUE (dev, FALSE);

	info = mozilla_embed_event_new (static_cast<gpointer>(dev));

	event_context.Init (mpriv->browser);
	rv = event_context.GetMouseEventInfo (ev, MOZILLA_EMBED_EVENT (info));
	if (NS_FAILED (rv))
	{
		g_object_unref (info);
		return FALSE;
	}

	nsCOMPtr<nsIDOMDocument> domDoc;
	rv = event_context.GetTargetDocument (getter_AddRefs(domDoc));
	if (NS_SUCCEEDED (rv))
	{
		mpriv->browser->PushTargetDocument (domDoc);

		g_signal_emit_by_name (embed, signal_name, 
				       info, &return_value); 
		mpriv->browser->PopTargetDocument ();
	}

	g_object_unref (info);

	return return_value;
}

static gboolean
mozilla_embed_dom_mouse_click_cb (GtkMozEmbed *embed,
				  gpointer dom_event, 
				  MozillaEmbed *membed)
{
	return mozilla_embed_emit_mouse_signal (membed, dom_event,
						"ge_dom_mouse_click");
}

static gboolean
mozilla_embed_dom_mouse_down_cb (GtkMozEmbed *embed, gpointer dom_event, 
				 MozillaEmbed *membed)
{
	return mozilla_embed_emit_mouse_signal (membed, dom_event,
						"ge_dom_mouse_down");
}

static gint
mozilla_embed_dom_key_press_cb (GtkMozEmbed *embed,
				gpointer dom_event, 
				MozillaEmbed *membed)
{
	gint retval = FALSE;

	if (dom_event == NULL) return FALSE;

	nsCOMPtr<nsIDOMKeyEvent> ev = static_cast<nsIDOMKeyEvent*>(dom_event);
	NS_ENSURE_TRUE (ev, FALSE);

	if (!EventContext::CheckKeyPress (ev)) return FALSE;

	GdkEvent *event = gtk_get_current_event ();
	if (event == NULL) return FALSE; /* shouldn't happen! */

	g_return_val_if_fail (GDK_KEY_PRESS == event->type, FALSE);

	g_signal_emit_by_name (embed, "ge-search-key-press", event, &retval);

	gdk_event_free (event);

	return retval;
}

EphyEmbedChrome
_mozilla_embed_translate_chrome (GtkMozEmbedChromeFlags flags)
{
	static const struct
	{
		guint mozilla_flag;
		guint ephy_flag;
	}
	conversion_map [] =
	{
		{ GTK_MOZ_EMBED_FLAG_MENUBARON, EPHY_EMBED_CHROME_MENUBAR },
		{ GTK_MOZ_EMBED_FLAG_TOOLBARON, EPHY_EMBED_CHROME_TOOLBAR },
		{ GTK_MOZ_EMBED_FLAG_STATUSBARON, EPHY_EMBED_CHROME_STATUSBAR },
		{ GTK_MOZ_EMBED_FLAG_PERSONALTOOLBARON, EPHY_EMBED_CHROME_BOOKMARKSBAR },
	};

	guint mask = 0, i;

	for (i = 0; i < G_N_ELEMENTS (conversion_map); i++)
	{
		if (flags & conversion_map[i].mozilla_flag)
		{
			mask |= conversion_map[i].ephy_flag;
		}
	}

	return (EphyEmbedChrome) mask;
}

static void
mozilla_embed_new_window_cb (GtkMozEmbed *embed, 
			     GtkMozEmbed **newEmbed,
			     guint chrome_mask, 
			     MozillaEmbed *membed)
{
	GtkMozEmbedChromeFlags chrome = (GtkMozEmbedChromeFlags) chrome_mask;
	EphyEmbed *new_embed = NULL;
	GObject *single;
	EphyEmbedChrome mask;

	if (chrome & GTK_MOZ_EMBED_FLAG_OPENASCHROME)
	{
		*newEmbed = _mozilla_embed_new_xul_dialog ();
		return;
	}

	mask = _mozilla_embed_translate_chrome (chrome);

	single = ephy_embed_shell_get_embed_single (embed_shell);
	g_signal_emit_by_name (single, "new-window", embed, mask,
			       &new_embed);

	g_assert (new_embed != NULL);

	gtk_moz_embed_set_chrome_mask (embed, chrome);

	g_signal_emit_by_name (membed, "ge-new-window", new_embed);

	*newEmbed = GTK_MOZ_EMBED (new_embed);
}

static void
mozilla_embed_set_security_level (MozillaEmbed *embed, EphyEmbedSecurityLevel level)
{
	MozillaEmbedPrivate *priv = embed->priv;

	if (priv->security_level != level)
	{
		priv->security_level = level;

		g_object_notify (G_OBJECT (embed), "security-level");
	}
}

static void
mozilla_embed_security_change_cb (GtkMozEmbed *embed, 
				  gpointer requestptr,
				  PRUint32 state,
				  MozillaEmbed *membed)
{
	mozilla_embed_set_security_level (membed, mozilla_embed_security_level (state));
}

static void
mozilla_embed_document_type_cb (EphyEmbed *embed,
				EphyEmbedDocumentType type,
				MozillaEmbed *membed)
{
	if (membed->priv->document_type != type)
	{
		membed->priv->document_type = type;

		g_object_notify (G_OBJECT (membed), "document-type");
	}
}

static void
mozilla_embed_zoom_change_cb (EphyEmbed *embed,
			      float zoom,
			      MozillaEmbed *membed)
{
	char *address;

	if (membed->priv->zoom != zoom)
	{
		if (membed->priv->is_setting_zoom)
		  {
		    return;
		  }

		address = ephy_embed_get_location (embed, TRUE);
		if (address_has_web_scheme (address))
		  {
		    EphyHistory *history;
		    EphyNode *host;
		    history = EPHY_HISTORY
		      (ephy_embed_shell_get_global_history (embed_shell));
		    host = ephy_history_get_host (history, address);

		    if (host != NULL)
		      {
			ephy_node_set_property_float (host,
						      EPHY_NODE_HOST_PROP_ZOOM,
						      zoom);
		      }
		  }

		g_free (address);

		membed->priv->zoom = zoom;

		g_object_notify (G_OBJECT (membed), "zoom");
	}
}

static char *
get_title_from_address (const char *address)
{
	GnomeVFSURI *uri;
	char *title;

	if (address == NULL) return NULL;
		
	uri = gnome_vfs_uri_new (address);
	if (uri == NULL) return g_strdup (address);
		
	title = gnome_vfs_uri_to_string (uri,
					 (GnomeVFSURIHideOptions)
					 (GNOME_VFS_URI_HIDE_USER_NAME |
					  GNOME_VFS_URI_HIDE_PASSWORD |
					  GNOME_VFS_URI_HIDE_HOST_PORT |
					  GNOME_VFS_URI_HIDE_TOPLEVEL_METHOD |
					  GNOME_VFS_URI_HIDE_FRAGMENT_IDENTIFIER));
	gnome_vfs_uri_unref (uri);

	return title;
}

static void
mozilla_embed_set_loading_title (MozillaEmbed *embed,
				 const char *title,
				 gboolean is_address)
{
	MozillaEmbedPrivate *priv = embed->priv;
	char *freeme = NULL;

	g_free (priv->loading_title);
	priv->loading_title = NULL;

	if (is_address)
	{
		title = freeme = get_title_from_address (title);	
	}

	if (title != NULL && title[0] != '\0')
	{
		/* translators: %s here is the address of the web page */
		priv->loading_title = g_strdup_printf (_("Loading “%s”…"), title);
	}
	else
	{
		priv->loading_title = g_strdup (_("Loading…"));
	}

	g_free (freeme);
}

static void
mozilla_embed_set_title (MozillaEmbed *embed,
			 char *title)
{
	MozillaEmbedPrivate *priv = embed->priv;

	if (!priv->is_blank && (title == NULL || g_strstrip (title)[0] == '\0'))
	{

		g_free (title);
		title = get_title_from_address (priv->address);

		/* Fallback */
		if (title == NULL || title[0] == '\0')
		{
			g_free (title);
			title = NULL;
			priv->is_blank = TRUE;
		}
	}
	else if (priv->is_blank && title != NULL)
	{
		g_free (title);
		title = NULL;
	}

	g_free (priv->title);
	priv->title = ephy_string_shorten (title, MAX_TITLE_LENGTH);

	g_object_notify (G_OBJECT (embed), "title");
}

static void
mozilla_embed_title_change_cb	    (GtkMozEmbed *embed,
				     MozillaEmbed *membed)
{
	GObject *object = G_OBJECT (embed);
	char *title;

	title = gtk_moz_embed_get_title (embed);

	g_object_freeze_notify (object);

	mozilla_embed_set_title (membed, title);
	mozilla_embed_set_loading_title (membed, title, FALSE);

	g_object_thaw_notify (object);
}

static gboolean
mozilla_embed_open_uri_cb (EphyEmbed *embed,
			   const char *uri,
			   MozillaEmbed *membed)
{
	MozillaEmbedPrivate *priv = membed->priv;

	/* Set the address here if we have a blank page.
	 * See bug #147840.
	 */
	if (priv->is_blank)
	{
		mozilla_embed_set_address (membed, g_strdup (uri));
		mozilla_embed_set_loading_title (membed, uri, TRUE);
	}

	/* allow load to proceed */
	return FALSE;
}

static EphyEmbedSecurityLevel
mozilla_embed_security_level (PRUint32 state)
{
	EphyEmbedSecurityLevel level;

	switch (state)
	{
	case nsIWebProgressListener::STATE_IS_INSECURE:
		level = EPHY_EMBED_STATE_IS_INSECURE;
		break;
	case nsIWebProgressListener::STATE_IS_BROKEN:
		level = EPHY_EMBED_STATE_IS_BROKEN;
		break;
	case nsIWebProgressListener::STATE_IS_SECURE|
	     nsIWebProgressListener::STATE_SECURE_HIGH:
		level = EPHY_EMBED_STATE_IS_SECURE_HIGH;
		break;
	case nsIWebProgressListener::STATE_IS_SECURE|
	     nsIWebProgressListener::STATE_SECURE_MED:
		level = EPHY_EMBED_STATE_IS_SECURE_MED;
		break;
	case nsIWebProgressListener::STATE_IS_SECURE|
	     nsIWebProgressListener::STATE_SECURE_LOW:
		level = EPHY_EMBED_STATE_IS_SECURE_LOW;
		break;
	default:
		level = EPHY_EMBED_STATE_IS_UNKNOWN;
		break;
	}
	return level;
}

static void
ephy_embed_iface_init (EphyEmbedIface *iface)
{
	iface->load_url = impl_load_url; 
	iface->load = impl_load; 
	iface->stop_load = impl_stop_load;
	iface->can_go_back = impl_can_go_back;
	iface->can_go_forward =impl_can_go_forward;
	iface->can_go_up = impl_can_go_up;
	iface->get_go_up_list = impl_get_go_up_list;
	iface->go_back = impl_go_back;
	iface->go_forward = impl_go_forward;
	iface->go_up = impl_go_up;
	iface->get_title = impl_get_title;
	iface->get_location = impl_get_location;
	iface->get_link_message = impl_get_link_message;
	iface->get_js_status = impl_get_js_status;
	iface->reload = impl_reload;
	iface->set_zoom = impl_set_zoom;
	iface->get_zoom = impl_get_zoom;
	iface->scroll_lines = impl_scroll_lines;
	iface->scroll_pages = impl_scroll_pages;
	iface->scroll_pixels = impl_scroll_pixels;
	iface->shistory_n_items = impl_shistory_n_items;
	iface->shistory_get_nth = impl_shistory_get_nth;
	iface->shistory_get_pos = impl_shistory_get_pos;
	iface->shistory_go_nth = impl_shistory_go_nth;
	iface->shistory_copy = impl_shistory_copy;
	iface->get_security_level = impl_get_security_level;
	iface->show_page_certificate = impl_show_page_certificate;
	iface->close = impl_close;
	iface->set_encoding = impl_set_encoding;
	iface->get_encoding = impl_get_encoding;
	iface->has_automatic_encoding = impl_has_automatic_encoding;
	iface->print = impl_print;
	iface->set_print_preview_mode = impl_set_print_preview_mode;
	iface->print_preview_n_pages = impl_print_preview_n_pages;
	iface->print_preview_navigate = impl_print_preview_navigate;
	iface->has_modified_forms = impl_has_modified_forms;
	iface->get_document_type = impl_get_document_type;
	iface->get_load_percent = impl_get_load_percent;
	iface->get_load_status = impl_get_load_status;
	iface->get_navigation_flags = impl_get_navigation_flags;
	iface->get_typed_address = impl_get_typed_address;
	iface->set_typed_address = impl_set_typed_address;
	iface->get_address = impl_get_address;
	iface->get_status_message = impl_get_status_message;
	iface->get_is_blank = impl_get_is_blank;
	iface->get_loading_title = impl_get_loading_title;
	iface->get_icon = impl_get_icon;
	iface->get_icon_address = impl_get_icon_address;
}

static void
xul_visibility_cb (GtkWidget *embed, gboolean visibility, GtkWidget *window)
{
	if (visibility)
	{
		gtk_widget_show (window);
	}
	else
	{
		gtk_widget_hide (window);
	}
}

static void
xul_size_to_cb (GtkWidget *embed, gint width, gint height, gpointer dummy)
{
	gtk_widget_set_size_request (embed, width, height);
}

static void
xul_new_window_cb (GtkMozEmbed *embed,
		   GtkMozEmbed **retval, 
		   guint chrome_mask,
		   gpointer dummy)
{
	g_assert (chrome_mask & GTK_MOZ_EMBED_FLAG_OPENASCHROME);

	*retval = _mozilla_embed_new_xul_dialog ();
}

static void
xul_title_cb (GtkMozEmbed *embed,
	      GtkWindow *window)
{
	char *title;

	title = gtk_moz_embed_get_title (embed);
	gtk_window_set_title (window, title);
	g_free (title);
}

GtkMozEmbed *
_mozilla_embed_new_xul_dialog (void)
{
	GtkWidget *window, *embed;

	g_object_ref (embed_shell);

	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
	g_object_set_data_full (G_OBJECT (window), "EmbedShellRef",
				embed_shell,
				(GDestroyNotify) g_object_unref);
	g_signal_connect_object (embed_shell, "prepare_close",
				 G_CALLBACK (gtk_widget_destroy), window,
				 (GConnectFlags) G_CONNECT_SWAPPED);

	embed = gtk_moz_embed_new ();
	gtk_widget_show (embed);
	gtk_container_add (GTK_CONTAINER (window), embed);

	g_signal_connect_object (embed, "destroy_browser",
				 G_CALLBACK (gtk_widget_destroy),
				 window, G_CONNECT_SWAPPED);
	g_signal_connect_object (embed, "visibility",
				 G_CALLBACK (xul_visibility_cb),
				 window, (GConnectFlags) 0);
	g_signal_connect_object (embed, "size_to",
				 G_CALLBACK (xul_size_to_cb),
				 NULL, (GConnectFlags) 0);
	g_signal_connect_object (embed, "new_window",
				 G_CALLBACK (xul_new_window_cb),
				 NULL, (GConnectFlags) 0);
	g_signal_connect_object (embed, "title",
				 G_CALLBACK (xul_title_cb),
				 window, (GConnectFlags) 0);

	return GTK_MOZ_EMBED (embed);
}