/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright © 2002 Jorn Baayen * Copyright © 2003, 2004 Marco Pesenti Gritti * Copyright © 2003, 2004, 2005, 2006, 2008 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. */ #include "config.h" #include "ephy-session.h" #include "eggdesktopfile.h" #include "eggsmclient.h" #include "ephy-bookmarks-editor.h" #include "ephy-debug.h" #include "ephy-embed-container.h" #include "ephy-embed-utils.h" #include "ephy-embed.h" #include "ephy-extension.h" #include "ephy-file-helpers.h" #include "ephy-gui.h" #include "ephy-history-window.h" #include "ephy-notebook.h" #include "ephy-prefs.h" #include "ephy-request-about.h" #include "ephy-settings.h" #include "ephy-shell.h" #include "ephy-stock-icons.h" #include "ephy-window.h" #include #include #include #include #include #include #include typedef struct { EphySessionCommand command; char *arg; char **args; guint32 user_time; } SessionCommand; #define EPHY_SESSION_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_SESSION, EphySessionPrivate)) struct _EphySessionPrivate { GList *windows; GList *tool_windows; GtkWidget *resume_infobar; GtkWidget *resume_window; GQueue *queue; guint queue_idle_id; GtkWidget *quit_interact_dialog; guint dont_save : 1; guint quit_while_resuming : 1; guint loading_homepage : 1; }; #define BOOKMARKS_EDITOR_ID "BookmarksEditor" #define HISTORY_WINDOW_ID "HistoryWindow" #define SESSION_CRASHED "type:session_crashed" static void ephy_session_class_init (EphySessionClass *klass); static void ephy_session_iface_init (EphyExtensionIface *iface); static void ephy_session_init (EphySession *session); static void session_command_queue_next (EphySession *session); enum { PROP_0, PROP_ACTIVE_WINDOW }; G_DEFINE_TYPE_WITH_CODE (EphySession, ephy_session, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (EPHY_TYPE_EXTENSION, ephy_session_iface_init)) /* Gnome session client */ typedef struct { EphySession *session; EggSMClient *sm_client; GtkWidget *dialog; GtkWidget *label; guint timeout_id; guint ticks; int response; } InteractData; static void confirm_shutdown_dialog_update_timeout_label (InteractData *data) { char *text; text = g_strdup_printf (ngettext ("Downloads will be aborted and logout proceed in %d second.", "Downloads will be aborted and logout proceed in %d seconds.", data->ticks), data->ticks); gtk_label_set_text (GTK_LABEL (data->label), text); g_free (text); } static gboolean confirm_shutdown_dialog_tick_cb (InteractData *data) { if (data->ticks > 0) { --data->ticks; confirm_shutdown_dialog_update_timeout_label (data); return TRUE; } data->timeout_id = 0; gtk_dialog_response (GTK_DIALOG (data->dialog), GTK_RESPONSE_ACCEPT); return FALSE; } static void confirm_shutdown_dialog_response_cb (GtkWidget *dialog, int response, InteractData *data) { LOG ("confirm_shutdown_dialog_response_cb response %d", response); data->response = response; gtk_widget_destroy (dialog); } static void confirm_shutdown_dialog_accept_cb (InteractData *data, GObject *zombie) { gtk_dialog_response (GTK_DIALOG (data->dialog), GTK_RESPONSE_ACCEPT); } static void confirm_shutdown_dialog_weak_ref_cb (InteractData *data, GObject *zombie) { EphySessionPrivate *priv = data->session->priv; EggSMClient *sm_client = data->sm_client; EphyShell *shell; gboolean will_quit; LOG ("confirm_shutdown_dialog_weak_ref_cb response %d", data->response); priv->quit_interact_dialog = NULL; shell = ephy_shell_get_default (); if (shell != NULL) { g_object_weak_unref (G_OBJECT (shell), (GWeakNotify) confirm_shutdown_dialog_accept_cb, data); } if (data->timeout_id != 0) { g_source_remove (data->timeout_id); } will_quit = data->response == GTK_RESPONSE_ACCEPT; g_free (data); egg_sm_client_will_quit (sm_client, will_quit); g_object_unref (sm_client); } static void client_quit_requested_cb (EggSMClient *sm_client, EphySession *session) { EphySessionPrivate *priv = session->priv; GtkWidget *dialog, *box; InteractData *data; GList *downloads; /* If we're shutting down, check if there are downloads * remaining, since they can't be restarted. */ downloads = ephy_embed_shell_get_downloads (embed_shell); if (ephy_shell_get_default () == NULL || downloads == NULL) { egg_sm_client_will_quit (sm_client, TRUE); return; } dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, _("Abort pending downloads?")); priv->quit_interact_dialog = dialog; gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("There are still downloads pending. If you log out, " "they will be aborted and lost.")); gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Cancel Logout"), GTK_RESPONSE_REJECT); gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Abort Downloads"), GTK_RESPONSE_ACCEPT); gtk_window_set_title (GTK_WINDOW (dialog), ""); gtk_window_set_icon_name (GTK_WINDOW (dialog), EPHY_STOCK_EPHY); gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_REJECT); data = g_new (InteractData, 1); data->sm_client = g_object_ref (sm_client); data->session = session; data->dialog = dialog; data->response = GTK_RESPONSE_REJECT; /* This isn't very exact, but it's good enough here */ data->timeout_id = g_timeout_add_seconds (1, (GSourceFunc) confirm_shutdown_dialog_tick_cb, data); data->ticks = 60; /* Add timeout label */ data->label = gtk_label_new (NULL); gtk_label_set_line_wrap (GTK_LABEL (data->label), TRUE); confirm_shutdown_dialog_update_timeout_label (data); box = ephy_gui_message_dialog_get_content_box (dialog); gtk_box_pack_end (GTK_BOX (box), data->label, FALSE, FALSE, 0); gtk_widget_show (data->label); /* When we're quitting, un-veto the shutdown */ g_object_weak_ref (G_OBJECT (ephy_shell_get_default ()), (GWeakNotify) confirm_shutdown_dialog_accept_cb, data); g_signal_connect (dialog, "response", G_CALLBACK (confirm_shutdown_dialog_response_cb), data); g_object_weak_ref (G_OBJECT (dialog), (GWeakNotify) confirm_shutdown_dialog_weak_ref_cb, data); gtk_window_present (GTK_WINDOW (dialog)); } static void client_quit_cancelled_cb (EggSMClient *sm_client, EphySession *session) { EphySessionPrivate *priv = session->priv; if (priv->quit_interact_dialog) { gtk_dialog_response (GTK_DIALOG (priv->quit_interact_dialog), GTK_RESPONSE_DELETE_EVENT); } } static void client_quit_cb (EggSMClient *sm_client, EphySession *session) { LOG ("quit-cb"); ephy_session_close (session); } static void client_save_state_cb (EggSMClient *sm_client, GKeyFile *keyfile, EphySession *session) { char *argv[] = { NULL, "--load-session", NULL }; char *discard_argv[] = { "rm", "-f", NULL }; char *tmp, *save_to; LOG ("save_yourself_cb"); tmp = g_build_filename (ephy_dot_dir (), "session_gnome-XXXXXX", NULL); save_to = ephy_file_tmp_filename (tmp, "xml"); g_free (tmp); argv[0] = g_get_prgname (); argv[2] = save_to; egg_sm_client_set_restart_command (sm_client, 3, (const char **) argv); discard_argv[2] = save_to; egg_sm_client_set_discard_command (sm_client, 3, (const char **) discard_argv); ephy_session_save (session, save_to); g_free (save_to); } /* Helper functions */ static GFile * get_session_file (const char *filename) { GFile *file; char *path; if (filename == NULL) { return NULL; } if (strcmp (filename, SESSION_CRASHED) == 0) { path = g_build_filename (ephy_dot_dir (), "session_crashed.xml", NULL); } else { path = g_strdup (filename); } file = g_file_new_for_path (path); g_free (path); return file; } static void session_delete (EphySession *session, const char *filename) { GFile *file; file = get_session_file (filename); g_file_delete (file, NULL, NULL); g_object_unref (file); } static void load_status_notify_cb (EphyWebView *view, GParamSpec *pspec, EphySession *session) { WebKitLoadStatus status = webkit_web_view_get_load_status (WEBKIT_WEB_VIEW (view)); /* We won't know the URL we are loading in PROVISIONAL because of bug #593149, but save session anyway */ if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_COMMITTED || status == WEBKIT_LOAD_FINISHED) ephy_session_save (session, SESSION_CRASHED); } static void notebook_page_added_cb (GtkWidget *notebook, EphyEmbed *embed, guint position, EphySession *session) { g_signal_connect (ephy_embed_get_web_view (embed), "notify::load-status", G_CALLBACK (load_status_notify_cb), session); } static void notebook_page_removed_cb (GtkWidget *notebook, EphyEmbed *embed, guint position, EphySession *session) { ephy_session_save (session, SESSION_CRASHED); g_signal_handlers_disconnect_by_func (ephy_embed_get_web_view (embed), G_CALLBACK (load_status_notify_cb), session); } static void notebook_page_reordered_cb (GtkWidget *notebook, GtkWidget *tab, guint position, EphySession *session) { ephy_session_save (session, SESSION_CRASHED); } static gboolean window_focus_in_event_cb (EphyWindow *window, GdkEventFocus *event, EphySession *session) { LOG ("focus-in-event for window %p", window); g_return_val_if_fail (g_list_find (session->priv->windows, window) != NULL, FALSE); /* move the active window to the front of the list */ session->priv->windows = g_list_remove (session->priv->windows, window); session->priv->windows = g_list_prepend (session->priv->windows, window); g_object_notify (G_OBJECT (session), "active-window"); /* propagate event */ return FALSE; } /* Queue worker */ static void session_command_free (SessionCommand *cmd) { g_assert (cmd != NULL); g_free (cmd->arg); if (cmd->args) { g_strfreev (cmd->args); } g_free (cmd); g_object_unref (ephy_shell_get_default ()); } static int session_command_find (const SessionCommand *cmd, gpointer cmdptr) { EphySessionCommand command = GPOINTER_TO_INT (cmdptr); return command != cmd->command; } static void resume_infobar_response_cb (GtkWidget *info_bar, int response, EphySession *session) { guint32 user_time; EphySessionPrivate *priv = session->priv; LOG ("resume_infobar_response_cb response:%d", response); gtk_widget_hide (info_bar); user_time = gtk_get_current_event_time (); priv->dont_save = FALSE; gtk_widget_destroy (info_bar); priv->resume_infobar = NULL; if (response == GTK_RESPONSE_YES) { ephy_session_queue_command (session, EPHY_SESSION_CMD_LOAD_SESSION, SESSION_CRASHED, NULL, user_time, TRUE); } } static void resume_infobar_weak_ref_cb (EphySession *session, GObject *zombie) { EphySessionPrivate *priv = session->priv; LOG ("resume_window_infobar_weak_ref_cb"); priv->dont_save = FALSE; session_command_queue_next (session); } static void session_command_autoresume (EphySession *session, guint32 user_time) { EphySessionPrivate *priv = session->priv; GFile *saved_session_file; char *saved_session_file_path; gboolean crashed_session; LOG ("ephy_session_autoresume"); saved_session_file = get_session_file (SESSION_CRASHED); saved_session_file_path = g_file_get_path (saved_session_file); g_object_unref (saved_session_file); crashed_session = g_file_test (saved_session_file_path, G_FILE_TEST_EXISTS); g_free (saved_session_file_path); if (crashed_session == FALSE || priv->windows != NULL || priv->tool_windows != NULL) { /* FIXME can this happen? */ if (priv->resume_infobar != NULL) { gtk_widget_hide (priv->resume_infobar); gtk_widget_destroy (priv->resume_infobar); } ephy_session_queue_command (session, EPHY_SESSION_CMD_MAYBE_OPEN_WINDOW, NULL, NULL, user_time, FALSE); return; } if (priv->resume_window) { gtk_window_present_with_time (GTK_WINDOW (priv->resume_window), user_time); return; } ephy_session_queue_command (session, EPHY_SESSION_CMD_MAYBE_OPEN_WINDOW_RESTORE, NULL, NULL, user_time, TRUE); } static void session_command_open_bookmarks_editor (EphySession *session, guint32 user_time) { GtkWidget *editor; editor = ephy_shell_get_bookmarks_editor (ephy_shell_get_default ()); gtk_window_present_with_time (GTK_WINDOW (editor), user_time); } static void session_command_open_uris (EphySession *session, char **uris, const char *options, guint32 user_time) { EphyShell *shell; EphyWindow *window; EphyEmbed *embed; EphySessionPrivate *priv; EphyNewTabFlags flags = 0; guint i; priv = session->priv; shell = ephy_shell_get_default (); g_object_ref (shell); window = ephy_session_get_active_window (session); if (options != NULL && strstr (options, "external") != NULL) { flags |= EPHY_NEW_TAB_FROM_EXTERNAL; } if (options != NULL && strstr (options, "new-window") != NULL) { window = NULL; flags |= EPHY_NEW_TAB_IN_NEW_WINDOW; } else if (options != NULL && strstr (options, "new-tab") != NULL) { flags |= EPHY_NEW_TAB_IN_EXISTING_WINDOW | EPHY_NEW_TAB_JUMP; } for (i = 0; uris[i] != NULL; ++i) { const char *url = uris[i]; EphyNewTabFlags page_flags; WebKitNetworkRequest *request = NULL; if (url[0] == '\0') { page_flags = EPHY_NEW_TAB_HOME_PAGE; } else { page_flags = EPHY_NEW_TAB_OPEN_PAGE; request = webkit_network_request_new (url); } /* For the first URI, if we have a valid recovery * window, reuse the already existing embed instead of * creating a new one, except if we still want to * present the option to resume a crashed session, in * that case use a new tab in the same window */ if (i == 0 && priv->resume_window != NULL) { if (priv->resume_infobar != NULL) { embed = ephy_shell_new_tab_full (shell, EPHY_WINDOW (priv->resume_window), NULL /* parent tab */, request, EPHY_NEW_TAB_IN_EXISTING_WINDOW | page_flags, EPHY_WEB_VIEW_CHROME_ALL, FALSE /* is popup? */, user_time); } else { EphyWebView *web_view; embed = ephy_embed_container_get_active_child (EPHY_EMBED_CONTAINER (priv->resume_window)); web_view = ephy_embed_get_web_view (embed); ephy_web_view_load_url (web_view, url); } } else { embed = ephy_shell_new_tab_full (shell, window, NULL /* parent tab */, request, flags | page_flags, EPHY_WEB_VIEW_CHROME_ALL, FALSE /* is popup? */, user_time); } if (request) g_object_unref (request); window = EPHY_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (embed))); } g_object_unref (shell); } static void send_no_response_cb (GtkButton *button, GtkInfoBar *info_bar) { gtk_info_bar_response (info_bar, GTK_RESPONSE_NO); } static void send_yes_response_cb (GtkButton *button, GtkInfoBar *info_bar) { gtk_info_bar_response (info_bar, GTK_RESPONSE_YES); } static void loading_homepage_cb (EphyWebView *view, EphySession *session) { EphySessionPrivate *priv = session->priv; priv->loading_homepage = TRUE; } static void new_document_now_cb (EphyWebView *view, const char *uri, EphySession *session) { EphySessionPrivate *priv = session->priv; if (priv->loading_homepage) { priv->loading_homepage = FALSE; return; } if (priv->resume_infobar) { EphyEmbed *embed = EPHY_GET_EMBED_FROM_EPHY_WEB_VIEW (view); ephy_embed_remove_top_widget (embed, priv->resume_infobar); priv->resume_infobar = NULL; priv->resume_window = NULL; } } static gboolean session_command_dispatch (EphySession *session) { EphySessionPrivate *priv = session->priv; SessionCommand *cmd; gboolean run_again = TRUE; cmd = g_queue_pop_head (priv->queue); g_assert (cmd != NULL); LOG ("dispatching queue cmd:%d", cmd->command); switch (cmd->command) { case EPHY_SESSION_CMD_RESUME_SESSION: session_command_autoresume (session, cmd->user_time); break; case EPHY_SESSION_CMD_LOAD_SESSION: ephy_session_load (session, cmd->arg, cmd->user_time); break; case EPHY_SESSION_CMD_OPEN_BOOKMARKS_EDITOR: session_command_open_bookmarks_editor (session, cmd->user_time); break; case EPHY_SESSION_CMD_OPEN_URIS: session_command_open_uris (session, cmd->args, cmd->arg, cmd->user_time); break; case EPHY_SESSION_CMD_MAYBE_OPEN_WINDOW: /* FIXME: maybe just check for normal windows? */ if (priv->windows == NULL && priv->tool_windows == NULL) { ephy_shell_new_tab_full (ephy_shell_get_default (), NULL /* window */, NULL /* tab */, NULL /* NetworkRequest */, EPHY_NEW_TAB_IN_NEW_WINDOW | EPHY_NEW_TAB_HOME_PAGE, EPHY_WEB_VIEW_CHROME_ALL, FALSE /* is popup? */, cmd->user_time); } break; case EPHY_SESSION_CMD_MAYBE_OPEN_WINDOW_RESTORE: /* FIXME: maybe just check for normal windows? */ if (priv->windows == NULL && priv->tool_windows == NULL) { GtkWidget *info_bar; GtkWidget *action_area; GtkWidget *button_box; GtkWidget *action_button; GtkWidget *content_area; GtkWidget *label; EphyEmbed *embed; EphyWebView *view; session->priv->dont_save = TRUE; embed = ephy_shell_new_tab_full (ephy_shell_get_default (), NULL /* window */, NULL /* tab */, NULL /* Networ kRequest */, EPHY_NEW_TAB_IN_NEW_WINDOW | EPHY_NEW_TAB_HOME_PAGE, EPHY_WEB_VIEW_CHROME_ALL, FALSE /* is popup? */, cmd->user_time); info_bar = gtk_info_bar_new (); session->priv->resume_infobar = info_bar; session->priv->resume_window = gtk_widget_get_toplevel (GTK_WIDGET (embed)); action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar)); button_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); gtk_container_add (GTK_CONTAINER (action_area), button_box); action_button = gtk_button_new_with_label (_("Don't recover")); g_signal_connect (action_button, "clicked", G_CALLBACK (send_no_response_cb), info_bar); gtk_box_pack_start (GTK_BOX (button_box), action_button, FALSE, FALSE, 0); action_button = gtk_button_new_with_label (_("Recover session")); g_signal_connect (action_button, "clicked", G_CALLBACK (send_yes_response_cb), info_bar); gtk_box_pack_start (GTK_BOX (button_box), action_button, FALSE, FALSE, 0); label = gtk_label_new (_("Do you want to recover the previous browser windows and tabs?")); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar)); gtk_container_add (GTK_CONTAINER (content_area), label); gtk_widget_show_all (info_bar); g_signal_connect (info_bar, "response", G_CALLBACK (resume_infobar_response_cb), session); g_object_weak_ref (G_OBJECT (info_bar), (GWeakNotify) resume_infobar_weak_ref_cb, session); ephy_embed_add_top_widget (embed, info_bar, FALSE); view = ephy_embed_get_web_view (embed); g_signal_connect (view, "loading-homepage", G_CALLBACK (loading_homepage_cb), session); g_signal_connect (view, "new-document-now", G_CALLBACK (new_document_now_cb), session); } break; default: g_assert_not_reached (); break; } /* Look if there's anything else to dispatch */ if (g_queue_is_empty (priv->queue)) { priv->queue_idle_id = 0; run_again = FALSE; } g_application_release (G_APPLICATION (ephy_shell_get_default ())); /* This unrefs the shell! */ session_command_free (cmd); return run_again; } static void session_command_queue_next (EphySession *session) { EphySessionPrivate *priv = session->priv; LOG ("queue_next"); if (!g_queue_is_empty (priv->queue) && priv->resume_infobar == NULL && priv->queue_idle_id == 0) { priv->queue_idle_id = g_idle_add ((GSourceFunc) session_command_dispatch, session); } } static void session_command_queue_clear (EphySession *session) { EphySessionPrivate *priv = session->priv; if (priv->queue_idle_id != 0) { g_source_remove (priv->queue_idle_id); priv->queue_idle_id = 0; } if (priv->queue != NULL) { g_queue_foreach (priv->queue, (GFunc) session_command_free, NULL); g_queue_free (priv->queue); priv->queue = NULL; } } /* EphyExtensionIface implementation */ static void impl_attach_window (EphyExtension *extension, EphyWindow *window) { EphySession *session = EPHY_SESSION (extension); GtkWidget *notebook; LOG ("impl_attach_window"); session->priv->windows = g_list_append (session->priv->windows, window); ephy_session_save (session, SESSION_CRASHED); g_signal_connect (window, "focus-in-event", G_CALLBACK (window_focus_in_event_cb), session); notebook = ephy_window_get_notebook (window); g_signal_connect (notebook, "page-added", G_CALLBACK (notebook_page_added_cb), session); g_signal_connect (notebook, "page-removed", G_CALLBACK (notebook_page_removed_cb), session); g_signal_connect (notebook, "page-reordered", G_CALLBACK (notebook_page_reordered_cb), session); /* Set unique identifier as role, so that on restore, the WM can * place the window on the right workspace */ if (gtk_window_get_role (GTK_WINDOW (window)) == NULL) { /* I guess rand() is unique enough, otherwise we could use * time + pid or something */ char *role; role = g_strdup_printf ("epiphany-window-%x", rand()); gtk_window_set_role (GTK_WINDOW (window), role); g_free (role); } } static void impl_detach_window (EphyExtension *extension, EphyWindow *window) { EphySession *session = EPHY_SESSION (extension); LOG ("impl_detach_window"); session->priv->windows = g_list_remove (session->priv->windows, window); ephy_session_save (session, SESSION_CRASHED); /* NOTE: since the window will be destroyed anyway, we don't need to * disconnect our signal handlers from its components. */ } /* Class implementation */ static void ephy_session_init (EphySession *session) { EphySessionPrivate *priv; EggSMClient *sm_client; LOG ("EphySession initialising"); priv = session->priv = EPHY_SESSION_GET_PRIVATE (session); priv->queue = g_queue_new (); sm_client = egg_sm_client_get (); g_signal_connect (sm_client, "save-state", G_CALLBACK (client_save_state_cb), session); g_signal_connect (sm_client, "quit-requested", G_CALLBACK (client_quit_requested_cb), session); g_signal_connect (sm_client, "quit-cancelled", G_CALLBACK (client_quit_cancelled_cb), session); g_signal_connect (sm_client, "quit", G_CALLBACK (client_quit_cb), session); } static void ephy_session_dispose (GObject *object) { EphySession *session = EPHY_SESSION (object); EphySessionPrivate *priv = session->priv; EggSMClient *sm_client; LOG ("EphySession disposing"); /* Only remove the crashed session if we're not shutting down while * the session resume dialogue was still shown! */ if (priv->quit_while_resuming == FALSE) { session_delete (session, SESSION_CRASHED); } session_command_queue_clear (session); sm_client = egg_sm_client_get (); g_signal_handlers_disconnect_matched (sm_client, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, session); G_OBJECT_CLASS (ephy_session_parent_class)->dispose (object); } static void ephy_session_finalize (GObject *object) { EphySession *session = EPHY_SESSION (object); LOG ("EphySession finalising"); /* FIXME: those should be NULL already!? */ g_list_free (session->priv->windows); g_list_free (session->priv->tool_windows); G_OBJECT_CLASS (ephy_session_parent_class)->finalize (object); } static void ephy_session_iface_init (EphyExtensionIface *iface) { iface->attach_window = impl_attach_window; iface->detach_window = impl_detach_window; } static void ephy_session_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { /* no writeable properties */ g_return_if_reached (); } static void ephy_session_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { EphySession *session = EPHY_SESSION (object); switch (prop_id) { case PROP_ACTIVE_WINDOW: g_value_set_object (value, ephy_session_get_active_window (session)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void ephy_session_class_init (EphySessionClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); object_class->dispose = ephy_session_dispose; object_class->finalize = ephy_session_finalize; object_class->get_property = ephy_session_get_property; object_class->set_property = ephy_session_set_property; g_object_class_install_property (object_class, PROP_ACTIVE_WINDOW, g_param_spec_object ("active-window", "Active Window", "The active window", EPHY_TYPE_WINDOW, G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); g_type_class_add_private (object_class, sizeof (EphySessionPrivate)); } /* Implementation */ static void close_dialog (GtkWidget *widget) { if (GTK_IS_DIALOG (widget)) { /* don't destroy them, someone might have a ref on them */ gtk_dialog_response (GTK_DIALOG (widget), GTK_RESPONSE_DELETE_EVENT); } } void ephy_session_close (EphySession *session) { EphySessionPrivate *priv = session->priv; GList *windows; LOG ("ephy_session_close"); /* we have to ref the shell or else we may get finalised between * destroying the windows and destroying the tool windows */ g_object_ref (ephy_shell_get_default ()); priv->dont_save = TRUE; /* need to set this up here while the dialogue hasn't been killed yet */ priv->quit_while_resuming = priv->resume_window != NULL; /* Clear command queue */ session_command_queue_clear (session); ephy_embed_shell_prepare_close (embed_shell); /* there may still be windows open, like dialogues posed from * web pages, etc. Try to kill them, but be sure NOT to destroy * the gtkmozembed offscreen window! * Here, we just check if it's a dialogue and close it if it is one. */ windows = gtk_window_list_toplevels (); g_list_foreach (windows, (GFunc) close_dialog, NULL); g_list_free (windows); windows = ephy_session_get_windows (session); g_list_foreach (windows, (GFunc) gtk_widget_destroy, NULL); g_list_free (windows); windows = g_list_copy (session->priv->tool_windows); g_list_foreach (windows, (GFunc) gtk_widget_destroy, NULL); g_list_free (windows); ephy_embed_shell_prepare_close (embed_shell); /* Just to be really sure, do it again: */ windows = gtk_window_list_toplevels (); g_list_foreach (windows, (GFunc) close_dialog, NULL); g_list_free (windows); session->priv->dont_save = FALSE; /* Clear command queue */ session_command_queue_clear (session); g_object_unref (ephy_shell_get_default ()); } static int write_tab (xmlTextWriterPtr writer, EphyEmbed *embed) { const char *address, *title; int ret; ret = xmlTextWriterStartElement (writer, (xmlChar *) "embed"); if (ret < 0) return ret; address = ephy_web_view_get_address (ephy_embed_get_web_view (embed)); /* Do not store ephy-about: URIs, they are not valid for * loading. */ if (g_str_has_prefix (address, EPHY_ABOUT_SCHEME)) { address = g_strconcat ("about", address + EPHY_ABOUT_SCHEME_LEN, NULL); } ret = xmlTextWriterWriteAttribute (writer, (xmlChar *) "url", (const xmlChar *) address); if (ret < 0) return ret; title = ephy_web_view_get_title (ephy_embed_get_web_view (embed)); ret = xmlTextWriterWriteAttribute (writer, (xmlChar *) "title", (const xmlChar *) title); if (ret < 0) return ret; if (ephy_web_view_is_loading (ephy_embed_get_web_view (embed))) { ret = xmlTextWriterWriteAttribute (writer, (const xmlChar *) "loading", (const xmlChar *) "true"); if (ret < 0) return ret; } ret = xmlTextWriterEndElement (writer); /* embed */ return ret; } static int write_active_tab (xmlTextWriterPtr writer, GtkWidget *notebook) { int ret; int current; current = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook)); ret = xmlTextWriterWriteFormatAttribute (writer, (const xmlChar *) "active-tab", "%d", current); return ret; } static int write_window_geometry (xmlTextWriterPtr writer, GtkWindow *window) { int x = 0, y = 0, width = -1, height = -1; int ret; /* get window geometry */ gtk_window_get_size (window, &width, &height); gtk_window_get_position (window, &x, &y); /* set window properties */ ret = xmlTextWriterWriteFormatAttribute (writer, (const xmlChar *) "x", "%d", x); if (ret < 0) return ret; ret = xmlTextWriterWriteFormatAttribute (writer, (const xmlChar *) "y", "%d", y); if (ret < 0) return ret; ret = xmlTextWriterWriteFormatAttribute (writer, (const xmlChar *) "width", "%d", width); if (ret < 0) return ret; ret = xmlTextWriterWriteFormatAttribute (writer, (const xmlChar *) "height", "%d", height); return ret; } static int write_tool_window (xmlTextWriterPtr writer, GtkWindow *window) { const xmlChar *id; int ret; if (EPHY_IS_BOOKMARKS_EDITOR (window)) { id = (const xmlChar *) BOOKMARKS_EDITOR_ID; } else if (EPHY_IS_HISTORY_WINDOW (window)) { id = (const xmlChar *) HISTORY_WINDOW_ID; } else { g_return_val_if_reached (-1); } ret = xmlTextWriterStartElement (writer, (const xmlChar *) "toolwindow"); if (ret < 0) return ret; ret = xmlTextWriterWriteAttribute (writer, (const xmlChar *) "id", id); if (ret < 0) return ret; ret = write_window_geometry (writer, window); if (ret < 0) return ret; ret = xmlTextWriterEndElement (writer); /* toolwindow */ return ret; } static int write_ephy_window (xmlTextWriterPtr writer, EphyWindow *window) { GList *tabs, *l; GtkWidget *notebook; const char *role; int ret; tabs = ephy_embed_container_get_children (EPHY_EMBED_CONTAINER (window)); notebook = ephy_window_get_notebook (window); /* Do not save an empty EphyWindow. * This only happens when the window was newly opened. */ if (tabs == NULL) return 0; ret = xmlTextWriterStartElement (writer, (xmlChar *) "window"); if (ret < 0) return ret; ret = write_window_geometry (writer, GTK_WINDOW (window)); if (ret < 0) return ret; ret = write_active_tab (writer, notebook); if (ret < 0) return ret; role = gtk_window_get_role (GTK_WINDOW (window)); if (role != NULL) { ret = xmlTextWriterWriteAttribute (writer, (const xmlChar *)"role", (const xmlChar *)role); if (ret < 0) return ret; } for (l = tabs; l != NULL; l = l->next) { EphyEmbed *embed = EPHY_EMBED (l->data); ret = write_tab (writer, embed); if (ret < 0) break; } g_list_free (tabs); if (ret < 0) return ret; ret = xmlTextWriterEndElement (writer); /* window */ return ret; } gboolean ephy_session_save (EphySession *session, const char *filename) { EphySessionPrivate *priv; xmlTextWriterPtr writer; GList *w; GFile *save_to_file, *tmp_file; char *tmp_file_path, *save_to_file_path; int ret; g_return_val_if_fail (EPHY_IS_SESSION (session), FALSE); priv = session->priv; if (priv->dont_save) { return TRUE; } LOG ("ephy_sesion_save %s", filename); if (priv->windows == NULL && priv->tool_windows == NULL) { session_delete (session, filename); return TRUE; } save_to_file = get_session_file (filename); save_to_file_path = g_file_get_path (save_to_file); tmp_file_path = g_strconcat (save_to_file_path, ".tmp", NULL); g_free (save_to_file_path); tmp_file = g_file_new_for_path (tmp_file_path); /* FIXME: do we want to turn on compression? */ writer = xmlNewTextWriterFilename (tmp_file_path, 0); if (writer == NULL) { g_free (tmp_file_path); return FALSE; } ret = xmlTextWriterSetIndent (writer, 1); if (ret < 0) goto out; ret = xmlTextWriterSetIndentString (writer, (const xmlChar *) " "); if (ret < 0) goto out; START_PROFILER ("Saving session") ret = xmlTextWriterStartDocument (writer, "1.0", NULL, NULL); if (ret < 0) goto out; /* create and set the root node for the session */ ret = xmlTextWriterStartElement (writer, (const xmlChar *) "session"); if (ret < 0) goto out; /* iterate through all the windows */ for (w = session->priv->windows; w != NULL && ret >= 0; w = w->next) { ret = write_ephy_window (writer, EPHY_WINDOW (w->data)); } if (ret < 0) goto out; for (w = session->priv->tool_windows; w != NULL && ret >= 0; w = w->next) { ret = write_tool_window (writer, GTK_WINDOW (w->data)); } if (ret < 0) goto out; ret = xmlTextWriterEndElement (writer); /* session */ if (ret < 0) goto out; ret = xmlTextWriterEndDocument (writer); out: xmlFreeTextWriter (writer); if (ret >= 0) { if (ephy_file_switch_temp_file (save_to_file, tmp_file) == FALSE) { ret = -1; } } g_free (tmp_file_path); g_object_unref (save_to_file); g_object_unref (tmp_file); STOP_PROFILER ("Saving session") return ret >= 0 ? TRUE : FALSE; } static void confirm_before_recover (EphyWindow* window, char* url, char* title) { EphyEmbed *embed; embed = ephy_shell_new_tab (ephy_shell, window, NULL, NULL, EPHY_NEW_TAB_IN_EXISTING_WINDOW | EPHY_NEW_TAB_APPEND_LAST); ephy_web_view_load_error_page (ephy_embed_get_web_view (embed), url, EPHY_WEB_VIEW_ERROR_PAGE_CRASH, NULL); } static void parse_embed (xmlNodePtr child, EphyWindow *window, gboolean is_first_window, EphySession *session) { EphySessionPrivate *priv = session->priv; while (child != NULL) { if (strcmp ((char *) child->name, "embed") == 0) { xmlChar *url, *attr; char *recover_url; gboolean was_loading; g_return_if_fail (window != NULL); /* Check if that tab wasn't fully loaded yet when the session crashed */ attr = xmlGetProp (child, (const xmlChar *) "loading"); was_loading = attr != NULL && xmlStrEqual (attr, (const xmlChar *) "true"); xmlFree (attr); url = xmlGetProp (child, (const xmlChar *) "url"); if (url == NULL) continue; /* in the case that crash happens before we receive the URL from the server, this will open an about:blank tab. See http://bugzilla.gnome.org/show_bug.cgi?id=591294 Otherwise, if the web was fully loaded, it is reloaded again. */ if (!was_loading || strcmp ((const char *) url, "about:blank") == 0) { recover_url = (char *) url; /* Reuse the window holding the recovery infobar instead of creating a new one */ if (is_first_window == TRUE && priv->resume_window != NULL) { EphyWebView *web_view; EphyEmbed *embed; embed = ephy_embed_container_get_active_child (EPHY_EMBED_CONTAINER (priv->resume_window)); web_view = ephy_embed_get_web_view (embed); ephy_web_view_load_url(web_view, recover_url); is_first_window = FALSE; } else { ephy_shell_new_tab (ephy_shell, window, NULL, recover_url, EPHY_NEW_TAB_IN_EXISTING_WINDOW | EPHY_NEW_TAB_OPEN_PAGE | EPHY_NEW_TAB_APPEND_LAST); } } else if (was_loading && url != NULL && strcmp ((const char *) url, "about:blank") != 0) { /* shows a message to the user that warns that this page was loading during crash and make Epiphany crash again, in this case we know the URL */ xmlChar* title = xmlGetProp (child, (const xmlChar *) "title"); confirm_before_recover (window, (char*) url, (char*) title); } xmlFree (url); } child = child->next; } } static gboolean int_from_string (const char *string, int *retval) { char *tail = NULL; long int val; gboolean success = FALSE; if (string == NULL) return FALSE; errno = 0; val = strtol (string, &tail, 0); if (errno == 0 && tail != NULL && tail[0] == '\0') { *retval = (int) val; success = TRUE; } return success; } static void restore_geometry (GtkWindow *window, xmlNodePtr node) { xmlChar *tmp; int x = 0, y = 0, width = -1, height = -1; gboolean success = TRUE; g_return_if_fail (window != NULL); tmp = xmlGetProp (node, (xmlChar *) "x"); success &= int_from_string ((char *) tmp, &x); xmlFree (tmp); tmp = xmlGetProp (node, (xmlChar *) "y"); success &= int_from_string ((char *) tmp, &y); xmlFree (tmp); tmp = xmlGetProp (node, (xmlChar *) "width"); success &= int_from_string ((char *) tmp, &width); xmlFree (tmp); tmp = xmlGetProp (node, (xmlChar *) "height"); success &= int_from_string ((char *) tmp, &height); xmlFree (tmp); if (success) { tmp = xmlGetProp (node, (xmlChar *)"role"); if (tmp != NULL) { gtk_window_set_role (GTK_WINDOW (window), (const char *)tmp); xmlFree (tmp); } gtk_window_move (window, x, y); gtk_window_set_default_size (window, width, height); } } /* * ephy_session_load: * @session: a #EphySession * @filename: the path of the source file * @user_time: a user_time, or 0 * * Load a session from disk, restoring the windows and their state * * Return value: TRUE if at least a window has been opened **/ gboolean ephy_session_load (EphySession *session, const char *filename, guint32 user_time) { EphySessionPrivate *priv = session->priv; xmlDocPtr doc; xmlNodePtr child; EphyWindow *window; GtkWidget *widget = NULL; GFile *save_to_file; char *save_to_path; gboolean first_window_created = FALSE; LOG ("ephy_sesion_load %s", filename); save_to_file = get_session_file (filename); save_to_path = g_file_get_path (save_to_file); g_object_unref (save_to_file); doc = xmlParseFile (save_to_path); g_free (save_to_path); if (doc == NULL) { return FALSE; } g_object_ref (ephy_shell_get_default ()); priv->dont_save = TRUE; child = xmlDocGetRootElement (doc); /* skip the session node */ child = child->children; while (child != NULL) { if (xmlStrEqual (child->name, (const xmlChar *) "window")) { xmlChar *tmp; EphyEmbed *active_child; if (first_window_created == FALSE && priv->resume_window != NULL) { window = EPHY_WINDOW (priv->resume_window); first_window_created = TRUE; } else window = ephy_window_new (); widget = GTK_WIDGET (window); restore_geometry (GTK_WINDOW (widget), child); ephy_gui_window_update_user_time (widget, user_time); /* Now add the tabs */ parse_embed (child->children, window, window == EPHY_WINDOW (priv->resume_window), session); /* Set focus to something sane */ tmp = xmlGetProp (child, (xmlChar *) "active-tab"); if (tmp != NULL) { gboolean success; int active_tab; success = int_from_string ((char *) tmp, &active_tab); xmlFree (tmp); if (success) { GtkWidget *notebook; notebook = ephy_window_get_notebook (window); gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), active_tab); } } active_child = ephy_embed_container_get_active_child (EPHY_EMBED_CONTAINER (window)); gtk_widget_grab_focus (GTK_WIDGET (active_child)); gtk_widget_show (widget); } else if (xmlStrEqual (child->name, (const xmlChar *) "toolwindow")) { xmlChar *id; id = xmlGetProp (child, (const xmlChar *) "id"); if (id && xmlStrEqual ((const xmlChar *) BOOKMARKS_EDITOR_ID, id)) { if (!g_settings_get_boolean (EPHY_SETTINGS_LOCKDOWN, EPHY_PREFS_LOCKDOWN_BOOKMARK_EDITING)) { widget = ephy_shell_get_bookmarks_editor (ephy_shell); } } else if (id && xmlStrEqual ((const xmlChar *) HISTORY_WINDOW_ID, id)) { if (!g_settings_get_boolean (EPHY_SETTINGS_LOCKDOWN, EPHY_PREFS_LOCKDOWN_HISTORY)) { widget = ephy_shell_get_history_window (ephy_shell); } } restore_geometry (GTK_WINDOW (widget), child); ephy_gui_window_update_user_time (widget, user_time); gtk_widget_show (widget); } child = child->next; } xmlFreeDoc (doc); priv->dont_save = FALSE; priv->resume_window = NULL; ephy_session_save (session, SESSION_CRASHED); g_object_unref (ephy_shell_get_default ()); return (priv->windows != NULL || priv->tool_windows != NULL); } /** * ephy_session_get_windows: * @session: the #EphySession * * Returns: (element-type EphyWindow) (transfer container): the list of * open #EphyWindow:s. **/ GList * ephy_session_get_windows (EphySession *session) { g_return_val_if_fail (EPHY_IS_SESSION (session), NULL); return g_list_copy (session->priv->windows); } /** * ephy_session_add_window: * @session: a #EphySession * @window: a #EphyWindow * * Add a tool window to the session. #EphyWindow take care of adding * itself to session. **/ void ephy_session_add_window (EphySession *session, GtkWindow *window) { LOG ("ephy_session_add_window %p", window); session->priv->tool_windows = g_list_append (session->priv->tool_windows, window); gtk_application_add_window (GTK_APPLICATION (ephy_shell_get_default ()), window); ephy_session_save (session, SESSION_CRASHED); } /** * ephy_session_remove_window: * @session: a #EphySession. * @window: a #GtkWindow, which must be either the bookmarks editor or the * history window. * * Remove a tool window from the session. **/ void ephy_session_remove_window (EphySession *session, GtkWindow *window) { LOG ("ephy_session_remove_window %p", window); session->priv->tool_windows = g_list_remove (session->priv->tool_windows, window); gtk_application_remove_window (GTK_APPLICATION (ephy_shell_get_default ()), window); ephy_session_save (session, SESSION_CRASHED); } /** * ephy_session_get_active_window: * @session: a #EphySession * * Get the current active browser window. Use it when you * need to take an action (like opening an url) on * a window but you dont have a target window. * * Return value: (transfer none): the current active non-popup browser * window, or NULL of there is none. **/ EphyWindow * ephy_session_get_active_window (EphySession *session) { EphyWindow *window = NULL; EphyEmbedContainer *w; GList *l; g_return_val_if_fail (EPHY_IS_SESSION (session), NULL); for (l = session->priv->windows; l != NULL; l = l->next) { w = EPHY_EMBED_CONTAINER (l->data); if (ephy_embed_container_get_is_popup (w) == FALSE) { window = EPHY_WINDOW (w); break; } } return window; } /** * ephy_session_queue_command: * @session: a #EphySession **/ void ephy_session_queue_command (EphySession *session, EphySessionCommand command, const char *arg, const char **args, guint32 user_time, gboolean priority) { EphySessionPrivate *priv; GList *element; SessionCommand *cmd; LOG ("queue_command command:%d", command); g_return_if_fail (EPHY_IS_SESSION (session)); g_return_if_fail (command != EPHY_SESSION_CMD_OPEN_URIS || args != NULL); priv = session->priv; /* First look if the same command is already queued */ if (command > EPHY_SESSION_CMD_RESUME_SESSION && command < EPHY_SESSION_CMD_OPEN_URIS) { element = g_queue_find_custom (priv->queue, GINT_TO_POINTER (command), (GCompareFunc) session_command_find); if (element != NULL) { cmd = (SessionCommand *) element->data; if ((command == EPHY_SESSION_CMD_LOAD_SESSION && strcmp (cmd->arg, arg) == 0) || command == EPHY_SESSION_CMD_OPEN_BOOKMARKS_EDITOR || command == EPHY_SESSION_CMD_RESUME_SESSION) { cmd->user_time = user_time; g_queue_remove (priv->queue, cmd); g_queue_push_tail (priv->queue, cmd); return; } } } /* FIXME: use g_slice_new */ cmd = g_new0 (SessionCommand, 1); cmd->command = command; cmd->arg = arg ? g_strdup (arg) : NULL; cmd->args = args ? g_strdupv ((gchar **)args) : NULL; cmd->user_time = user_time; /* This ref is released in session_command_free */ g_object_ref (ephy_shell_get_default ()); if (priority) { g_queue_push_head (priv->queue, cmd); } else { g_queue_push_tail (priv->queue, cmd); } session_command_queue_next (session); g_application_hold (G_APPLICATION (ephy_shell_get_default ())); if (priv->resume_window != NULL) { gtk_window_present_with_time (GTK_WINDOW (priv->resume_window), user_time); } }