aboutsummaryrefslogblamecommitdiffstats
path: root/src/ephy-session.c
blob: b9d9c4d5024b5a0103ea4cdfd81a4299da5d9197 (plain) (tree)
1
2
3
4
5
6
                                                                           
  

                                                
                                                              
                                 












                                                                        
                                                                                  

   
                   

                         
                               
                       
                                 

                             
                              
                     
                       
                          
                       
                        
                        

                       
                    

                             
 

                                                                                                                       
                          
 
                                       
                            

  
                                                    
 
                                                        
 

                      

                                       
 

                    





                             
                                                  
         
                                                         
                                                             
                                               
         

            
                                           

         



                                          





                                     
                    
 
                                           
 
                                         
                              

 
                   







                                                              
     
           
                                         

                                            
 






                                                                                           
                                                           
 
      

           
                                            
                                         

                                             
 
                   

                                                                          
     
                                                                                 
                                                                       
      


           
                                              
                                           

                                               
 
                                                   
 
                   


                                                                               
     
                                            
                                                                                     
                          
      


           



                                                 
 
                                                   

 



















                                                                           
           


                                             
 
                            
                                
 
                                                   
 



                                           
 
                                                          





                                                                            




                                                                        
                                                 






                                                                          
                                                   

                              


           


                                               
 
                                                   





                                                                            

                          
           

                                        
                         
 
                                         
 
                                                           





                                                                   


           



                                                            


                                                                             
                    
 


                                         
                                             
 
                                   
 




                                                                         
 
                                       
 
                                                                                 
         

 
















































































































































                                                                                                

                                   
                           
 

                
                                                                      

                                
                                                                     
                                                                       

                                
                                                                       
                                                                         

                                
                         






                                                                               





                                                           
                                              
 

                
                                   

                                                                                     

                                

                                                                                     

                                

                                                                                         

                                

                                                                                          



                   
                                           
                                         
 
                 

                
                                                                       

                                
                                                                

                                

                                                                                              

                                
                                 
         


                                                                                   


                                        
                                                      
         

                                                         

                                   





                                                            

                                            
 








                                                                          
                            

                                

                

                                                    

                           
                                       
 


                             


                                                 
                                                                            

                              





                                                                     
                                                                              

                              
                                             
                                                                   
         
                                                                            


                              







                                                             
                                                                  
         






                                                                            
                 




                                                                                       


                 
                               
 




                                                                                       

                                        









































                                                                                              

 
           
                                                                               
 
                         
 

                                                              

                                                                     
 

                                                                             

 


                                         
 





                                                                   
         



















































                                                                                        
                 




















































































































                                                                                            
 









                                                                                                                      

                 


                                                 
         
































                                                                            

 
           





























                                                                                            
 















                                                                                              
                                                                



















                                                                          
         












                                                                             
                 
                                                      

                 
                       
         










                                                                                           
 
 
   
                                 
                            
                                                         
                                










                                                                          
    






                                                            
 



                                      
 

                                                      
 
                                                                       
 
                                        
 
                                                                                                                    
 



                                                                                                                       
 


                                                                                                             
 
















                                                           
 

                                                                 
 

                                                                                                        
 

                                                                      
 



                                  
 




                                               
 


                                                                            
 

                    
 



                                            
 

                                           
 







                                                                       
 






                                                                            
 







                                                                       
 


















                                                                                                    
 
                                                                          

 
   
                     
                            
                                         




                                                                      
  

                                                                            
  


                                                                    
    
    

                                        



                                                

                            

                                   
 

                                                     


                                              



                                                                                                        
                                                   


                                                                                                       
                                      
 
 
















                                                        
 

                                                                 
 

                                                                                            
 
                                                                      

 






























































                                                                                                          
                                                               




























                                                                                              
/* -*- 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-prefs.h"
#include "ephy-settings.h"
#include "ephy-shell.h"
#include "ephy-string.h"
#include "ephy-window.h"

#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <libxml/tree.h>
#include <libxml/xmlwriter.h>

#define EPHY_SESSION_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_SESSION, EphySessionPrivate))

struct _EphySessionPrivate
{
    GCancellable *save_cancellable;
    guint dont_save : 1;
};

#define SESSION_STATE       "type:session_state"

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 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
}

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);

    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_class_init (EphySessionClass *class)
{
    GObjectClass *object_class = G_OBJECT_CLASS (class);

    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 = ephy_shell_get_windows (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);
    }
    g_list_free (windows);
    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
session_save_finished (EphySession *session)
{
    g_application_release (G_APPLICATION (ephy_shell_get_default ()));
}

static gboolean
save_session_in_thread (GIOSchedulerJob *job,
            GCancellable *cancellable,
            gpointer user_data)
{
    SaveData *data = (SaveData *)user_data;
    xmlBufferPtr buffer;
    xmlTextWriterPtr writer;
    GList *w;
    int ret;

    buffer = xmlBufferCreate ();
    writer = xmlNewTextWriterMemory (buffer, 0);
    if (writer == NULL)
    {
        xmlBufferFree (buffer);

        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 = 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:
    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_io_scheduler_job_send_to_mainloop_async (job,
                           (GSourceFunc) session_save_finished,
                           g_object_ref (data->session),
                           (GDestroyNotify) g_object_unref);

    STOP_PROFILER ("Saving session")

    return FALSE;
}

void
ephy_session_save (EphySession *session,
           const char *filename)
{
    EphySessionPrivate *priv;
    EphyShell *shell;
    SaveData *data;

    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));
    g_io_scheduler_push_job (save_session_in_thread, data, (GDestroyNotify)save_data_free,
                 G_PRIORITY_DEFAULT, priv->save_cancellable);
}

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);
}