/* -*- 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 * Copyright © 2012 Igalia S.L. * * 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 "ephy-about-handler.h" #include "ephy-debug.h" #include "ephy-embed-container.h" #include "ephy-embed-utils.h" #include "ephy-embed.h" #include "ephy-file-helpers.h" #include "ephy-gui.h" #include "ephy-notebook.h" #include "ephy-prefs.h" #include "ephy-private.h" #include "ephy-settings.h" #include "ephy-shell.h" #include "ephy-string.h" #include "ephy-window.h" #include #include #include #include #define EPHY_SESSION_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_SESSION, EphySessionPrivate)) typedef struct { gpointer* parent_location; int position; char *url; GList *bflist; } ClosedTab; struct _EphySessionPrivate { GQueue *closed_tabs; GCancellable *save_cancellable; guint dont_save : 1; }; #define SESSION_STATE "type:session_state" #define MAX_CLOSED_TABS 10 enum { PROP_0, PROP_CAN_UNDO_TAB_CLOSED }; G_DEFINE_TYPE (EphySession, ephy_session, G_TYPE_OBJECT) /* Helper functions */ static GFile * get_session_file (const char *filename) { GFile *file; char *path; if (filename == NULL) { return NULL; } if (strcmp (filename, SESSION_STATE) == 0) { path = g_build_filename (ephy_dot_dir (), "session_state.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); } #ifdef HAVE_WEBKIT2 static void load_changed_cb (WebKitWebView *view, WebKitLoadEvent load_event, EphySession *session) { if (!ephy_web_view_load_failed (EPHY_WEB_VIEW (view))) ephy_session_save (session, SESSION_STATE); } #else 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_STATE); } #endif static gpointer * parent_location_new (EphyNotebook *notebook) { gpointer *location = g_slice_new (gpointer); *location = notebook; g_object_add_weak_pointer (G_OBJECT (notebook), location); return location; } static void parent_location_free (gpointer *location, gboolean last_reference) { if (!location) return; if (*location && last_reference) { g_object_remove_weak_pointer (G_OBJECT (*location), location); } g_slice_free (gpointer, location); } static void closed_tab_free (ClosedTab *tab) { if (tab->bflist) { g_list_free_full (tab->bflist, g_object_unref); tab->bflist = NULL; } if (tab->url) { g_free (tab->url); tab->url = NULL; } g_slice_free (ClosedTab, tab); } static int compare_func (ClosedTab *iter, EphyNotebook *notebook) { return (EphyNotebook *)*iter->parent_location - notebook; } static ClosedTab * find_tab_with_notebook (GQueue *queue, EphyNotebook *notebook) { GList *item = g_queue_find_custom (queue, notebook, (GCompareFunc)compare_func); return item ? (ClosedTab*)item->data : NULL; } static ClosedTab * closed_tab_new (GQueue *closed_tabs, const char *address, GList *bflist, int position, EphyNotebook *parent_notebook) { ClosedTab *tab = g_slice_new0 (ClosedTab); ClosedTab *sibling_tab; tab->url = g_strdup (address); #ifndef HAVE_WEBKIT2 tab->bflist = g_list_copy_deep (bflist, (GCopyFunc)webkit_web_history_item_copy, NULL); #endif tab->position = position; sibling_tab = find_tab_with_notebook (closed_tabs, parent_notebook); if (sibling_tab) tab->parent_location = sibling_tab->parent_location; else tab->parent_location = parent_location_new (parent_notebook); return tab; } static void post_restore_cleanup (GQueue *closed_tabs, ClosedTab *restored_tab, gboolean notebook_is_new) { if (find_tab_with_notebook (closed_tabs, *restored_tab->parent_location)) { if (notebook_is_new == TRUE) { /* If this is a newly opened notebook and there are other tabs that must be restored here, add a weak poiner to keep track of the lifetime of it. */ g_object_add_weak_pointer (G_OBJECT (*restored_tab->parent_location), restored_tab->parent_location); } } else { /* If there are no other tabs that must be restored to this notebook, we can remove the pointer keeping track of its location. If this is a new window, we don't need to remove any weak pointer, as no one has been added yet. */ parent_location_free (restored_tab->parent_location, !notebook_is_new); } } void ephy_session_undo_close_tab (EphySession *session) { EphySessionPrivate *priv; EphyEmbed *embed, *new_tab; ClosedTab *tab; #ifndef HAVE_WEBKIT2 WebKitWebBackForwardList *dest; GList *i; #endif EphyNewTabFlags flags = EPHY_NEW_TAB_OPEN_PAGE | EPHY_NEW_TAB_PRESENT_WINDOW | EPHY_NEW_TAB_JUMP | EPHY_NEW_TAB_DONT_COPY_HISTORY; g_return_if_fail (EPHY_IS_SESSION (session)); priv = session->priv; tab = g_queue_pop_head (priv->closed_tabs); if (tab == NULL) return; LOG ("UNDO CLOSE TAB: %s", tab->url); if (*tab->parent_location != NULL) { GtkWidget *window; flags |= EPHY_NEW_TAB_IN_EXISTING_WINDOW; if (tab->position > 0) { /* Append in the n-th position. */ embed = EPHY_EMBED (gtk_notebook_get_nth_page (GTK_NOTEBOOK (*tab->parent_location), tab->position - 1)); flags |= EPHY_NEW_TAB_APPEND_AFTER; } else { /* Just prepend in the first position. */ embed = NULL; flags |= EPHY_NEW_TAB_FIRST; } window = gtk_widget_get_toplevel (GTK_WIDGET (*tab->parent_location)); new_tab = ephy_shell_new_tab (ephy_shell_get_default (), EPHY_WINDOW (window), embed, tab->url, flags); post_restore_cleanup (priv->closed_tabs, tab, FALSE); } else { EphyNotebook *notebook; flags |= EPHY_NEW_TAB_IN_NEW_WINDOW; new_tab = ephy_shell_new_tab (ephy_shell_get_default (), NULL, NULL, tab->url, flags); /* FIXME: This makes the assumption that the notebook is the parent of the returned EphyEmbed. */ notebook = EPHY_NOTEBOOK (gtk_widget_get_parent (GTK_WIDGET (new_tab))); *tab->parent_location = notebook; post_restore_cleanup (priv->closed_tabs, tab, TRUE); } /* This is deficient: we need to recreate the whole * BackForward list. Also, WebKit2 doesn't have this API. */ #ifndef HAVE_WEBKIT2 dest = webkit_web_view_get_back_forward_list (EPHY_GET_WEBKIT_WEB_VIEW_FROM_EMBED (new_tab)); for (i = tab->bflist; i; i = i->next) { LOG ("ADDING TO BF: %s", webkit_web_history_item_get_title ((WebKitWebHistoryItem*) i->data)); webkit_web_back_forward_list_add_item (dest, webkit_web_history_item_copy ((WebKitWebHistoryItem*) i->data)); } #endif closed_tab_free (tab); if (g_queue_is_empty (priv->closed_tabs)) g_object_notify (G_OBJECT (session), "can-undo-tab-closed"); } static void ephy_session_tab_closed (EphySession *session, EphyNotebook *notebook, EphyEmbed *embed, gint position) { EphySessionPrivate *priv = session->priv; EphyWebView *view; const char *address; #ifdef HAVE_WEBKIT2 WebKitBackForwardList *source; #else WebKitWebBackForwardList *source; #endif ClosedTab *tab; GList *items = NULL; view = ephy_embed_get_web_view (embed); address = ephy_web_view_get_address (view); source = webkit_web_view_get_back_forward_list (WEBKIT_WEB_VIEW (view)); #ifdef HAVE_WEBKIT2 items = webkit_back_forward_list_get_back_list_with_limit (source, EPHY_WEBKIT_BACK_FORWARD_LIMIT); #else items = webkit_web_back_forward_list_get_back_list_with_limit (source, EPHY_WEBKIT_BACK_FORWARD_LIMIT); #endif if (items == NULL && g_strcmp0 (address, "ephy-about:overview") == 0) return; if (g_queue_get_length (priv->closed_tabs) == MAX_CLOSED_TABS) { tab = g_queue_pop_tail (priv->closed_tabs); if (tab->parent_location && !find_tab_with_notebook (priv->closed_tabs, *tab->parent_location)) { parent_location_free (tab->parent_location, TRUE); } closed_tab_free (tab); tab = NULL; } items = g_list_reverse (items); tab = closed_tab_new (priv->closed_tabs, address, items, position, notebook); g_list_free (items); g_queue_push_head (priv->closed_tabs, tab); if (g_queue_get_length (priv->closed_tabs) == 1) g_object_notify (G_OBJECT (session), "can-undo-tab-closed"); LOG ("Added: %s to the list (%d elements)", address, g_queue_get_length (priv->closed_tabs)); } gboolean ephy_session_get_can_undo_tab_closed (EphySession *session) { g_return_val_if_fail (EPHY_IS_SESSION (session), FALSE); return g_queue_is_empty (session->priv->closed_tabs) == FALSE; } static void notebook_page_added_cb (GtkWidget *notebook, EphyEmbed *embed, guint position, EphySession *session) { #ifdef HAVE_WEBKIT2 g_signal_connect (ephy_embed_get_web_view (embed), "load-changed", G_CALLBACK (load_changed_cb), session); #else g_signal_connect (ephy_embed_get_web_view (embed), "notify::load-status", G_CALLBACK (load_status_notify_cb), session); #endif } static void notebook_page_removed_cb (GtkWidget *notebook, EphyEmbed *embed, guint position, EphySession *session) { ephy_session_save (session, SESSION_STATE); #ifdef HAVE_WEBKIT2 g_signal_handlers_disconnect_by_func (ephy_embed_get_web_view (embed), G_CALLBACK (load_changed_cb), session); #else g_signal_handlers_disconnect_by_func (ephy_embed_get_web_view (embed), G_CALLBACK (load_status_notify_cb), session); #endif ephy_session_tab_closed (session, EPHY_NOTEBOOK (notebook), embed, position); } static void notebook_page_reordered_cb (GtkWidget *notebook, GtkWidget *tab, guint position, EphySession *session) { ephy_session_save (session, SESSION_STATE); } static void session_maybe_open_window (EphySession *session, guint32 user_time) { EphyShell *shell = ephy_shell_get_default (); /* FIXME: maybe just check for normal windows? */ if (ephy_shell_get_n_windows (shell) == 0) { ephy_shell_new_tab_full (shell, 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? */, user_time); } } static void window_added_cb (GtkApplication *application, GtkWindow *window, EphySession *session) { GtkWidget *notebook; EphyWindow *ephy_window; ephy_session_save (session, SESSION_STATE); if (!EPHY_IS_WINDOW (window)) return; ephy_window = EPHY_WINDOW (window); notebook = ephy_window_get_notebook (ephy_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 (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 (window, role); g_free (role); } } static void window_removed_cb (GtkApplication *application, GtkWindow *window, EphySession *session) { ephy_session_save (session, SESSION_STATE); /* 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) { EphyShell *shell; LOG ("EphySession initialising"); session->priv = EPHY_SESSION_GET_PRIVATE (session); session->priv->closed_tabs = g_queue_new (); shell = ephy_shell_get_default (); g_signal_connect (shell, "window-added", G_CALLBACK (window_added_cb), session); g_signal_connect (shell, "window-removed", G_CALLBACK (window_removed_cb), session); } static void ephy_session_dispose (GObject *object) { EphySession *session = EPHY_SESSION (object); LOG ("EphySession disposing"); g_queue_free_full (session->priv->closed_tabs, (GDestroyNotify)closed_tab_free); G_OBJECT_CLASS (ephy_session_parent_class)->dispose (object); } static void ephy_session_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { EphySession *session = EPHY_SESSION (object); switch (property_id) { case PROP_CAN_UNDO_TAB_CLOSED: g_value_set_boolean (value, ephy_session_get_can_undo_tab_closed (session)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_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->get_property = ephy_session_get_property; g_object_class_install_property (object_class, PROP_CAN_UNDO_TAB_CLOSED, g_param_spec_boolean ("can-undo-tab-closed", "Can undo tab close", "Session can undo a tab closure", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_type_class_add_private (object_class, sizeof (EphySessionPrivate)); } /* Implementation */ void ephy_session_close (EphySession *session) { EphyPrefsRestoreSessionPolicy policy; LOG ("ephy_session_close"); policy = g_settings_get_enum (EPHY_SETTINGS_MAIN, EPHY_PREFS_RESTORE_SESSION_POLICY); if (policy == EPHY_PREFS_RESTORE_SESSION_POLICY_ALWAYS) { EphySessionPrivate *priv = session->priv; priv->dont_save = TRUE; ephy_embed_shell_prepare_close (ephy_embed_shell_get_default ()); } } static void get_window_geometry (GtkWindow *window, GdkRectangle *rectangle) { gtk_window_get_size (window, &rectangle->width, &rectangle->height); gtk_window_get_position (window, &rectangle->x, &rectangle->y); } typedef struct { char *url; char *title; gboolean loading; } SessionTab; static SessionTab * session_tab_new (EphyEmbed *embed) { SessionTab *session_tab; const char *address; EphyWebView *web_view = ephy_embed_get_web_view (embed); session_tab = g_slice_new (SessionTab); address = ephy_web_view_get_address (web_view); /* Do not store ephy-about: URIs, they are not valid for loading. */ if (g_str_has_prefix (address, EPHY_ABOUT_SCHEME)) { session_tab->url = g_strconcat ("about", address + EPHY_ABOUT_SCHEME_LEN, NULL); } else { session_tab->url = g_strdup (address); } session_tab->title = g_strdup (ephy_web_view_get_title (web_view)); session_tab->loading = ephy_web_view_is_loading (web_view); return session_tab; } static void session_tab_free (SessionTab *tab) { g_free (tab->url); g_free (tab->title); g_slice_free (SessionTab, tab); } typedef struct { GdkRectangle geometry; char *role; GList *tabs; gint active_tab; } SessionWindow; static SessionWindow * session_window_new (EphyWindow *window) { SessionWindow *session_window; GList *tabs, *l; GtkNotebook *notebook; tabs = ephy_embed_container_get_children (EPHY_EMBED_CONTAINER (window)); /* Do not save an empty EphyWindow. * This only happens when the window was newly opened. */ if (!tabs) { return NULL; } session_window = g_slice_new0 (SessionWindow); get_window_geometry (GTK_WINDOW (window), &session_window->geometry); session_window->role = g_strdup (gtk_window_get_role (GTK_WINDOW (window))); for (l = tabs; l != NULL; l = l->next) { SessionTab *tab; tab = session_tab_new (EPHY_EMBED (l->data)); session_window->tabs = g_list_prepend (session_window->tabs, tab); } g_list_free (tabs); session_window->tabs = g_list_reverse (session_window->tabs); notebook = GTK_NOTEBOOK (ephy_window_get_notebook (window)); session_window->active_tab = gtk_notebook_get_current_page (notebook); return session_window; } static void session_window_free (SessionWindow *session_window) { g_free (session_window->role); g_list_free_full (session_window->tabs, (GDestroyNotify)session_tab_free); g_slice_free (SessionWindow, session_window); } typedef struct { EphySession *session; GFile *save_file; GList *windows; } SaveData; static SaveData * save_data_new (EphySession *session, const char *filename) { SaveData *data; EphyShell *shell = ephy_shell_get_default (); GList *windows, *w; data = g_slice_new0 (SaveData); data->session = g_object_ref (session); data->save_file = get_session_file (filename); windows = gtk_application_get_windows (GTK_APPLICATION (shell)); for (w = windows; w != NULL ; w = w->next) { SessionWindow *session_window; session_window = session_window_new (EPHY_WINDOW (w->data)); if (session_window) data->windows = g_list_prepend (data->windows, session_window); } data->windows = g_list_reverse (data->windows); return data; } static void save_data_free (SaveData *data) { g_list_free_full (data->windows, (GDestroyNotify)session_window_free); g_object_unref (data->save_file); g_object_unref (data->session); } static int write_tab (xmlTextWriterPtr writer, SessionTab *tab) { int ret; ret = xmlTextWriterStartElement (writer, (xmlChar *) "embed"); if (ret < 0) return ret; ret = xmlTextWriterWriteAttribute (writer, (xmlChar *) "url", (const xmlChar *) tab->url); if (ret < 0) return ret; ret = xmlTextWriterWriteAttribute (writer, (xmlChar *) "title", (const xmlChar *) tab->title); if (ret < 0) return ret; if (tab->loading) { ret = xmlTextWriterWriteAttribute (writer, (const xmlChar *) "loading", (const xmlChar *) "true"); if (ret < 0) return ret; } ret = xmlTextWriterEndElement (writer); /* embed */ return ret; } static int write_window_geometry (xmlTextWriterPtr writer, GdkRectangle *geometry) { int ret; /* set window properties */ ret = xmlTextWriterWriteFormatAttribute (writer, (const xmlChar *) "x", "%d", geometry->x); if (ret < 0) return ret; ret = xmlTextWriterWriteFormatAttribute (writer, (const xmlChar *) "y", "%d", geometry->y); if (ret < 0) return ret; ret = xmlTextWriterWriteFormatAttribute (writer, (const xmlChar *) "width", "%d", geometry->width); if (ret < 0) return ret; ret = xmlTextWriterWriteFormatAttribute (writer, (const xmlChar *) "height", "%d", geometry->height); return ret; } static int write_ephy_window (xmlTextWriterPtr writer, SessionWindow *window) { GList *l; int ret; ret = xmlTextWriterStartElement (writer, (xmlChar *) "window"); if (ret < 0) return ret; ret = write_window_geometry (writer, &window->geometry); if (ret < 0) return ret; ret = xmlTextWriterWriteFormatAttribute (writer, (const xmlChar *) "active-tab", "%d", window->active_tab); if (ret < 0) return ret; if (window->role != NULL) { ret = xmlTextWriterWriteAttribute (writer, (const xmlChar *) "role", (const xmlChar *) window->role); if (ret < 0) return ret; } for (l = window->tabs; l != NULL; l = l->next) { SessionTab *tab = (SessionTab *) l->data; ret = write_tab (writer, tab); if (ret < 0) break; } if (ret < 0) return ret; ret = xmlTextWriterEndElement (writer); /* window */ return ret; } static void save_session_in_thread_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { g_application_release (G_APPLICATION (ephy_shell_get_default ())); } static void save_session_sync (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { SaveData *data = (SaveData *)g_task_get_task_data (task); xmlBufferPtr buffer; xmlTextWriterPtr writer; GList *w; int ret = -1; buffer = xmlBufferCreate (); writer = xmlNewTextWriterMemory (buffer, 0); if (writer == NULL) goto out; 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 = data->windows; w != NULL && ret >= 0; w = w->next) { ret = write_ephy_window (writer, (SessionWindow *) w->data); } if (ret < 0) goto out; ret = xmlTextWriterEndElement (writer); /* session */ if (ret < 0) goto out; ret = xmlTextWriterEndDocument (writer); out: if (writer) xmlFreeTextWriter (writer); if (ret >= 0 && !g_cancellable_is_cancelled (cancellable)) { GError *error = NULL; if (!g_file_replace_contents (data->save_file, (const char *)buffer->content, buffer->use, NULL, TRUE, 0, NULL, cancellable, &error)) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning ("Error saving session: %s", error->message); } g_error_free (error); } } xmlBufferFree (buffer); g_task_return_boolean (task, TRUE); STOP_PROFILER ("Saving session") } void ephy_session_save (EphySession *session, const char *filename) { EphySessionPrivate *priv; EphyShell *shell; SaveData *data; GTask *task; g_return_if_fail (EPHY_IS_SESSION (session)); priv = session->priv; if (priv->save_cancellable) { g_cancellable_cancel (priv->save_cancellable); g_object_unref (priv->save_cancellable); priv->save_cancellable = NULL; } if (priv->dont_save) { return; } LOG ("ephy_sesion_save %s", filename); shell = ephy_shell_get_default (); if (ephy_shell_get_n_windows (shell) == 0) { session_delete (session, filename); return; } priv->save_cancellable = g_cancellable_new (); data = save_data_new (session, filename); g_application_hold (G_APPLICATION (shell)); task = g_task_new (session, priv->save_cancellable, save_session_in_thread_cb, NULL); g_task_set_task_data (task, data, (GDestroyNotify)save_data_free); g_task_run_in_thread (task, save_session_sync); } static void confirm_before_recover (EphyWindow *window, const char *url, const char *title) { EphyEmbed *embed; embed = ephy_shell_new_tab (ephy_shell_get_default (), 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 restore_geometry (GtkWindow *window, GdkRectangle *geometry) { if (geometry->x >= 0 && geometry->y >= 0) { gtk_window_move (window, geometry->x, geometry->y); } if (geometry->width > 0 && geometry->height > 0) { gtk_window_set_default_size (window, geometry->width, geometry->height); } } typedef struct { EphySession *session; guint32 user_time; EphyWindow *window; gboolean is_first_window; gint active_tab; gboolean is_first_tab; } SessionParserContext; static SessionParserContext * session_parser_context_new (EphySession *session, guint32 user_time) { SessionParserContext *context; context = g_slice_new0 (SessionParserContext); context->session = g_object_ref (session); context->user_time = user_time; context->is_first_window = TRUE; return context; } static void session_parser_context_free (SessionParserContext *context) { g_object_unref (context->session); g_slice_free (SessionParserContext, context); } static void session_parse_window (SessionParserContext *context, const gchar **names, const gchar **values) { GdkRectangle geometry = { -1, -1, 0, 0 }; guint i; context->window = ephy_window_new (); for (i = 0; names[i]; i++) { gulong int_value; if (strcmp (names[i], "x") == 0) { ephy_string_to_int (values[i], &int_value); geometry.x = int_value; } else if (strcmp (names[i], "y") == 0) { ephy_string_to_int (values[i], &int_value); geometry.y = int_value; } else if (strcmp (names[i], "width") == 0) { ephy_string_to_int (values[i], &int_value); geometry.width = int_value; } else if (strcmp (names[i], "height") == 0) { ephy_string_to_int (values[i], &int_value); geometry.height = int_value; } else if (strcmp (names[i], "role") == 0) { gtk_window_set_role (GTK_WINDOW (context->window), values[i]); } else if (strcmp (names[i], "active-tab") == 0) { ephy_string_to_int (values[i], &int_value); context->active_tab = int_value; } } restore_geometry (GTK_WINDOW (context->window), &geometry); ephy_gui_window_update_user_time (GTK_WIDGET (context->window), context->user_time); } static void session_parse_embed (SessionParserContext *context, const gchar **names, const gchar **values) { const char *url = NULL; const char *title = NULL; gboolean was_loading = FALSE; gboolean is_blank_page = FALSE; guint i; for (i = 0; names[i]; i++) { if (strcmp (names[i], "url") == 0) { url = values[i]; is_blank_page = (strcmp (url, "about:blank") == 0 || strcmp (url, "about:overview") == 0); } else if (strcmp (names[i], "title") == 0) { title = values[i]; } else if (strcmp (names[i], "loading") == 0) { was_loading = strcmp (values[i], "true") == 0; } } /* 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 || is_blank_page) { ephy_shell_new_tab (ephy_shell_get_default (), context->window, NULL, url, EPHY_NEW_TAB_IN_EXISTING_WINDOW | EPHY_NEW_TAB_OPEN_PAGE | EPHY_NEW_TAB_APPEND_LAST); } else if (was_loading && url != NULL) { /* 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. */ confirm_before_recover (context->window, url, title); } } static void session_start_element (GMarkupParseContext *ctx, const gchar *element_name, const gchar **names, const gchar **values, gpointer user_data, GError **error) { SessionParserContext *context = (SessionParserContext *)user_data; if (strcmp (element_name, "window") == 0) { session_parse_window (context, names, values); context->is_first_tab = TRUE; } else if (strcmp (element_name, "embed") == 0) { session_parse_embed (context, names, values); } } static void session_end_element (GMarkupParseContext *ctx, const gchar *element_name, gpointer user_data, GError **error) { SessionParserContext *context = (SessionParserContext *)user_data; if (strcmp (element_name, "window") == 0) { GtkWidget *notebook; notebook = ephy_window_get_notebook (context->window); gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), context->active_tab); if (ephy_embed_shell_get_mode (ephy_embed_shell_get_default ()) != EPHY_EMBED_SHELL_MODE_TEST) { EphyEmbed *active_child; active_child = ephy_embed_container_get_active_child (EPHY_EMBED_CONTAINER (context->window)); gtk_widget_grab_focus (GTK_WIDGET (active_child)); gtk_widget_show (GTK_WIDGET (context->window)); } context->window = NULL; context->active_tab = 0; context->is_first_window = FALSE; } else if (strcmp (element_name, "embed") == 0) { context->is_first_tab = FALSE; } } static const GMarkupParser session_parser = { session_start_element, session_end_element, NULL, NULL, NULL }; typedef struct { EphyShell *shell; GMarkupParseContext *parser; GCancellable *cancellable; char buffer[1024]; } LoadFromStreamAsyncData; static LoadFromStreamAsyncData * load_from_stream_async_data_new (GMarkupParseContext *parser, GCancellable *cancellable) { LoadFromStreamAsyncData *data; data = g_slice_new (LoadFromStreamAsyncData); data->shell = g_object_ref (ephy_shell_get_default ()); data->parser = parser; data->cancellable = cancellable ? g_object_ref (cancellable) : NULL; return data; } static void load_from_stream_async_data_free (LoadFromStreamAsyncData *data) { g_object_unref (data->shell); g_markup_parse_context_free (data->parser); g_clear_object (&data->cancellable); g_slice_free (LoadFromStreamAsyncData, data); } static void load_stream_complete (GSimpleAsyncResult *simple) { EphySession *session; g_simple_async_result_complete (simple); session = EPHY_SESSION (g_async_result_get_source_object (G_ASYNC_RESULT (simple))); session->priv->dont_save = FALSE; ephy_session_save (session, SESSION_STATE); g_object_unref (session); g_object_unref (simple); g_application_release (G_APPLICATION (ephy_shell_get_default ())); } static void load_stream_complete_error (GSimpleAsyncResult *simple, GError *error) { EphySession *session; LoadFromStreamAsyncData *data; SessionParserContext *context; g_simple_async_result_take_error (simple, error); g_simple_async_result_complete (simple); session = EPHY_SESSION (g_async_result_get_source_object (G_ASYNC_RESULT (simple))); session->priv->dont_save = FALSE; /* If the session fails to load for whatever reason, * delete the file and open an empty window. */ session_delete (session, SESSION_STATE); data = g_simple_async_result_get_op_res_gpointer (simple); context = (SessionParserContext *)g_markup_parse_context_get_user_data (data->parser); session_maybe_open_window (session, context->user_time); g_object_unref (session); g_object_unref (simple); g_application_release (G_APPLICATION (ephy_shell_get_default ())); } static void load_stream_read_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GInputStream *stream = G_INPUT_STREAM (object); GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data); LoadFromStreamAsyncData *data; gssize bytes_read; GError *error = NULL; bytes_read = g_input_stream_read_finish (stream, result, &error); if (bytes_read < 0) { load_stream_complete_error (simple, error); return; } data = g_simple_async_result_get_op_res_gpointer (simple); if (bytes_read == 0) { if (!g_markup_parse_context_end_parse (data->parser, &error)) { load_stream_complete_error (simple, error); } else { load_stream_complete (simple); } return; } if (!g_markup_parse_context_parse (data->parser, data->buffer, bytes_read, &error)) { load_stream_complete_error (simple, error); return; } g_input_stream_read_async (stream, data->buffer, sizeof (data->buffer), G_PRIORITY_HIGH, data->cancellable, load_stream_read_cb, simple); } /** * ephy_session_load_from_stream: * @session: an #EphySession * @stream: a #GInputStream to read the session data from * @user_time: a user time, or 0 * @cancellable: (allow-none): optional #GCancellable object, or %NULL * @callback: (scope async): a #GAsyncReadyCallback to call when the * request is satisfied * @user_data: (closure): the data to pass to callback function * * Asynchronously loads the session reading the session data from @stream, * restoring windows and their state. * * When the operation is finished, @callback will be called. You can * then call ephy_session_load_from_stream_finish() to get the result of * the operation. **/ void ephy_session_load_from_stream (EphySession *session, GInputStream *stream, guint32 user_time, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; SessionParserContext *context; GMarkupParseContext *parser; LoadFromStreamAsyncData *data; g_return_if_fail (EPHY_IS_SESSION (session)); g_return_if_fail (G_IS_INPUT_STREAM (stream)); g_application_hold (G_APPLICATION (ephy_shell_get_default ())); session->priv->dont_save = TRUE; result = g_simple_async_result_new (G_OBJECT (session), callback, user_data, ephy_session_load_from_stream); context = session_parser_context_new (session, user_time); parser = g_markup_parse_context_new (&session_parser, 0, context, (GDestroyNotify)session_parser_context_free); data = load_from_stream_async_data_new (parser, cancellable); g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify)load_from_stream_async_data_free); g_input_stream_read_async (stream, data->buffer, sizeof (data->buffer), G_PRIORITY_HIGH, cancellable, load_stream_read_cb, result); } /** * ephy_session_load_from_stream_finish: * @session: an #EphySession * @result: a #GAsyncResult * @error: a #GError * * Finishes an async session load operation started with * ephy_session_load_from_stream(). * * Returns: %TRUE if at least a window has been opened **/ gboolean ephy_session_load_from_stream_finish (EphySession *session, GAsyncResult *result, GError **error) { GSimpleAsyncResult *simple; g_return_val_if_fail (EPHY_IS_SESSION (session), FALSE); g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == ephy_session_load_from_stream); return !g_simple_async_result_propagate_error (simple, error); } typedef struct { guint32 user_time; GCancellable *cancellable; } LoadAsyncData; static LoadAsyncData * load_async_data_new (guint32 user_time, GCancellable *cancellable) { LoadAsyncData *data; data = g_slice_new (LoadAsyncData); data->user_time = user_time; data->cancellable = cancellable ? g_object_ref (cancellable) : NULL; return data; } static void load_async_data_free (LoadAsyncData *data) { g_clear_object (&data->cancellable); g_slice_free (LoadAsyncData, data); } static void load_from_stream_cb (GObject *object, GAsyncResult *result, gpointer user_data) { EphySession *session = EPHY_SESSION (object); GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data); GError *error = NULL; if (!ephy_session_load_from_stream_finish (session, result, &error)) { g_simple_async_result_take_error (simple, error); } g_simple_async_result_complete (simple); g_object_unref (simple); } static void session_read_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GFileInputStream *stream; GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data); GError *error = NULL; stream = g_file_read_finish (G_FILE (object), result, &error); if (stream) { EphySession *session; LoadAsyncData *data; session = EPHY_SESSION (g_async_result_get_source_object (G_ASYNC_RESULT (simple))); data = g_simple_async_result_get_op_res_gpointer (simple); ephy_session_load_from_stream (session, G_INPUT_STREAM (stream), data->user_time, data->cancellable, load_from_stream_cb, simple); g_object_unref (stream); g_object_unref (session); } else { g_simple_async_result_take_error (simple, error); g_simple_async_result_complete (simple); g_object_unref (simple); } g_application_release (G_APPLICATION (ephy_shell_get_default ())); } /** * ephy_session_load: * @session: an #EphySession * @filename: the path of the source file * @user_time: a user time, or 0 * @cancellable: (allow-none): optional #GCancellable object, or %NULL * @callback: (scope async): a #GAsyncReadyCallback to call when the * request is satisfied * @user_data: (closure): the data to pass to callback function * * Asynchronously loads the session reading the session data from @filename, * restoring windows and their state. * * When the operation is finished, @callback will be called. You can * then call ephy_session_load_finish() to get the result of * the operation. **/ void ephy_session_load (EphySession *session, const char *filename, guint32 user_time, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GFile *save_to_file; GSimpleAsyncResult *result; LoadAsyncData *data; g_return_if_fail (EPHY_IS_SESSION (session)); g_return_if_fail (filename); LOG ("ephy_sesion_load %s", filename); g_application_hold (G_APPLICATION (ephy_shell_get_default ())); result = g_simple_async_result_new (G_OBJECT (session), callback, user_data, ephy_session_load); save_to_file = get_session_file (filename); data = load_async_data_new (user_time, cancellable); g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify)load_async_data_free); g_file_read_async (save_to_file, G_PRIORITY_HIGH, cancellable, session_read_cb, result); g_object_unref (save_to_file); } /** * ephy_session_load_finish: * @session: an #EphySession * @result: a #GAsyncResult * @error: a #GError * * Finishes an async session load operation started with * ephy_session_load(). * * Returns: %TRUE if at least a window has been opened **/ gboolean ephy_session_load_finish (EphySession *session, GAsyncResult *result, GError **error) { GSimpleAsyncResult *simple; g_return_val_if_fail (EPHY_IS_SESSION (session), FALSE); g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == ephy_session_load); return !g_simple_async_result_propagate_error (simple, error); } static gboolean session_state_file_exists (EphySession *session) { GFile *saved_session_file; char *saved_session_file_path; gboolean retval; saved_session_file = get_session_file (SESSION_STATE); saved_session_file_path = g_file_get_path (saved_session_file); g_object_unref (saved_session_file); retval = g_file_test (saved_session_file_path, G_FILE_TEST_EXISTS); g_free (saved_session_file_path); return retval; } static void session_resumed_cb (GObject *object, GAsyncResult *result, gpointer user_data) { EphySession *session = EPHY_SESSION (object); GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data); GError *error = NULL; if (!ephy_session_load_finish (session, result, &error)) g_simple_async_result_take_error (simple, error); g_simple_async_result_complete (simple); g_object_unref (simple); } void ephy_session_resume (EphySession *session, guint32 user_time, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; gboolean has_session_state; EphyPrefsRestoreSessionPolicy policy; EphyShell *shell; LOG ("ephy_session_autoresume"); result = g_simple_async_result_new (G_OBJECT (session), callback, user_data, ephy_session_resume); has_session_state = session_state_file_exists (session); policy = g_settings_get_enum (EPHY_SETTINGS_MAIN, EPHY_PREFS_RESTORE_SESSION_POLICY); shell = ephy_shell_get_default (); if (has_session_state == FALSE || policy == EPHY_PREFS_RESTORE_SESSION_POLICY_NEVER) { /* If we are auto-resuming, and we never want to * restore the session, clobber the session state * file. */ if (policy == EPHY_PREFS_RESTORE_SESSION_POLICY_NEVER) session_delete (session, SESSION_STATE); session_maybe_open_window (session, user_time); } else if (ephy_shell_get_n_windows (shell) == 0) { ephy_session_load (session, SESSION_STATE, user_time, cancellable, session_resumed_cb, result); return; } g_simple_async_result_complete_in_idle (result); g_object_unref (result); } gboolean ephy_session_resume_finish (EphySession *session, GAsyncResult *result, GError **error) { GSimpleAsyncResult *simple; g_return_val_if_fail (EPHY_IS_SESSION (session), FALSE); g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == ephy_session_resume); return !g_simple_async_result_propagate_error (simple, error); } void ephy_session_clear (EphySession *session) { EphyShell *shell; GList *windows, *p; g_return_if_fail (EPHY_IS_SESSION (session)); shell = ephy_shell_get_default (); windows = g_list_copy (gtk_application_get_windows (GTK_APPLICATION (shell))); for (p = windows; p; p = p->next) gtk_widget_destroy (GTK_WIDGET (p->data)); g_list_free (windows); g_queue_foreach (session->priv->closed_tabs, (GFunc)closed_tab_free, NULL); g_queue_clear (session->priv->closed_tabs); ephy_session_save (session, SESSION_STATE); }