[epiphany/wip/sync: 4/6] sync: Implement saved passwords sync



commit 24ab41a9b8bd2de67231d1bc6dea12787d909afa
Author: Gabriel Ivascu <ivascu gabriel59 gmail com>
Date:   Sun Apr 30 22:11:15 2017 +0300

    sync: Implement saved passwords sync

 data/org.gnome.epiphany.gschema.xml          |   15 +
 embed/meson.build                            |    1 +
 embed/web-extension/ephy-embed-form-auth.c   |   14 +
 embed/web-extension/ephy-embed-form-auth.h   |   23 +-
 embed/web-extension/ephy-web-extension.c     |   53 +-
 embed/web-extension/meson.build              |    1 +
 lib/ephy-password-manager.c                  |  480 ------------
 lib/ephy-password-manager.h                  |   77 --
 lib/ephy-password-record.c                   |  236 ------
 lib/ephy-password-record.h                   |   45 --
 lib/ephy-prefs.h                             |    3 +
 lib/meson.build                              |    2 -
 lib/sync/ephy-password-manager.c             | 1056 ++++++++++++++++++++++++++
 lib/sync/ephy-password-manager.h             |   82 ++
 lib/sync/ephy-password-record.c              |  370 +++++++++
 lib/sync/ephy-password-record.h              |   50 ++
 lib/sync/ephy-sync-service.c                 |  160 +++-
 lib/sync/ephy-sync-service.h                 |    2 +-
 lib/sync/ephy-synchronizable-manager.c       |   27 +-
 lib/sync/ephy-synchronizable-manager.h       |   22 +-
 lib/sync/meson.build                         |    2 +
 meson.build                                  |    2 +-
 src/bookmarks/ephy-bookmarks-manager.c       |   24 +-
 src/ephy-shell.c                             |   11 +-
 src/passwords-dialog.c                       |   26 +-
 src/prefs-dialog.c                           |   34 +-
 src/profile-migrator/ephy-profile-migrator.c |   24 +-
 src/resources/gtk/prefs-dialog.ui            |    7 +
 28 files changed, 1873 insertions(+), 976 deletions(-)
---
diff --git a/data/org.gnome.epiphany.gschema.xml b/data/org.gnome.epiphany.gschema.xml
index 2264bbd..ddc61dc 100644
--- a/data/org.gnome.epiphany.gschema.xml
+++ b/data/org.gnome.epiphany.gschema.xml
@@ -302,6 +302,21 @@
                        <summary>Initial sync or normal sync</summary>
                        <description>TRUE if bookmarks collection needs to be synced for the first time, 
FALSE otherwise.</description>
                </key>
+               <key type="b" name="sync-passwords-enabled">
+                       <default>false</default>
+                       <summary>Enable passwords sync</summary>
+                       <description>TRUE if passwords collection should be synced, FALSE 
otherwise.</description>
+               </key>
+               <key type="d" name="sync-passwords-time">
+                       <default>0</default>
+                       <summary>Passwords sync timestamp</summary>
+                       <description>The timestamp at which last passwords sync was made.</description>
+               </key>
+               <key type="b" name="sync-passwords-initial">
+                       <default>true</default>
+                       <summary>Initial sync or normal sync</summary>
+                       <description>TRUE if passwords collection needs to be synced for the first time, 
FALSE otherwise.</description>
+               </key>
        </schema>
        <enum id="org.gnome.Epiphany.Permission">
                <value nick="undecided" value="-1"/>
diff --git a/embed/meson.build b/embed/meson.build
index 99812c5..f6d2297 100644
--- a/embed/meson.build
+++ b/embed/meson.build
@@ -54,6 +54,7 @@ libephyembed_includes = include_directories(
   '../lib/contrib',
   '../lib/contrib/gvdb',
   '../lib/history',
+  '../lib/sync',
   '../lib/widgets',
   '../lib/widgets/contrib'
 )
diff --git a/embed/web-extension/ephy-embed-form-auth.c b/embed/web-extension/ephy-embed-form-auth.c
index 930cb3a..ae36027 100644
--- a/embed/web-extension/ephy-embed-form-auth.c
+++ b/embed/web-extension/ephy-embed-form-auth.c
@@ -29,6 +29,7 @@ struct _EphyEmbedFormAuth {
   WebKitDOMNode *username_node;
   WebKitDOMNode *password_node;
   char *username;
+  gboolean password_updated;
 };
 
 G_DEFINE_TYPE (EphyEmbedFormAuth, ephy_embed_form_auth, G_TYPE_OBJECT)
@@ -110,6 +111,19 @@ ephy_embed_form_auth_get_username (EphyEmbedFormAuth *form_auth)
   return form_auth->username;
 }
 
+gboolean
+ephy_embed_form_auth_get_password_updated (EphyEmbedFormAuth *form_auth)
+{
+  return form_auth->password_updated;
+}
+
+void
+ephy_embed_form_auth_set_password_updated (EphyEmbedFormAuth *form_auth,
+                                           gboolean           password_updated)
+{
+  form_auth->password_updated = password_updated;
+}
+
 WebKitDOMDocument *
 ephy_embed_form_auth_get_owner_document (EphyEmbedFormAuth *form_auth)
 {
diff --git a/embed/web-extension/ephy-embed-form-auth.h b/embed/web-extension/ephy-embed-form-auth.h
index cb78e06..3266771 100644
--- a/embed/web-extension/ephy-embed-form-auth.h
+++ b/embed/web-extension/ephy-embed-form-auth.h
@@ -30,15 +30,18 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (EphyEmbedFormAuth, ephy_embed_form_auth, EPHY, EMBED_FORM_AUTH, GObject)
 
-EphyEmbedFormAuth *ephy_embed_form_auth_new               (WebKitWebPage     *web_page,
-                                                           WebKitDOMNode     *username_node,
-                                                           WebKitDOMNode     *password_node,
-                                                           const char        *username);
-WebKitDOMNode     *ephy_embed_form_auth_get_username_node (EphyEmbedFormAuth *form_auth);
-WebKitDOMNode     *ephy_embed_form_auth_get_password_node (EphyEmbedFormAuth *form_auth);
-SoupURI           *ephy_embed_form_auth_get_uri           (EphyEmbedFormAuth *form_auth);
-guint64            ephy_embed_form_auth_get_page_id       (EphyEmbedFormAuth *form_auth);
-const char        *ephy_embed_form_auth_get_username      (EphyEmbedFormAuth *form_auth);
-WebKitDOMDocument *ephy_embed_form_auth_get_owner_document(EphyEmbedFormAuth *form_auth);
+EphyEmbedFormAuth *ephy_embed_form_auth_new                   (WebKitWebPage     *web_page,
+                                                               WebKitDOMNode     *username_node,
+                                                               WebKitDOMNode     *password_node,
+                                                               const char        *username);
+WebKitDOMNode     *ephy_embed_form_auth_get_username_node     (EphyEmbedFormAuth *form_auth);
+WebKitDOMNode     *ephy_embed_form_auth_get_password_node     (EphyEmbedFormAuth *form_auth);
+SoupURI           *ephy_embed_form_auth_get_uri               (EphyEmbedFormAuth *form_auth);
+guint64            ephy_embed_form_auth_get_page_id           (EphyEmbedFormAuth *form_auth);
+const char        *ephy_embed_form_auth_get_username          (EphyEmbedFormAuth *form_auth);
+gboolean           ephy_embed_form_auth_get_password_updated  (EphyEmbedFormAuth *form_auth);
+void               ephy_embed_form_auth_set_password_updated  (EphyEmbedFormAuth *form_auth,
+                                                               gboolean           password_updated);
+WebKitDOMDocument *ephy_embed_form_auth_get_owner_document    (EphyEmbedFormAuth *form_auth);
 
 G_END_DECLS
diff --git a/embed/web-extension/ephy-web-extension.c b/embed/web-extension/ephy-web-extension.c
index b628358..bcc3446 100644
--- a/embed/web-extension/ephy-web-extension.c
+++ b/embed/web-extension/ephy-web-extension.c
@@ -30,6 +30,7 @@
 #include "ephy-permissions-manager.h"
 #include "ephy-prefs.h"
 #include "ephy-settings.h"
+#include "ephy-sync-service.h"
 #include "ephy-uri-helpers.h"
 #include "ephy-uri-tester.h"
 #include "ephy-web-dom-utils.h"
@@ -51,6 +52,7 @@ struct _EphyWebExtension {
   GDBusConnection *dbus_connection;
   GArray *page_created_signals_pending;
 
+  EphySyncService *sync_service;
   EphyPasswordManager *password_manager;
   GHashTable *form_auth_data_save_requests;
   EphyWebOverviewModel *overview_model;
@@ -267,6 +269,7 @@ store_password (EphyEmbedFormAuth *form_auth)
   char *username_field_value = NULL;
   char *password_field_name = NULL;
   char *password_field_value = NULL;
+  gboolean password_updated;
   WebKitDOMNode *username_node;
   EphyWebExtension *extension = ephy_web_extension_get ();
 
@@ -283,12 +286,14 @@ store_password (EphyEmbedFormAuth *form_auth)
 
   uri = ephy_embed_form_auth_get_uri (form_auth);
   uri_str = soup_uri_to_string (uri, FALSE);
+  password_updated = ephy_embed_form_auth_get_password_updated (form_auth);
   ephy_password_manager_save (extension->password_manager,
                               uri_str,
+                              username_field_value,
+                              password_field_value,
                               username_field_name,
                               password_field_name,
-                              username_field_value,
-                              password_field_value);
+                              !password_updated);
 
   g_free (uri_str);
   g_free (username_field_name);
@@ -417,15 +422,18 @@ should_store_cb (GSList   *records,
       LOG ("User/password already stored. Not asking about storing.");
     } else if (permission == EPHY_PERMISSION_PERMIT) {
       LOG ("User/password not yet stored. Storing.");
+      ephy_embed_form_auth_set_password_updated (form_auth, TRUE);
       store_password (form_auth);
     } else {
       LOG ("User/password not yet stored. Asking about storing.");
+      ephy_embed_form_auth_set_password_updated (form_auth, TRUE);
       request_decision_on_storing (g_object_ref (form_auth));
     }
 
     g_free (username_field_value);
   } else {
     LOG ("No result on query; asking whether we should store.");
+    ephy_embed_form_auth_set_password_updated (form_auth, FALSE);
     request_decision_on_storing (g_object_ref (form_auth));
   }
 
@@ -475,9 +483,9 @@ form_submitted_cb (WebKitDOMHTMLFormElement *dom_form,
 
   ephy_password_manager_query (extension->password_manager,
                                uri_str,
+                               username_field_value,
                                username_field_name,
                                password_field_name,
-                               username_field_value,
                                should_store_cb,
                                form_auth);
 
@@ -555,9 +563,9 @@ pre_fill_form (EphyEmbedFormAuth *form_auth)
 
   ephy_password_manager_query (extension->password_manager,
                                uri_str,
+                               username,
                                username_field_name,
                                password_field_name,
-                               username,
                                fill_form_cb,
                                form_auth);
 
@@ -730,8 +738,7 @@ show_user_choices (WebKitDOMDocument *document,
   WebKitDOMNode *body;
   WebKitDOMElement *main_div;
   WebKitDOMElement *ul;
-  GSList *iter;
-  GSList *password_records_list;
+  GSList *cached_users;
   gboolean username_node_ever_edited;
   double x, y;
   double input_width;
@@ -777,32 +784,27 @@ show_user_choices (WebKitDOMDocument *document,
                                     "padding: 0;",
                                     NULL);
 
-  password_records_list = (GSList *)g_object_get_data (G_OBJECT (username_node),
-                                                       "ephy-password-records-list");
+  cached_users = (GSList *)g_object_get_data (G_OBJECT (username_node), "ephy-cached-users");
 
   username_node_ever_edited =
     GPOINTER_TO_INT (g_object_get_data (G_OBJECT (username_node),
                                         "ephy-user-ever-edited"));
 
-  for (iter = password_records_list; iter; iter = iter->next) {
-    EphyPasswordRecord *record;
+  for (GSList *l = cached_users; l && l->data; l = l->next) {
+    const char *user = l->data;
     WebKitDOMElement *li;
     WebKitDOMElement *anchor;
     char *child_style;
-    const char *record_username;
     gboolean is_selected;
 
-    record = EPHY_PASSWORD_RECORD (iter->data);
-    record_username = ephy_password_record_get_username (record);
-
     /* Filter out the available names that do not match, but show all options in
      * case we have been triggered by something other than the user editing the
      * input.
      */
-    if (username_node_ever_edited && !g_str_has_prefix (record_username, username))
+    if (username_node_ever_edited && !g_str_has_prefix (user, username))
       continue;
 
-    is_selected = !g_strcmp0 (username, record_username);
+    is_selected = !g_strcmp0 (username, user);
 
     li = webkit_dom_document_create_element (document, "li", NULL);
     webkit_dom_element_set_attribute (li, "tabindex", "-1", NULL);
@@ -834,7 +836,7 @@ show_user_choices (WebKitDOMDocument *document,
                                                 username_node);
 
     webkit_dom_node_set_text_content (WEBKIT_DOM_NODE (anchor),
-                                      record_username,
+                                      user,
                                       NULL);
   }
 
@@ -1108,7 +1110,7 @@ web_page_form_controls_associated (WebKitWebPage    *web_page,
     /* We have a field that may be the user, and one for a password. */
     if (ephy_web_dom_utils_find_form_auth_elements (form, &username_node, &password_node)) {
       EphyEmbedFormAuth *form_auth;
-      GSList *password_records_list;
+      GSList *cached_users;
       const char *uri;
 
       LOG ("Hooking and pre-filling a form");
@@ -1126,11 +1128,11 @@ web_page_form_controls_associated (WebKitWebPage    *web_page,
 
       /* Plug in the user autocomplete */
       uri = webkit_web_page_get_uri (web_page);
-      password_records_list = ephy_password_manager_get_cached_by_uri (extension->password_manager, uri);
+      cached_users = ephy_password_manager_get_cached_users_for_uri (extension->password_manager, uri);
 
-      if (password_records_list && password_records_list->next && username_node) {
+      if (cached_users && cached_users->next && username_node) {
         LOG ("More than 1 password saved, hooking menu for choosing which on focus");
-        g_object_set_data (G_OBJECT (username_node), "ephy-password-records-list", password_records_list);
+        g_object_set_data (G_OBJECT (username_node), "ephy-cached-users", cached_users);
         g_object_set_data (G_OBJECT (username_node), "ephy-form-auth", form_auth);
         g_object_set_data (G_OBJECT (username_node), "ephy-document", document);
         webkit_dom_event_target_add_event_listener (WEBKIT_DOM_EVENT_TARGET (username_node), "input",
@@ -1149,7 +1151,7 @@ web_page_form_controls_associated (WebKitWebPage    *web_page,
                                                     G_CALLBACK (username_node_changed_cb), FALSE,
                                                     web_page);
       } else
-        LOG ("No items or a single item in password_records_list, not hooking menu for choosing.");
+        LOG ("No items or a single item in cached_users, not hooking menu for choosing.");
 
       pre_fill_form (form_auth);
 
@@ -1453,6 +1455,7 @@ ephy_web_extension_dispose (GObject *object)
   g_clear_object (&extension->overview_model);
   g_clear_object (&extension->permissions_manager);
   g_clear_object (&extension->password_manager);
+  g_clear_object (&extension->sync_service);
 
   if (extension->form_auth_data_save_requests) {
     g_hash_table_destroy (extension->form_auth_data_save_requests);
@@ -1562,8 +1565,12 @@ ephy_web_extension_initialize (EphyWebExtension   *extension,
   extension->initialized = TRUE;
 
   extension->extension = g_object_ref (wk_extension);
-  if (!is_private_profile)
+  if (!is_private_profile) {
+    extension->sync_service = ephy_sync_service_new (FALSE);
     extension->password_manager = ephy_password_manager_new ();
+    ephy_sync_service_register_manager (extension->sync_service,
+                                        EPHY_SYNCHRONIZABLE_MANAGER (extension->password_manager));
+  }
 
   extension->permissions_manager = ephy_permissions_manager_new ();
 
diff --git a/embed/web-extension/meson.build b/embed/web-extension/meson.build
index de6be46..af53a64 100644
--- a/embed/web-extension/meson.build
+++ b/embed/web-extension/meson.build
@@ -10,6 +10,7 @@ web_extension_sources = [
 
 web_extension_deps = [
   ephymisc_dep,
+  ephysync_dep,
   webkit2gtk_web_extension_dep
 ]
 
diff --git a/lib/ephy-prefs.h b/lib/ephy-prefs.h
index 3167d12..d91fdf2 100644
--- a/lib/ephy-prefs.h
+++ b/lib/ephy-prefs.h
@@ -157,6 +157,9 @@ static const char * const ephy_prefs_web_schema[] = {
 #define EPHY_PREFS_SYNC_BOOKMARKS_ENABLED "sync-bookmarks-enabled"
 #define EPHY_PREFS_SYNC_BOOKMARKS_TIME    "sync-bookmarks-time"
 #define EPHY_PREFS_SYNC_BOOKMARKS_INITIAL "sync-bookmarks-initial"
+#define EPHY_PREFS_SYNC_PASSWORDS_ENABLED "sync-passwords-enabled"
+#define EPHY_PREFS_SYNC_PASSWORDS_TIME    "sync-passwords-time"
+#define EPHY_PREFS_SYNC_PASSWORDS_INITIAL "sync-passwords-initial"
 
 static struct {
   const char *schema;
diff --git a/lib/meson.build b/lib/meson.build
index 6e88051..b81c721 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -22,8 +22,6 @@ libephymisc_sources = [
   'ephy-filters-manager.c',
   'ephy-gui.c',
   'ephy-langs.c',
-  'ephy-password-manager.c',
-  'ephy-password-record.c',
   'ephy-permissions-manager.c',
   'ephy-profile-utils.c',
   'ephy-search-engine-manager.c',
diff --git a/lib/sync/ephy-password-manager.c b/lib/sync/ephy-password-manager.c
new file mode 100644
index 0000000..3dfa947
--- /dev/null
+++ b/lib/sync/ephy-password-manager.c
@@ -0,0 +1,1056 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany 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 Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-password-manager.h"
+
+#include "ephy-debug.h"
+#include "ephy-settings.h"
+#include "ephy-synchronizable-manager.h"
+#include "ephy-uri-helpers.h"
+
+#include <glib/gi18n.h>
+#include <stdio.h>
+
+const SecretSchema *
+ephy_password_manager_get_password_schema (void)
+{
+  static const SecretSchema schema = {
+    "org.epiphany.FormPassword", SECRET_SCHEMA_NONE,
+    {
+      { ID_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
+      { HOSTNAME_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
+      { USERNAME_FIELD_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
+      { PASSWORD_FIELD_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
+      { USERNAME_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
+      { SERVER_TIME_MODIFIED_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING},
+      { "NULL", 0 },
+    }
+  };
+  return &schema;
+}
+
+struct _EphyPasswordManager {
+  GObject parent_instance;
+
+  GHashTable *cache;
+};
+
+static void ephy_synchronizable_manager_iface_init (EphySynchronizableManagerInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (EphyPasswordManager, ephy_password_manager, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (EPHY_TYPE_SYNCHRONIZABLE_MANAGER,
+                                                ephy_synchronizable_manager_iface_init))
+
+typedef struct {
+  EphyPasswordManagerQueryCallback callback;
+  gpointer user_data;
+} QueryAsyncData;
+
+typedef struct {
+  EphyPasswordManager *manager;
+  char                *password;
+} UpdatePasswordAsyncData;
+
+typedef struct {
+  EphyPasswordManager *manager;
+  EphyPasswordRecord  *record;
+} ReplaceRecordAsyncData;
+
+typedef struct {
+  EphyPasswordManager                    *manager;
+  gboolean                                is_initial;
+  GSList                                 *remotes_deleted;
+  GSList                                 *remotes_updated;
+  EphySynchronizableManagerMergeCallback  callback;
+  gpointer                                user_data;
+} MergeAsyncData;
+
+static QueryAsyncData *
+query_async_data_new (EphyPasswordManagerQueryCallback callback,
+                      gpointer                         user_data)
+{
+  QueryAsyncData *data;
+
+  data = g_slice_new (QueryAsyncData);
+  data->callback = callback;
+  data->user_data = user_data;
+
+  return data;
+}
+
+static void
+query_async_data_free (QueryAsyncData *data)
+{
+  g_assert (data);
+
+  g_slice_free (QueryAsyncData, data);
+}
+
+static UpdatePasswordAsyncData *
+update_password_async_data_new (EphyPasswordManager *manager,
+                                const char          *password)
+{
+  UpdatePasswordAsyncData *data;
+
+  data = g_slice_new (UpdatePasswordAsyncData);
+  data->manager = g_object_ref (manager);
+  data->password = g_strdup (password);
+
+  return data;
+}
+
+static void
+update_password_async_data_free (UpdatePasswordAsyncData *data)
+{
+  g_assert (data);
+
+  g_object_unref (data->manager);
+  g_free (data->password);
+  g_slice_free (UpdatePasswordAsyncData, data);
+}
+
+static MergeAsyncData *
+merge_async_data_new (EphyPasswordManager                    *manager,
+                      gboolean                                is_initial,
+                      GSList                                 *remotes_deleted,
+                      GSList                                 *remotes_updated,
+                      EphySynchronizableManagerMergeCallback  callback,
+                      gpointer                                user_data)
+{
+  MergeAsyncData *data;
+
+  data = g_slice_new (MergeAsyncData);
+  data->manager = g_object_ref (manager);
+  data->is_initial = is_initial;
+  data->remotes_deleted = remotes_deleted;
+  data->remotes_updated = remotes_updated;
+  data->callback = callback;
+  data->user_data = user_data;
+
+  return data;
+}
+
+static void
+merge_async_data_free (MergeAsyncData *data)
+{
+  g_assert (data);
+
+  g_object_unref (data->manager);
+  g_slice_free (MergeAsyncData, data);
+}
+
+static ReplaceRecordAsyncData *
+replace_record_async_data_new (EphyPasswordManager *manager,
+                               EphyPasswordRecord  *record)
+{
+  ReplaceRecordAsyncData *data;
+
+  data = g_slice_new (ReplaceRecordAsyncData);
+  data->manager = g_object_ref (manager);
+  data->record = g_object_ref (record);
+
+  return data;
+}
+
+static void
+replace_record_async_data_free (ReplaceRecordAsyncData *data)
+{
+  g_assert (data);
+
+  g_object_unref (data->manager);
+  g_object_unref (data->record);
+  g_slice_free (ReplaceRecordAsyncData, data);
+}
+
+static GHashTable *
+get_attributes_table (const char *id,
+                      const char *uri,
+                      const char *username,
+                      const char *username_field,
+                      const char *password_field,
+                      double      server_time_modified)
+{
+  GHashTable *attributes = secret_attributes_build (EPHY_FORM_PASSWORD_SCHEMA, NULL);
+
+  if (id)
+    g_hash_table_insert (attributes,
+                         g_strdup (ID_KEY),
+                         g_strdup (id));
+  if (uri)
+    g_hash_table_insert (attributes,
+                         g_strdup (HOSTNAME_KEY),
+                         ephy_uri_to_security_origin (uri));
+  if (username)
+    g_hash_table_insert (attributes,
+                         g_strdup (USERNAME_KEY),
+                         g_strdup (username));
+  if (username_field)
+    g_hash_table_insert (attributes,
+                         g_strdup (USERNAME_FIELD_KEY),
+                         g_strdup (username_field));
+  if (password_field)
+    g_hash_table_insert (attributes,
+                         g_strdup (PASSWORD_FIELD_KEY),
+                         g_strdup (password_field));
+  if (server_time_modified >= 0)
+    g_hash_table_insert (attributes,
+                         g_strdup (SERVER_TIME_MODIFIED_KEY),
+                         g_strdup_printf ("%.2lf", server_time_modified));
+
+  return attributes;
+}
+
+static void
+ephy_password_manager_cache_clear (EphyPasswordManager *self)
+{
+  GHashTableIter iter;
+  gpointer key, value;
+
+  g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+  g_assert (self->cache);
+
+  g_hash_table_iter_init (&iter, self->cache);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    g_slist_free_full (value, g_free);
+  g_hash_table_remove_all (self->cache);
+}
+
+static void
+ephy_password_manager_cache_remove (EphyPasswordManager *self,
+                                    const char          *hostname,
+                                    const char          *username)
+{
+  GSList *usernames;
+  GSList *new_usernames = NULL;
+
+  g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+  g_assert (self->cache);
+  g_assert (hostname);
+  g_assert (username);
+
+  usernames = g_hash_table_lookup (self->cache, hostname);
+  if (usernames) {
+    for (GSList *l = usernames; l && l->data; l = l->next) {
+      if (g_strcmp0 (username, l->data))
+        new_usernames = g_slist_prepend (new_usernames, g_strdup (l->data));
+    }
+    g_hash_table_replace (self->cache, g_strdup (hostname), new_usernames);
+    g_slist_free_full (usernames, g_free);
+  }
+}
+
+static void
+ephy_password_manager_cache_add (EphyPasswordManager *self,
+                                 const char          *hostname,
+                                 const char          *username)
+{
+  GSList *usernames;
+
+  g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+  g_assert (self->cache);
+  g_assert (hostname);
+  g_assert (username);
+
+  usernames = g_hash_table_lookup (self->cache, hostname);
+  for (GSList *l = usernames; l && l->data; l = l->next) {
+    if (!g_strcmp0 (username, l->data))
+      return;
+  }
+  usernames = g_slist_prepend (usernames, g_strdup (username));
+  g_hash_table_replace (self->cache, g_strdup (hostname), usernames);
+}
+
+static void
+populate_cache_cb (GSList   *records,
+                   gpointer  user_data)
+{
+  EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (user_data);
+
+  for (GSList *l = records; l && l->data; l = l->next) {
+    EphyPasswordRecord *record = EPHY_PASSWORD_RECORD (l->data);
+    const char *hostname = ephy_password_record_get_hostname (record);
+    const char *username = ephy_password_record_get_username (record);
+
+    ephy_password_manager_cache_add (self, hostname, username);
+  }
+
+  g_slist_free_full (records, g_object_unref);
+}
+
+static void
+ephy_password_manager_dispose (GObject *object)
+{
+  EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (object);
+
+  if (self->cache) {
+    ephy_password_manager_cache_clear (self);
+    g_clear_pointer (&self->cache, g_hash_table_unref);
+  }
+
+  G_OBJECT_CLASS (ephy_password_manager_parent_class)->dispose (object);
+}
+
+static void
+ephy_password_manager_class_init (EphyPasswordManagerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = ephy_password_manager_dispose;
+}
+
+static void
+ephy_password_manager_init (EphyPasswordManager *self)
+{
+  LOG ("Loading usernames into internal cache...");
+  self->cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  ephy_password_manager_query (self, NULL, NULL, NULL, NULL,
+                               populate_cache_cb, self);
+}
+
+EphyPasswordManager *
+ephy_password_manager_new (void)
+{
+  return EPHY_PASSWORD_MANAGER (g_object_new (EPHY_TYPE_PASSWORD_MANAGER, NULL));
+}
+
+GSList *
+ephy_password_manager_get_cached_users_for_uri (EphyPasswordManager *self,
+                                                const char          *uri)
+{
+  GSList *list;
+  char *hostname;
+
+  g_return_val_if_fail (EPHY_IS_PASSWORD_MANAGER (self), NULL);
+  g_return_val_if_fail (uri, NULL);
+
+  hostname = ephy_uri_to_security_origin (uri);
+  list = g_hash_table_lookup (self->cache, hostname);
+  g_free (hostname);
+
+  return list;
+}
+
+static void
+secret_service_store_cb (SecretService *service,
+                         GAsyncResult  *result,
+                         GTask         *task)
+{
+  GError *error = NULL;
+
+  secret_service_store_finish (service, result, &error);
+  if (error)
+    g_task_return_error (task, error);
+  else
+    g_task_return_boolean (task, TRUE);
+
+  g_object_unref (task);
+}
+
+static void
+store_internal (const char          *password,
+                GHashTable          *attributes,
+                GAsyncReadyCallback  callback,
+                gpointer             user_data)
+{
+  SecretValue *value;
+  GTask *task;
+  const char *hostname;
+  const char *username;
+  char *label;
+
+  g_assert (password);
+  g_assert (attributes);
+
+  task = g_task_new (NULL, NULL, callback, user_data);
+  value = secret_value_new (password, -1, "text/plain");
+  hostname = g_hash_table_lookup (attributes, HOSTNAME_KEY);
+  username = g_hash_table_lookup (attributes, USERNAME_KEY);
+
+  if (username) {
+    /* Translators: The first %s is the username and the second one is the
+     * security origin where this is happening. Example: gnome gmail com and
+     * https://mail.google.com. */
+    label = g_strdup_printf (_("Password for %s in a form in %s"), username, hostname);
+  } else {
+    /* Translators: The %s is the security origin where this is happening.
+     * Example: https://mail.google.com. */
+    label = g_strdup_printf (_("Password in a form in %s"), hostname);
+  }
+
+  LOG ("Storing password record for (%s, %s, %s, %s)",
+       hostname, username,
+       (char *)g_hash_table_lookup (attributes, USERNAME_FIELD_KEY),
+       (char *)g_hash_table_lookup (attributes, PASSWORD_FIELD_KEY));
+
+  secret_service_store (NULL, EPHY_FORM_PASSWORD_SCHEMA,
+                        attributes, NULL, label, value, NULL,
+                        (GAsyncReadyCallback)secret_service_store_cb,
+                        g_object_ref (task));
+
+  g_free (label);
+  secret_value_unref (value);
+  g_object_unref (task);
+}
+
+static void
+ephy_password_manger_store_record (EphyPasswordManager *self,
+                                   EphyPasswordRecord  *record)
+{
+  GHashTable *attributes;
+
+  g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+  g_assert (EPHY_IS_PASSWORD_RECORD (record));
+
+  attributes = get_attributes_table (ephy_password_record_get_id (record),
+                                     ephy_password_record_get_hostname (record),
+                                     ephy_password_record_get_username (record),
+                                     ephy_password_record_get_username_field (record),
+                                     ephy_password_record_get_password_field (record),
+                                     ephy_synchronizable_get_server_time_modified (EPHY_SYNCHRONIZABLE 
(record)));
+  store_internal (ephy_password_record_get_password (record), attributes, NULL, NULL);
+  ephy_password_manager_cache_add (self,
+                                   ephy_password_record_get_hostname (record),
+                                   ephy_password_record_get_username (record));
+
+  g_hash_table_unref (attributes);
+}
+
+static void
+update_password_cb (GSList   *records,
+                    gpointer  user_data)
+{
+  UpdatePasswordAsyncData *data = (UpdatePasswordAsyncData *)user_data;
+  EphyPasswordRecord *record;
+
+  /* We only expect one matching record here. */
+  g_assert (g_slist_length (records) == 1);
+
+  record = EPHY_PASSWORD_RECORD (records->data);
+  ephy_password_record_set_password (record, data->password);
+  ephy_password_manger_store_record (data->manager, record);
+  g_signal_emit_by_name (data->manager, "synchronizable-modified", record);
+
+  g_slist_free_full (records, g_object_unref);
+  update_password_async_data_free (data);
+}
+
+void
+ephy_password_manager_save (EphyPasswordManager *self,
+                            const char          *uri,
+                            const char          *username,
+                            const char          *password,
+                            const char          *username_field,
+                            const char          *password_field,
+                            gboolean             is_new)
+{
+  EphyPasswordRecord *record;
+  char *hostname;
+  char *uuid;
+  char *id;
+  gint64 timestamp;
+
+  g_return_if_fail (EPHY_IS_PASSWORD_MANAGER (self));
+  g_return_if_fail (uri);
+  g_return_if_fail (password);
+  g_return_if_fail (!username_field || username);
+  g_return_if_fail (!password_field || password);
+
+  if (!is_new) {
+    LOG ("Updating password for (%s, %s, %s, %s)",
+         uri, username, username_field, password_field);
+    ephy_password_manager_query (self, uri, username,
+                                 username_field, password_field,
+                                 update_password_cb,
+                                 update_password_async_data_new (self, password));
+    return;
+  }
+
+  uuid = g_uuid_string_random ();
+  id = g_strdup_printf ("{%s}", uuid);
+  timestamp = g_get_real_time () / 1000;
+  hostname = ephy_uri_to_security_origin (uri);
+  record = ephy_password_record_new (id, hostname,
+                                     username, password,
+                                     username_field, password_field,
+                                     timestamp, timestamp);
+  ephy_password_manger_store_record (self, record);
+  g_signal_emit_by_name (self, "synchronizable-modified", record);
+
+  g_free (hostname);
+  g_free (uuid);
+  g_free (id);
+  g_object_unref (record);
+}
+
+static void
+secret_service_search_cb (SecretService  *service,
+                          GAsyncResult   *result,
+                          QueryAsyncData *data)
+{
+  GList *matches;
+  GSList *records = NULL;
+  GError *error = NULL;
+
+  matches = secret_service_search_finish (service, result, &error);
+  if (error) {
+    g_warning ("Failed to search secrets in password schema: %s", error->message);
+    g_error_free (error);
+    goto out;
+  }
+
+  for (GList *l = matches; l && l->data; l = l->next) {
+    SecretItem *item = (SecretItem *)l->data;
+    GHashTable *attributes = secret_item_get_attributes (item);
+    SecretValue *value = secret_item_get_secret (item);
+    const char *id = g_hash_table_lookup (attributes, ID_KEY);
+    const char *hostname = g_hash_table_lookup (attributes, HOSTNAME_KEY);
+    const char *username = g_hash_table_lookup (attributes, USERNAME_KEY);
+    const char *username_field = g_hash_table_lookup (attributes, USERNAME_FIELD_KEY);
+    const char *password_field = g_hash_table_lookup (attributes, PASSWORD_FIELD_KEY);
+    const char *timestamp = g_hash_table_lookup (attributes, SERVER_TIME_MODIFIED_KEY);
+    const char *password = secret_value_get (value, NULL);
+    double server_time_modified;
+    EphyPasswordRecord *record;
+
+    LOG ("Found password record for (%s, %s, %s, %s)",
+         hostname, username, username_field, password_field);
+
+    record = ephy_password_record_new (id, hostname,
+                                       username, password,
+                                       username_field, password_field,
+                                       secret_item_get_created (item) * 1000,
+                                       secret_item_get_modified (item) * 1000);
+    sscanf (timestamp, "%lf", &server_time_modified);
+    ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (record),
+                                                  server_time_modified);
+    records = g_slist_prepend (records, record);
+
+    secret_value_unref (value);
+    g_hash_table_unref (attributes);
+  }
+
+out:
+  if (data->callback)
+    data->callback (records, data->user_data);
+  query_async_data_free (data);
+}
+
+void
+ephy_password_manager_query (EphyPasswordManager              *self,
+                             const char                       *uri,
+                             const char                       *username,
+                             const char                       *username_field,
+                             const char                       *password_field,
+                             EphyPasswordManagerQueryCallback  callback,
+                             gpointer                          user_data)
+{
+  QueryAsyncData *data;
+  GHashTable *attributes;
+
+  g_return_if_fail (EPHY_IS_PASSWORD_MANAGER (self));
+
+  LOG ("Querying password records for (%s, %s, %s, %s)",
+       uri, username, username_field, password_field);
+
+  attributes = get_attributes_table (NULL, uri, username,
+                                     username_field, password_field, -1);
+  data = query_async_data_new (callback, user_data);
+
+  secret_service_search (NULL,
+                         EPHY_FORM_PASSWORD_SCHEMA,
+                         attributes,
+                         SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS,
+                         NULL,
+                         (GAsyncReadyCallback)secret_service_search_cb,
+                         data);
+
+  g_hash_table_unref (attributes);
+}
+
+void
+ephy_password_manager_store_raw (const char          *uri,
+                                 const char          *username,
+                                 const char          *password,
+                                 const char          *username_field,
+                                 const char          *password_field,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  GHashTable *attributes;
+
+  g_return_if_fail (uri);
+  g_return_if_fail (password);
+  g_return_if_fail (!username_field || username);
+  g_return_if_fail (!password_field || password);
+
+  attributes = get_attributes_table (NULL, uri, username,
+                                     username_field, password_field, -1);
+  store_internal (password, attributes, callback, user_data);
+
+  g_hash_table_unref (attributes);
+}
+
+gboolean
+ephy_password_manager_store_finish (GAsyncResult  *result,
+                                    GError       **error)
+{
+  g_return_val_if_fail (!error || !(*error), FALSE);
+  g_return_val_if_fail (g_task_is_valid (result, NULL), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+secret_service_clear_cb (SecretService *service,
+                         GAsyncResult  *result,
+                         gpointer       user_data)
+{
+  GError *error = NULL;
+
+  secret_service_clear_finish (service, result, &error);
+  if (error) {
+    g_warning ("Failed to clear secrets from password schema: %s", error->message);
+    g_error_free (error);
+  }
+}
+
+static void
+ephy_password_manager_forget_record (EphyPasswordManager *self,
+                                     EphyPasswordRecord  *record)
+{
+  GHashTable *attributes;
+
+  g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+  g_assert (EPHY_IS_PASSWORD_RECORD (record));
+
+  attributes = get_attributes_table (ephy_password_record_get_id (record),
+                                     ephy_password_record_get_hostname (record),
+                                     ephy_password_record_get_username (record),
+                                     ephy_password_record_get_username_field (record),
+                                     ephy_password_record_get_password_field (record),
+                                     ephy_synchronizable_get_server_time_modified (EPHY_SYNCHRONIZABLE 
(record)));
+  secret_service_clear (NULL, EPHY_FORM_PASSWORD_SCHEMA, attributes, NULL,
+                        (GAsyncReadyCallback)secret_service_clear_cb, NULL);
+
+  ephy_password_manager_cache_remove (self,
+                                      ephy_password_record_get_hostname (record),
+                                      ephy_password_record_get_username (record));
+  g_hash_table_unref (attributes);
+}
+
+static void
+forget_cb (GSList   *records,
+           gpointer  user_data)
+{
+  EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (user_data);
+  EphyPasswordRecord *record;
+
+  /* We only expect one matching record here. */
+  g_assert (g_slist_length (records) == 1);
+
+  record = EPHY_PASSWORD_RECORD (records->data);
+  g_signal_emit_by_name (self, "synchronizable-deleted", record);
+  ephy_password_manager_forget_record (self, record);
+
+  g_slist_free_full (records, g_object_unref);
+}
+
+void
+ephy_password_manager_forget (EphyPasswordManager *self,
+                              const char          *hostname,
+                              const char          *username,
+                              const char          *username_field,
+                              const char          *password_field)
+{
+  g_return_if_fail (EPHY_IS_PASSWORD_MANAGER (self));
+  g_return_if_fail (hostname);
+  g_return_if_fail (password_field);
+  g_return_if_fail (!username_field || username);
+
+  /* synchronizable-deleted signal needs an EphySynchronizable object,
+   * therefore we need to obtain the password record first and then emit
+   * the signal before clearing the password from the secret schema. */
+  ephy_password_manager_query (self, hostname, username,
+                               username_field, password_field,
+                               forget_cb, self);
+}
+
+static void
+forget_all_cb (GSList   *records,
+               gpointer  user_data)
+{
+  EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (user_data);
+  GHashTable *attributes;
+
+  attributes = secret_attributes_build (EPHY_FORM_PASSWORD_SCHEMA, NULL);
+  secret_service_clear (NULL, EPHY_FORM_PASSWORD_SCHEMA, attributes, NULL,
+                        (GAsyncReadyCallback)secret_service_clear_cb, NULL);
+
+  for (GSList *l = records; l && l->data; l = l->next)
+    g_signal_emit_by_name (self, "synchronizable-deleted", l->data);
+
+  ephy_password_manager_cache_clear (self);
+
+  g_hash_table_unref (attributes);
+  g_slist_free_full (records, g_object_unref);
+}
+
+void
+ephy_password_manager_forget_all (EphyPasswordManager *self)
+{
+  g_return_if_fail (EPHY_IS_PASSWORD_MANAGER (self));
+
+  /* synchronizable-deleted signal needs an EphySynchronizable object, therefore
+   * we need to obtain the password records first and emit the signal for each
+   * one before clearing the secret schema. */
+  ephy_password_manager_query (self, NULL, NULL, NULL, NULL,
+                               forget_all_cb, self);
+}
+
+static const char *
+synchronizable_manager_get_collection_name (EphySynchronizableManager *manager)
+{
+  gboolean sync_with_firefox = g_settings_get_boolean (EPHY_SETTINGS_SYNC,
+                                                       EPHY_PREFS_SYNC_WITH_FIREFOX);
+
+  return sync_with_firefox ? "passwords" : "ephy-passwords";
+}
+
+static GType
+synchronizable_manager_get_synchronizable_type (EphySynchronizableManager *manager)
+{
+  return EPHY_TYPE_PASSWORD_RECORD;
+}
+
+static gboolean
+synchronizable_manager_is_initial_sync (EphySynchronizableManager *manager)
+{
+  return g_settings_get_boolean (EPHY_SETTINGS_SYNC,
+                                 EPHY_PREFS_SYNC_PASSWORDS_INITIAL);
+}
+
+static void
+synchronizable_manager_set_is_initial_sync (EphySynchronizableManager *manager,
+                                            gboolean                   is_initial)
+{
+  g_settings_set_boolean (EPHY_SETTINGS_SYNC,
+                          EPHY_PREFS_SYNC_PASSWORDS_INITIAL,
+                          is_initial);
+}
+
+static double
+synchronizable_manager_get_sync_time (EphySynchronizableManager *manager)
+{
+  return g_settings_get_double (EPHY_SETTINGS_SYNC,
+                                EPHY_PREFS_SYNC_PASSWORDS_TIME);
+}
+
+static void
+synchronizable_manager_set_sync_time (EphySynchronizableManager *manager,
+                                      double                     sync_time)
+{
+  g_settings_set_double (EPHY_SETTINGS_SYNC,
+                         EPHY_PREFS_SYNC_PASSWORDS_TIME,
+                         sync_time);
+}
+
+static void
+synchronizable_manager_add (EphySynchronizableManager *manager,
+                            EphySynchronizable        *synchronizable)
+{
+  EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (manager);
+  EphyPasswordRecord *record = EPHY_PASSWORD_RECORD (synchronizable);
+
+  ephy_password_manger_store_record (self, record);
+}
+
+static void
+synchronizable_manager_remove (EphySynchronizableManager *manager,
+                               EphySynchronizable        *synchronizable)
+{
+  EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (manager);
+  EphyPasswordRecord *record = EPHY_PASSWORD_RECORD (synchronizable);
+
+  ephy_password_manager_forget_record (self, record);
+}
+
+static EphyPasswordRecord *
+get_record_by_id (GSList     *records,
+                  const char *id)
+{
+  g_assert (id);
+
+  for (GSList *l = records; l && l->data; l = l->next) {
+    if (!g_strcmp0 (ephy_password_record_get_id (l->data), id))
+      return l->data;
+  }
+
+  return NULL;
+}
+
+static EphyPasswordRecord *
+get_record_by_parameters (GSList     *records,
+                          const char *hostname,
+                          const char *username,
+                          const char *username_field,
+                          const char *password_field)
+{
+  for (GSList *l = records; l && l->data; l = l->next) {
+    if (!g_strcmp0 (ephy_password_record_get_username (l->data), username) &&
+        !g_strcmp0 (ephy_password_record_get_hostname (l->data), hostname) &&
+        !g_strcmp0 (ephy_password_record_get_username_field (l->data), username_field) &&
+        !g_strcmp0 (ephy_password_record_get_password_field (l->data), password_field))
+      return l->data;
+  }
+
+  return NULL;
+}
+
+static void
+replace_record_cb (GSList   *records,
+                   gpointer  user_data)
+{
+  ReplaceRecordAsyncData *data = (ReplaceRecordAsyncData *)user_data;
+
+  /* We only expect one matching record here. */
+  g_assert (g_slist_length (records) == 1);
+
+  ephy_password_manager_forget_record (data->manager, records->data);
+  ephy_password_manger_store_record (data->manager, data->record);
+
+  replace_record_async_data_free (data);
+}
+
+static void
+ephy_password_manager_replace_record (EphyPasswordManager *self,
+                                      EphyPasswordRecord  *record)
+{
+  g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+  g_assert (EPHY_IS_PASSWORD_RECORD (record));
+
+  ephy_password_manager_query (self,
+                               ephy_password_record_get_hostname (record),
+                               ephy_password_record_get_username (record),
+                               ephy_password_record_get_username_field (record),
+                               ephy_password_record_get_password_field (record),
+                               replace_record_cb,
+                               replace_record_async_data_new (self, record));
+}
+
+static GSList *
+ephy_password_manager_handle_initial_merge (EphyPasswordManager *self,
+                                            GSList              *local_records,
+                                            GSList              *remote_records)
+{
+  EphyPasswordRecord *record;
+  GHashTable *dont_upload;
+  GSList *to_upload = NULL;
+
+  g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+
+  /* A saved password record is uniquely identified by its id or by its tuple of
+   * (hostname, username, username field, password field). When it comes to
+   * importing passwords from server, we may encounter duplicates either by id
+   * or by mentioned tuple. Moreover, we assume that same id means same tuple
+   * but same tuple does not necessarily means same id. This is what our merge
+   * logic is based on. */
+
+  dont_upload = g_hash_table_new (g_str_hash, g_str_equal);
+
+  for (GSList *l = remote_records; l && l->data; l = l->next) {
+    const char *id = ephy_password_record_get_id (l->data);
+    const char *hostname = ephy_password_record_get_hostname (l->data);
+    const char *username = ephy_password_record_get_username (l->data);
+    const char *password = ephy_password_record_get_password (l->data);
+    const char *username_field = ephy_password_record_get_username_field (l->data);
+    const char *password_field = ephy_password_record_get_password_field (l->data);
+    guint64 timestamp = ephy_password_record_get_time_password_changed (l->data);
+    double server_time_modified = ephy_synchronizable_get_server_time_modified (l->data);
+
+    record = get_record_by_id (local_records, id);
+    if (record) {
+      if (!g_strcmp0 (ephy_password_record_get_password (record), password)) {
+        /* Same id, same password. Nothing to do. */
+        g_hash_table_add (dont_upload, (char *)id);
+      } else {
+        /* Same id, different password. Keep the most recent modified. */
+        if (ephy_password_record_get_time_password_changed (record) > timestamp) {
+          /* Local record is newer. Keep it and upload it to server.
+           * Also, must keep the most recent server time modified. */
+          if (ephy_synchronizable_get_server_time_modified (EPHY_SYNCHRONIZABLE (record)) < 
server_time_modified) {
+            ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (record), 
server_time_modified);
+            ephy_password_manager_replace_record (self, record);
+          }
+        } else {
+          /* Remote record is newer. Forget local record and store remote record. */
+          ephy_password_manager_forget_record (self, record);
+          ephy_password_manger_store_record (self, l->data);
+          g_hash_table_add (dont_upload, (char *)id);
+        }
+      }
+    } else {
+      record = get_record_by_parameters (local_records,
+                                         hostname, username,
+                                         username_field, password_field);
+      if (record) {
+        /* Different id, same tuple. Keep the most recent modified. */
+        if (ephy_password_record_get_time_password_changed (record) > timestamp) {
+          /* Local record is newer. Keep it, upload it and delete remote record from server. */
+          g_signal_emit_by_name (self, "synchronizable-deleted", l->data);
+        } else {
+          /* Remote record is newer. Forget local record and store remote record. */
+          ephy_password_manager_forget_record (self, record);
+          ephy_password_manger_store_record (self, l->data);
+          g_hash_table_add (dont_upload, (char *)id);
+        }
+      } else {
+        /* Different id, different tuple. This is a new record, add it. */
+        ephy_password_manger_store_record (self, l->data);
+        g_hash_table_add (dont_upload, (char *)id);
+      }
+    }
+  }
+
+  /* Set the remaining local records to be uploaded to server. */
+  for (GSList *l = local_records; l && l->data; l = l->next) {
+    record = EPHY_PASSWORD_RECORD (l->data);
+    if (!g_hash_table_contains (dont_upload, ephy_password_record_get_id (record)))
+      to_upload = g_slist_prepend (to_upload, g_object_ref (record));
+  }
+
+  g_hash_table_unref (dont_upload);
+
+  return to_upload;
+}
+
+static GSList *
+ephy_password_manager_handle_regular_merge (EphyPasswordManager *self,
+                                            GSList              *local_records,
+                                            GSList              *deleted_records,
+                                            GSList              *updated_records)
+{
+  EphyPasswordRecord *record;
+  GSList *to_upload = NULL;
+
+  g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+
+  for (GSList *l = deleted_records; l && l->data; l = l->next) {
+    record = get_record_by_id (local_records, ephy_password_record_get_id (l->data));
+    if (record)
+      ephy_password_manager_forget_record (self, record);
+  }
+
+  /* See comment in ephy_password_manager_handle_initial_merge.
+   * Same logic applies here too. */
+  for (GSList *l = updated_records; l && l->data; l = l->next) {
+    const char *id = ephy_password_record_get_id (l->data);
+    const char *hostname = ephy_password_record_get_hostname (l->data);
+    const char *username = ephy_password_record_get_username (l->data);
+    const char *username_field = ephy_password_record_get_username_field (l->data);
+    const char *password_field = ephy_password_record_get_password_field (l->data);
+    guint64 timestamp = ephy_password_record_get_time_password_changed (l->data);
+
+    record = get_record_by_id (local_records, id);
+    if (record) {
+      /* Same id. Overwrite local record. */
+      ephy_password_manager_forget_record (self, record);
+      ephy_password_manger_store_record (self, l->data);
+    } else {
+      record = get_record_by_parameters (local_records,
+                                         hostname, username,
+                                         username_field, password_field);
+      if (record) {
+        /* Different id, same tuple. Keep the most recent modified. */
+        if (ephy_password_record_get_time_password_changed (record) > timestamp) {
+          /* Local record is newer. Keep it, upload it and delete remote record from server. */
+          to_upload = g_slist_prepend (to_upload, g_object_ref (record));
+          g_signal_emit_by_name (self, "synchronizable-deleted", l->data);
+        } else {
+          /* Remote record is newer. Forget local record and store remote record. */
+          ephy_password_manager_forget_record (self, record);
+          ephy_password_manger_store_record (self, l->data);
+        }
+      } else {
+        /* Different id, different tuple. This is a new record, add it. */
+        ephy_password_manger_store_record (self, l->data);
+      }
+    }
+  }
+
+  return to_upload;
+}
+
+static void
+merge_cb (GSList   *records,
+          gpointer  user_data)
+{
+  MergeAsyncData *data = (MergeAsyncData *)user_data;
+  GSList *to_upload = NULL;
+
+  if (data->is_initial)
+    to_upload = ephy_password_manager_handle_initial_merge (data->manager,
+                                                            records,
+                                                            data->remotes_updated);
+  else
+    to_upload = ephy_password_manager_handle_regular_merge (data->manager,
+                                                            records,
+                                                            data->remotes_deleted,
+                                                            data->remotes_updated);
+
+  data->callback (to_upload, data->user_data);
+
+  g_slist_free_full (records, g_object_unref);
+  merge_async_data_free (data);
+}
+
+static void
+synchronizable_manager_merge (EphySynchronizableManager              *manager,
+                              gboolean                                is_initial,
+                              GSList                                 *remotes_deleted,
+                              GSList                                 *remotes_updated,
+                              EphySynchronizableManagerMergeCallback  callback,
+                              gpointer                                user_data)
+{
+  EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (manager);
+
+  ephy_password_manager_query (self, NULL, NULL, NULL, NULL,
+                               merge_cb,
+                               merge_async_data_new (self,
+                                                     is_initial,
+                                                     remotes_deleted,
+                                                     remotes_updated,
+                                                     callback,
+                                                     user_data));
+}
+
+static void
+ephy_synchronizable_manager_iface_init (EphySynchronizableManagerInterface *iface)
+{
+  iface->get_collection_name = synchronizable_manager_get_collection_name;
+  iface->get_synchronizable_type = synchronizable_manager_get_synchronizable_type;
+  iface->is_initial_sync = synchronizable_manager_is_initial_sync;
+  iface->set_is_initial_sync = synchronizable_manager_set_is_initial_sync;
+  iface->get_sync_time = synchronizable_manager_get_sync_time;
+  iface->set_sync_time = synchronizable_manager_set_sync_time;
+  iface->add = synchronizable_manager_add;
+  iface->remove = synchronizable_manager_remove;
+  iface->merge = synchronizable_manager_merge;
+}
diff --git a/lib/sync/ephy-password-manager.h b/lib/sync/ephy-password-manager.h
new file mode 100644
index 0000000..70c08cd
--- /dev/null
+++ b/lib/sync/ephy-password-manager.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany 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 Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ephy-password-record.h"
+
+#include <glib-object.h>
+#include <libsecret/secret.h>
+
+G_BEGIN_DECLS
+
+const SecretSchema *ephy_password_manager_get_password_schema (void) G_GNUC_CONST;
+
+#define ID_KEY                    "id"
+#define HOSTNAME_KEY              "uri"
+#define USERNAME_FIELD_KEY        "form_username"
+#define PASSWORD_FIELD_KEY        "form_password"
+#define USERNAME_KEY              "username"
+#define SERVER_TIME_MODIFIED_KEY  "server_time_modified"
+
+#define EPHY_FORM_PASSWORD_SCHEMA ephy_password_manager_get_password_schema ()
+
+#define EPHY_TYPE_PASSWORD_MANAGER (ephy_password_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphyPasswordManager, ephy_password_manager, EPHY, PASSWORD_MANAGER, GObject)
+
+typedef void (*EphyPasswordManagerQueryCallback) (GSList *records, gpointer user_data);
+
+EphyPasswordManager *ephy_password_manager_new                      (void);
+GSList              *ephy_password_manager_get_cached_users_for_uri (EphyPasswordManager *self,
+                                                                     const char          *uri);
+void                 ephy_password_manager_save                     (EphyPasswordManager *self,
+                                                                     const char          *uri,
+                                                                     const char          *username,
+                                                                     const char          *password,
+                                                                     const char          *username_field,
+                                                                     const char          *password_field,
+                                                                     gboolean             is_new);
+void                 ephy_password_manager_query                    (EphyPasswordManager              *self,
+                                                                     const char                       *uri,
+                                                                     const char                       
*username,
+                                                                     const char                       
*username_field,
+                                                                     const char                       
*password_field,
+                                                                     EphyPasswordManagerQueryCallback  
callback,
+                                                                     gpointer                          
user_data);
+void                 ephy_password_manager_forget                    (EphyPasswordManager *self,
+                                                                      const char          *hostname,
+                                                                      const char          *username,
+                                                                      const char          *username_field,
+                                                                      const char          *password_field);
+void                 ephy_password_manager_forget_all                (EphyPasswordManager *self);
+/* Note: Below functions are deprecated and should not be used in newly written code.
+ * The only reason they still exist is that the profile migrator expects them. */
+void                 ephy_password_manager_store_raw                 (const char          *uri,
+                                                                      const char          *username,
+                                                                      const char          *password,
+                                                                      const char          *username_field,
+                                                                      const char          *password_field,
+                                                                      GAsyncReadyCallback  callback,
+                                                                      gpointer             user_data);
+gboolean             ephy_password_manager_store_finish              (GAsyncResult  *result,
+                                                                      GError       **error);
+
+G_END_DECLS
diff --git a/lib/sync/ephy-password-record.c b/lib/sync/ephy-password-record.c
new file mode 100644
index 0000000..63ffc6b
--- /dev/null
+++ b/lib/sync/ephy-password-record.c
@@ -0,0 +1,370 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany 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 Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-password-record.h"
+
+#include "ephy-synchronizable.h"
+
+struct _EphyPasswordRecord {
+  GObject parent_instance;
+
+  char    *id;
+  char    *hostname;
+  char    *form_submit_url;
+  char    *username;
+  char    *password;
+  char    *username_field;
+  char    *password_field;
+  guint64  time_created;
+  guint64  time_password_changed;
+
+  double   server_time_modified;
+};
+
+static void json_serializable_iface_init (JsonSerializableIface *iface);
+static void ephy_synchronizable_iface_init (EphySynchronizableInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (EphyPasswordRecord, ephy_password_record, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (JSON_TYPE_SERIALIZABLE,
+                                                json_serializable_iface_init)
+                         G_IMPLEMENT_INTERFACE (EPHY_TYPE_SYNCHRONIZABLE,
+                                                ephy_synchronizable_iface_init))
+
+enum {
+  PROP_0,
+  PROP_ID,                    /* Firefox Sync */
+  PROP_HOSTNAME,              /* Epiphany && Firefox Sync */
+  PROP_FORM_SUBMIT_URL,       /* Firefox Sync */
+  PROP_USERNAME,              /* Epiphany && Firefox Sync */
+  PROP_PASSWORD,              /* Epiphany && Firefox Sync */
+  PROP_USERNAME_FIELD,        /* Epiphany && Firefox Sync */
+  PROP_PASSWORD_FIELD,        /* Epiphany && Firefox Sync */
+  PROP_TIME_CREATED,          /* Firefox Sync */
+  PROP_TIME_PASSWORD_CHANGED, /* Firefox Sync */
+  LAST_PROP
+};
+
+static GParamSpec *obj_properties[LAST_PROP];
+
+static void
+ephy_password_record_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  EphyPasswordRecord *self = EPHY_PASSWORD_RECORD (object);
+
+  switch (prop_id) {
+    case PROP_ID:
+      g_free (self->id);
+      self->id = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_HOSTNAME:
+      g_free (self->hostname);
+      self->hostname = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_FORM_SUBMIT_URL:
+      g_free (self->form_submit_url);
+      self->form_submit_url = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_USERNAME:
+      g_free (self->username);
+      self->username = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_PASSWORD:
+      g_free (self->password);
+      self->password = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_USERNAME_FIELD:
+      g_free (self->username_field);
+      self->username_field = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_PASSWORD_FIELD:
+      g_free (self->password_field);
+      self->password_field = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_TIME_CREATED:
+      self->time_created = g_value_get_uint64 (value);
+      break;
+    case PROP_TIME_PASSWORD_CHANGED:
+      self->time_password_changed = g_value_get_uint64 (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+ephy_password_record_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  EphyPasswordRecord *self = EPHY_PASSWORD_RECORD (object);
+
+  switch (prop_id) {
+    case PROP_ID:
+      g_value_set_string (value, self->id);
+      break;
+    case PROP_HOSTNAME:
+      g_value_set_string (value, self->hostname);
+      break;
+    case PROP_FORM_SUBMIT_URL:
+      g_value_set_string (value, self->form_submit_url);
+      break;
+    case PROP_USERNAME:
+      g_value_set_string (value, self->username);
+      break;
+    case PROP_PASSWORD:
+      g_value_set_string (value, self->password);
+      break;
+    case PROP_USERNAME_FIELD:
+      g_value_set_string (value, self->username_field);
+      break;
+    case PROP_PASSWORD_FIELD:
+      g_value_set_string (value, self->password_field);
+      break;
+    case PROP_TIME_CREATED:
+      g_value_set_uint64 (value, self->time_created);
+      break;
+    case PROP_TIME_PASSWORD_CHANGED:
+      g_value_set_uint64 (value, self->time_password_changed);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+ephy_password_record_dispose (GObject *object)
+{
+  EphyPasswordRecord *self = EPHY_PASSWORD_RECORD (object);
+
+  g_clear_pointer (&self->id, g_free);
+  g_clear_pointer (&self->hostname, g_free);
+  g_clear_pointer (&self->form_submit_url, g_free);
+  g_clear_pointer (&self->username, g_free);
+  g_clear_pointer (&self->password, g_free);
+  g_clear_pointer (&self->username_field, g_free);
+  g_clear_pointer (&self->password_field, g_free);
+
+  G_OBJECT_CLASS (ephy_password_record_parent_class)->dispose (object);
+}
+
+static void
+ephy_password_record_class_init (EphyPasswordRecordClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->set_property = ephy_password_record_set_property;
+  object_class->get_property = ephy_password_record_get_property;
+  object_class->dispose = ephy_password_record_dispose;
+
+  obj_properties[PROP_ID] =
+    g_param_spec_string ("id",
+                         "Id",
+                         "Id of the password record",
+                         "Default id",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_HOSTNAME] =
+    g_param_spec_string ("hostname",
+                         "Hostname",
+                         "Hostname url that password is applicable at",
+                         "Default hostname",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_FORM_SUBMIT_URL] =
+    g_param_spec_string ("formSubmitURL",
+                         "Form submit URL",
+                         "Submission URL set by form",
+                         "Default form submit URL",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_USERNAME] =
+    g_param_spec_string ("username",
+                         "Username",
+                         "Username to log in as",
+                         "Default username",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_PASSWORD] =
+    g_param_spec_string ("password",
+                         "Password",
+                         "Password for the username",
+                         "Default password",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_USERNAME_FIELD] =
+    g_param_spec_string ("usernameField",
+                         "Username field",
+                         "HTML field name of the username",
+                         "Default username field",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_PASSWORD_FIELD] =
+    g_param_spec_string ("passwordField",
+                         "Password field",
+                         "HTML field name of the password",
+                         "Default password field",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_TIME_CREATED] =
+    g_param_spec_uint64 ("timeCreated",
+                         "Time created",
+                         "Unix timestamp in milliseconds at which the password was created",
+                         0,
+                         G_MAXUINT64,
+                         0,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_TIME_PASSWORD_CHANGED] =
+    g_param_spec_uint64 ("timePasswordChanged",
+                         "Time password changed",
+                         "Unix timestamp in milliseconds at which the password was changed",
+                         0,
+                         G_MAXUINT64,
+                         0,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
+}
+
+static void
+ephy_password_record_init (EphyPasswordRecord *self)
+{
+}
+
+EphyPasswordRecord *
+ephy_password_record_new (const char *id,
+                          const char *hostname,
+                          const char *username,
+                          const char *password,
+                          const char *username_field,
+                          const char *password_field,
+                          guint64     time_created,
+                          guint64     time_password_changed)
+{
+  return EPHY_PASSWORD_RECORD (g_object_new (EPHY_TYPE_PASSWORD_RECORD,
+                                             "id", id,
+                                             "hostname", hostname,
+                                             "formSubmitURL", hostname,
+                                             "username", username,
+                                             "password", password,
+                                             "usernameField", username_field,
+                                             "passwordField", password_field,
+                                             "timeCreated", time_created,
+                                             "timePasswordChanged", time_password_changed,
+                                             NULL));
+}
+
+const char *
+ephy_password_record_get_id (EphyPasswordRecord *self)
+{
+  g_return_val_if_fail (EPHY_IS_PASSWORD_RECORD (self), NULL);
+
+  return self->id;
+}
+
+const char *
+ephy_password_record_get_hostname (EphyPasswordRecord *self)
+{
+  g_return_val_if_fail (EPHY_IS_PASSWORD_RECORD (self), NULL);
+
+  return self->hostname;
+}
+
+const char *
+ephy_password_record_get_username (EphyPasswordRecord *self)
+{
+  g_return_val_if_fail (EPHY_IS_PASSWORD_RECORD (self), NULL);
+
+  return self->username;
+}
+
+const char *
+ephy_password_record_get_password (EphyPasswordRecord *self)
+{
+  g_return_val_if_fail (EPHY_IS_PASSWORD_RECORD (self), NULL);
+
+  return self->password;
+}
+
+void
+ephy_password_record_set_password (EphyPasswordRecord *self,
+                                   const char         *password)
+{
+  g_return_if_fail (EPHY_IS_PASSWORD_RECORD (self));
+
+  g_free (self->password);
+  self->password = g_strdup (password);
+}
+
+const char *
+ephy_password_record_get_username_field (EphyPasswordRecord *self)
+{
+  g_return_val_if_fail (EPHY_IS_PASSWORD_RECORD (self), NULL);
+
+  return self->username_field;
+}
+
+const char *
+ephy_password_record_get_password_field (EphyPasswordRecord *self)
+{
+  g_return_val_if_fail (EPHY_IS_PASSWORD_RECORD (self), NULL);
+
+  return self->password_field;
+}
+
+guint64
+ephy_password_record_get_time_password_changed (EphyPasswordRecord *self)
+{
+  g_return_val_if_fail (EPHY_IS_PASSWORD_RECORD (self), 0);
+
+  return self->time_password_changed;
+}
+
+static void
+json_serializable_iface_init (JsonSerializableIface *iface)
+{
+  iface->serialize_property = json_serializable_default_serialize_property;
+  iface->deserialize_property = json_serializable_default_deserialize_property;
+}
+
+static const char *
+synchronizable_get_id (EphySynchronizable *synchronizable)
+{
+  return ephy_password_record_get_id (EPHY_PASSWORD_RECORD (synchronizable));
+}
+
+static double
+synchronizable_get_server_time_modified (EphySynchronizable *synchronizable)
+{
+  return EPHY_PASSWORD_RECORD (synchronizable)->server_time_modified;
+}
+
+static void
+synchronizable_set_server_time_modified (EphySynchronizable *synchronizable,
+                                         double              server_time_modified)
+{
+  EPHY_PASSWORD_RECORD (synchronizable)->server_time_modified = server_time_modified;
+}
+
+static void
+ephy_synchronizable_iface_init (EphySynchronizableInterface *iface)
+{
+  iface->get_id = synchronizable_get_id;
+  iface->get_server_time_modified = synchronizable_get_server_time_modified;
+  iface->set_server_time_modified = synchronizable_set_server_time_modified;
+  iface->to_bso = ephy_synchronizable_default_to_bso;
+}
diff --git a/lib/sync/ephy-password-record.h b/lib/sync/ephy-password-record.h
new file mode 100644
index 0000000..e4d1106
--- /dev/null
+++ b/lib/sync/ephy-password-record.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany 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 Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <libsecret/secret.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_PASSWORD_RECORD (ephy_password_record_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphyPasswordRecord, ephy_password_record, EPHY, PASSWORD_RECORD, GObject)
+
+EphyPasswordRecord *ephy_password_record_new                        (const char *id,
+                                                                     const char *hostname,
+                                                                     const char *username,
+                                                                     const char *password,
+                                                                     const char *username_field,
+                                                                     const char *password_field,
+                                                                     guint64     time_created,
+                                                                     guint64     time_password_changed);
+const char         *ephy_password_record_get_id                     (EphyPasswordRecord *self);
+const char         *ephy_password_record_get_hostname               (EphyPasswordRecord *self);
+const char         *ephy_password_record_get_username               (EphyPasswordRecord *self);
+const char         *ephy_password_record_get_password               (EphyPasswordRecord *self);
+void                ephy_password_record_set_password               (EphyPasswordRecord *self,
+                                                                     const char         *password);
+const char         *ephy_password_record_get_username_field         (EphyPasswordRecord *self);
+const char         *ephy_password_record_get_password_field         (EphyPasswordRecord *self);
+guint64             ephy_password_record_get_time_password_changed  (EphyPasswordRecord *self);
+
+G_END_DECLS
diff --git a/lib/sync/ephy-sync-service.c b/lib/sync/ephy-sync-service.c
index 7d35fb7..657f1d9 100644
--- a/lib/sync/ephy-sync-service.c
+++ b/lib/sync/ephy-sync-service.c
@@ -75,6 +75,8 @@ struct _EphySyncService {
 
   char                 *certificate;
   SyncCryptoRSAKeyPair *rsa_key_pair;
+
+  gboolean sync_periodically;
 };
 
 G_DEFINE_TYPE (EphySyncService, ephy_sync_service, G_TYPE_OBJECT);
@@ -95,6 +97,14 @@ static const char * const secrets[LAST_SECRET] = {
 };
 
 enum {
+  PROP_0,
+  PROP_SYNC_PERIODICALLY,
+  LAST_PROP
+};
+
+static GParamSpec *obj_properties[LAST_PROP];
+
+enum {
   STORE_FINISHED,
   SIGN_IN_ERROR,
   SYNC_FREQUENCY_CHANGED,
@@ -131,6 +141,8 @@ typedef struct {
   EphySynchronizableManager *manager;
   gboolean                   is_initial;
   gboolean                   is_last;
+  GSList                    *remotes_deleted;
+  GSList                    *remotes_updated;
 } SyncCollectionAsyncData;
 
 typedef struct {
@@ -233,6 +245,8 @@ sync_collection_async_data_new (EphySyncService           *service,
   data->manager = g_object_ref (manager);
   data->is_initial = is_initial;
   data->is_last = is_last;
+  data->remotes_deleted = NULL;
+  data->remotes_updated = NULL;
 
   return data;
 }
@@ -244,6 +258,8 @@ sync_collection_async_data_free (SyncCollectionAsyncData *data)
 
   g_object_unref (data->service);
   g_object_unref (data->manager);
+  g_slist_free_full (data->remotes_deleted, g_object_unref);
+  g_slist_free_full (data->remotes_updated, g_object_unref);
   g_slice_free (SyncCollectionAsyncData, data);
 }
 
@@ -273,6 +289,40 @@ sync_async_data_free (SyncAsyncData *data)
   g_slice_free (SyncAsyncData, data);
 }
 
+static void
+ephy_sync_service_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  EphySyncService *self = EPHY_SYNC_SERVICE (object);
+
+  switch (prop_id) {
+    case PROP_SYNC_PERIODICALLY:
+      self->sync_periodically = g_value_get_boolean (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+ephy_sync_service_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  EphySyncService *self = EPHY_SYNC_SERVICE (object);
+
+  switch (prop_id) {
+    case PROP_SYNC_PERIODICALLY:
+      g_value_set_boolean (value, self->sync_periodically);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
 static const char *
 ephy_sync_service_get_secret (EphySyncService *self,
                               const char      *name)
@@ -943,6 +993,7 @@ ephy_sync_service_delete_synchronizable (EphySyncService           *self,
   char *record;
   char *payload;
   char *body;
+  char *id_safe;
   const char *collection;
   const char *id;
 
@@ -951,9 +1002,12 @@ ephy_sync_service_delete_synchronizable (EphySyncService           *self,
   g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
   g_assert (ephy_sync_service_is_signed_in (self));
 
-  id = ephy_synchronizable_get_id (synchronizable);
   collection = ephy_synchronizable_manager_get_collection_name (manager);
-  endpoint = g_strdup_printf ("storage/%s/%s", collection, id);
+  id = ephy_synchronizable_get_id (synchronizable);
+  /* Firefox uses UUIDs with curly braces as IDs for saved passwords records.
+   * Curly braces are unsafe characters in URLs so they must be encoded. */
+  id_safe = soup_uri_encode (id, NULL);
+  endpoint = g_strdup_printf ("storage/%s/%s", collection, id_safe);
 
   node = json_node_new (JSON_NODE_OBJECT);
   object = json_object_new ();
@@ -973,6 +1027,7 @@ ephy_sync_service_delete_synchronizable (EphySyncService           *self,
                                            SOUP_METHOD_PUT, body, -1, -1,
                                            delete_synchronizable_cb, NULL);
 
+  g_free (id_safe);
   g_free (endpoint);
   g_free (record);
   g_free (payload);
@@ -1042,6 +1097,7 @@ ephy_sync_service_download_synchronizable (EphySyncService           *self,
 {
   SyncAsyncData *data;
   char *endpoint;
+  char *id_safe;
   const char *collection;
   const char *id;
 
@@ -1052,7 +1108,10 @@ ephy_sync_service_download_synchronizable (EphySyncService           *self,
 
   id = ephy_synchronizable_get_id (synchronizable);
   collection = ephy_synchronizable_manager_get_collection_name (manager);
-  endpoint = g_strdup_printf ("storage/%s/%s", collection, id);
+  /* Firefox uses UUIDs with curly braces as IDs for saved passwords records.
+   * Curly braces are unsafe characters in URLs so they must be encoded. */
+  id_safe = soup_uri_encode (id, NULL);
+  endpoint = g_strdup_printf ("storage/%s/%s", collection, id_safe);
   data = sync_async_data_new (self, manager, synchronizable);
 
   LOG ("Downloading object with id %s...", id);
@@ -1061,6 +1120,7 @@ ephy_sync_service_download_synchronizable (EphySyncService           *self,
                                            download_synchronizable_cb, data);
 
   g_free (endpoint);
+  g_free (id_safe);
 }
 
 static void
@@ -1099,6 +1159,7 @@ ephy_sync_service_upload_synchronizable (EphySyncService           *self,
   JsonNode *bso;
   char *endpoint;
   char *body;
+  char *id_safe;
   const char *collection;
   const char *id;
 
@@ -1111,7 +1172,10 @@ ephy_sync_service_upload_synchronizable (EphySyncService           *self,
   bundle = ephy_sync_service_get_key_bundle (self, collection);
   bso = ephy_synchronizable_to_bso (synchronizable, bundle);
   id = ephy_synchronizable_get_id (synchronizable);
-  endpoint = g_strdup_printf ("storage/%s/%s", collection, id);
+  /* Firefox uses UUIDs with curly braces as IDs for saved passwords records.
+   * Curly braces are unsafe characters in URLs so they must be encoded. */
+  id_safe = soup_uri_encode (id, NULL);
+  endpoint = g_strdup_printf ("storage/%s/%s", collection, id_safe);
   data = sync_async_data_new (self, manager, synchronizable);
   body = json_to_string (bso, FALSE);
 
@@ -1120,6 +1184,7 @@ ephy_sync_service_upload_synchronizable (EphySyncService           *self,
                                            ephy_synchronizable_get_server_time_modified (synchronizable),
                                            upload_synchronizable_cb, data);
 
+  g_free (id_safe);
   g_free (body);
   g_free (endpoint);
   json_node_unref (bso);
@@ -1127,6 +1192,23 @@ ephy_sync_service_upload_synchronizable (EphySyncService           *self,
 }
 
 static void
+merge_finished_cb (GSList   *to_upload,
+                   gpointer  user_data)
+{
+  SyncCollectionAsyncData *data = (SyncCollectionAsyncData *)user_data;
+
+  for (GSList *l = to_upload; l && l->data; l = l->next)
+    ephy_sync_service_upload_synchronizable (data->service, data->manager, l->data);
+
+  if (data->is_last)
+    g_signal_emit (data->service, signals[SYNC_FINISHED], 0);
+
+  if (to_upload)
+    g_slist_free_full (to_upload, g_object_unref);
+  sync_collection_async_data_free (data);
+}
+
+static void
 sync_collection_cb (SoupSession *session,
                     SoupMessage *msg,
                     gpointer     user_data)
@@ -1137,9 +1219,6 @@ sync_collection_cb (SoupSession *session,
   JsonNode *node = NULL;
   JsonArray *array = NULL;
   GError *error = NULL;
-  GSList *remotes_updated = NULL;
-  GSList *remotes_deleted = NULL;
-  GSList *to_upload = NULL;
   GType type;
   const char *collection;
   const char *last_modified;
@@ -1150,21 +1229,19 @@ sync_collection_cb (SoupSession *session,
   if (msg->status_code != 200) {
     g_warning ("Failed to get records in collection %s. Status code: %u, response: %s",
                collection, msg->status_code, msg->response_body->data);
-    goto out;
+    goto out_error;
   }
   node = json_from_string (msg->response_body->data, &error);
   if (error) {
     g_warning ("Response is not a valid JSON: %s", error->message);
-    goto out;
+    goto out_error;
   }
   array = json_node_get_array (node);
   if (!array) {
     g_warning ("JSON node does not hold an array");
-    goto out;
+    goto out_error;
   }
 
-  LOG ("Found %u new remote objects...", json_array_get_length (array));
-
   type = ephy_synchronizable_manager_get_synchronizable_type (data->manager);
   bundle = ephy_sync_service_get_key_bundle (data->service, collection);
   for (guint i = 0; i < json_array_get_length (array); i++) {
@@ -1175,36 +1252,35 @@ sync_collection_cb (SoupSession *session,
       continue;
     }
     if (is_deleted)
-      remotes_deleted = g_slist_prepend (remotes_deleted, remote);
+      data->remotes_deleted = g_slist_prepend (data->remotes_deleted, remote);
     else
-      remotes_updated = g_slist_prepend (remotes_updated, remote);
+      data->remotes_updated = g_slist_prepend (data->remotes_updated, remote);
   }
 
-  to_upload = ephy_synchronizable_manager_merge (data->manager, data->is_initial,
-                                                 remotes_deleted, remotes_updated);
-  for (GSList *l = to_upload; l && l->data; l = l->next)
-    ephy_sync_service_upload_synchronizable (data->service, data->manager, l->data);
+  LOG ("Found %u deleted objects and %u new/updated objects in %s collection",
+       g_slist_length (data->remotes_deleted),
+       g_slist_length (data->remotes_updated),
+       collection);
 
   /* Update sync time. */
   last_modified = soup_message_headers_get_one (msg->response_headers, "X-Last-Modified");
   ephy_synchronizable_manager_set_sync_time (data->manager, g_ascii_strtod (last_modified, NULL));
   ephy_synchronizable_manager_set_is_initial_sync (data->manager, FALSE);
 
-out:
+  ephy_synchronizable_manager_merge (data->manager, data->is_initial,
+                                     data->remotes_deleted, data->remotes_updated,
+                                     merge_finished_cb, data);
+  goto out_no_error;
+
+out_error:
   if (data->is_last)
     g_signal_emit (data->service, signals[SYNC_FINISHED], 0);
-
-  if (to_upload)
-    g_slist_free_full (to_upload, g_object_unref);
-  if (remotes_updated)
-    g_slist_free_full (remotes_updated, g_object_unref);
-  if (remotes_deleted)
-    g_slist_free_full (remotes_deleted, g_object_unref);
+  sync_collection_async_data_free (data);
+out_no_error:
   if (node)
     json_node_unref (node);
   if (error)
     g_error_free (error);
-  sync_collection_async_data_free (data);
 }
 
 static void
@@ -1447,7 +1523,8 @@ load_secrets_cb (SecretService   *service,
     ephy_sync_service_set_secret (self, l->data,
                                   json_object_get_string_member (object, l->data));
 
-  ephy_sync_service_start_periodical_sync (self);
+  if (self->sync_periodically)
+    ephy_sync_service_start_periodical_sync (self);
   goto out_no_error;
 
 out_error:
@@ -1572,8 +1649,19 @@ ephy_sync_service_class_init (EphySyncServiceClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
+  object_class->set_property = ephy_sync_service_set_property;
+  object_class->get_property = ephy_sync_service_get_property;
   object_class->dispose = ephy_sync_service_dispose;
 
+  obj_properties[PROP_SYNC_PERIODICALLY] =
+    g_param_spec_boolean ("sync-periodically",
+                          "Sync periodically",
+                          "Whether should periodically sync data",
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
+
   signals[STORE_FINISHED] =
     g_signal_new ("sync-secrets-store-finished",
                   EPHY_TYPE_SYNC_SERVICE,
@@ -1616,9 +1704,11 @@ ephy_sync_service_init (EphySyncService *self)
   self->storage_queue = g_queue_new ();
   self->secrets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
 
-  settings = ephy_embed_prefs_get_settings ();
-  user_agent = webkit_settings_get_user_agent (settings);
-  g_object_set (self->session, "user-agent", user_agent, NULL);
+  if (self->sync_periodically) {
+    settings = ephy_embed_prefs_get_settings ();
+    user_agent = webkit_settings_get_user_agent (settings);
+    g_object_set (self->session, "user-agent", user_agent, NULL);
+  }
 
   account = g_settings_get_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_USER);
   if (g_strcmp0 (account, "")) {
@@ -1634,9 +1724,11 @@ ephy_sync_service_init (EphySyncService *self)
 }
 
 EphySyncService *
-ephy_sync_service_new (void)
+ephy_sync_service_new (gboolean sync_periodically)
 {
-  return EPHY_SYNC_SERVICE (g_object_new (EPHY_TYPE_SYNC_SERVICE, NULL));
+  return EPHY_SYNC_SERVICE (g_object_new (EPHY_TYPE_SYNC_SERVICE,
+                                          "sync-periodically", sync_periodically,
+                                          NULL));
 }
 
 gboolean
@@ -2182,6 +2274,7 @@ ephy_sync_service_do_sign_out (EphySyncService *self)
 
   g_settings_set_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_USER, "");
   g_settings_set_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_BOOKMARKS_INITIAL, TRUE);
+  g_settings_set_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_PASSWORDS_INITIAL, TRUE);
 }
 
 void
@@ -2198,6 +2291,7 @@ ephy_sync_service_start_periodical_sync (EphySyncService *self)
 {
   g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
   g_return_if_fail (ephy_sync_service_is_signed_in (self));
+  g_return_if_fail (self->sync_periodically);
 
   ephy_sync_service_sync (self);
   ephy_sync_service_schedule_periodical_sync (self);
diff --git a/lib/sync/ephy-sync-service.h b/lib/sync/ephy-sync-service.h
index b45892c..369b9a6 100644
--- a/lib/sync/ephy-sync-service.h
+++ b/lib/sync/ephy-sync-service.h
@@ -30,7 +30,7 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (EphySyncService, ephy_sync_service, EPHY, SYNC_SERVICE, GObject)
 
-EphySyncService   *ephy_sync_service_new                    (void);
+EphySyncService   *ephy_sync_service_new                    (gboolean sync_periodically);
 gboolean           ephy_sync_service_is_signed_in           (EphySyncService *self);
 const char        *ephy_sync_service_get_sync_user          (EphySyncService *self);
 void               ephy_sync_service_do_sign_in             (EphySyncService *self,
diff --git a/lib/sync/ephy-synchronizable-manager.c b/lib/sync/ephy-synchronizable-manager.c
index c25a5cb..01c27f1 100644
--- a/lib/sync/ephy-synchronizable-manager.c
+++ b/lib/sync/ephy-synchronizable-manager.c
@@ -227,23 +227,28 @@ ephy_synchronizable_manager_remove (EphySynchronizableManager *manager,
  *                   objects that were removed remotely from the server.
  * @remotes_updated: (transfer none): a #GSList holding the #EphySynchronizable
  *                   objects that were updated remotely on the server.
+ * @callback: an #EphySynchronizableManagerMergeCallback that will be called
+ *            when the merge is complete.
+ * @user_data: user data to pass to the @callback.
  *
  * Merges a list of remote-deleted objects and a list of remote-updated objects
- * with the local objects in @manager's collection.
- *
- * Return value: (transfer full): a #GSList holding the #EphySynchronizable
- *               objects that need to be re-uploaded to server.
+ * with the local objects in @manager's collection. When the merge is completed,
+ * @callback will be invoked with a list of objects that need to be (re)uploaded
+ * to server. Transfer full for the list of objects to be (re)uploaded.
  **/
-GSList *
-ephy_synchronizable_manager_merge (EphySynchronizableManager *manager,
-                                   gboolean                   is_initial,
-                                   GSList                    *remotes_deleted,
-                                   GSList                    *remotes_updated)
+void
+ephy_synchronizable_manager_merge (EphySynchronizableManager              *manager,
+                                   gboolean                                is_initial,
+                                   GSList                                 *remotes_deleted,
+                                   GSList                                 *remotes_updated,
+                                   EphySynchronizableManagerMergeCallback  callback,
+                                   gpointer                                user_data)
 {
   EphySynchronizableManagerInterface *iface;
 
-  g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager), NULL);
+  g_return_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+  g_return_if_fail (callback);
 
   iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
-  return iface->merge (manager, is_initial, remotes_deleted, remotes_updated);
+  iface->merge (manager, is_initial, remotes_deleted, remotes_updated, callback, user_data);
 }
diff --git a/lib/sync/ephy-synchronizable-manager.h b/lib/sync/ephy-synchronizable-manager.h
index fe791a3..e848b94 100644
--- a/lib/sync/ephy-synchronizable-manager.h
+++ b/lib/sync/ephy-synchronizable-manager.h
@@ -30,6 +30,8 @@ G_BEGIN_DECLS
 
 G_DECLARE_INTERFACE (EphySynchronizableManager, ephy_synchronizable_manager, EPHY, SYNCHRONIZABLE_MANAGER, 
GObject)
 
+typedef void (*EphySynchronizableManagerMergeCallback) (GSList *to_upload, gpointer user_data);
+
 struct _EphySynchronizableManagerInterface {
   GTypeInterface parent_iface;
 
@@ -45,10 +47,12 @@ struct _EphySynchronizableManagerInterface {
                                                    EphySynchronizable        *synchronizable);
   void                 (*remove)                  (EphySynchronizableManager *manager,
                                                    EphySynchronizable        *synchronizable);
-  GSList             * (*merge)                   (EphySynchronizableManager *manager,
-                                                   gboolean                   is_initial,
-                                                   GSList                    *remotes_deleted,
-                                                   GSList                    *remotes_updated);
+  void                 (*merge)                   (EphySynchronizableManager              *manager,
+                                                   gboolean                                is_initial,
+                                                   GSList                                 *remotes_deleted,
+                                                   GSList                                 *remotes_updated,
+                                                   EphySynchronizableManagerMergeCallback  callback,
+                                                   gpointer                                user_data);
 };
 
 const char         *ephy_synchronizable_manager_get_collection_name     (EphySynchronizableManager *manager);
@@ -63,9 +67,11 @@ void                ephy_synchronizable_manager_add                     (EphySyn
                                                                          EphySynchronizable        
*synchronizable);
 void                ephy_synchronizable_manager_remove                  (EphySynchronizableManager *manager,
                                                                          EphySynchronizable        
*synchronizable);
-GSList             *ephy_synchronizable_manager_merge                   (EphySynchronizableManager *manager,
-                                                                         gboolean                   
is_initial,
-                                                                         GSList                    
*remotes_deleted,
-                                                                         GSList                    
*remotes_updated);
+void                ephy_synchronizable_manager_merge                   (EphySynchronizableManager           
   *manager,
+                                                                         gboolean                            
    is_initial,
+                                                                         GSList                              
   *remotes_deleted,
+                                                                         GSList                              
   *remotes_updated,
+                                                                         
EphySynchronizableManagerMergeCallback  callback,
+                                                                         gpointer                            
    user_data);
 
 G_END_DECLS
diff --git a/lib/sync/meson.build b/lib/sync/meson.build
index b4e6a22..658c17c 100644
--- a/lib/sync/meson.build
+++ b/lib/sync/meson.build
@@ -1,4 +1,6 @@
 libephysync_sources = [
+  'ephy-password-manager.c',
+  'ephy-password-record.c',
   'ephy-sync-crypto.c',
   'ephy-sync-service.c',
   'ephy-synchronizable-manager.c',
diff --git a/meson.build b/meson.build
index 1119843..61e755f 100644
--- a/meson.build
+++ b/meson.build
@@ -41,7 +41,7 @@ configure_file(
   configuration: conf
 )
 
-glib_requirement = '>= 2.46.0'
+glib_requirement = '>= 2.52.0'
 gtk_requirement = '>= 3.22.0'
 nettle_requirement = '>= 3.2'
 webkitgtk_requirement = '>= 2.16.0'
diff --git a/src/bookmarks/ephy-bookmarks-manager.c b/src/bookmarks/ephy-bookmarks-manager.c
index 8f7619a..58c34a8 100644
--- a/src/bookmarks/ephy-bookmarks-manager.c
+++ b/src/bookmarks/ephy-bookmarks-manager.c
@@ -888,18 +888,26 @@ next:
   return to_upload;
 }
 
-static GSList *
-synchronizable_manager_merge (EphySynchronizableManager *manager,
-                              gboolean                   is_initial,
-                              GSList                    *remotes_deleted,
-                              GSList                    *remotes_updated)
+static void
+synchronizable_manager_merge (EphySynchronizableManager              *manager,
+                              gboolean                                is_initial,
+                              GSList                                 *remotes_deleted,
+                              GSList                                 *remotes_updated,
+                              EphySynchronizableManagerMergeCallback  callback,
+                              gpointer                                user_data)
 {
   EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (manager);
+  GSList *to_upload = NULL;
 
   if (is_initial)
-    return ephy_bookmarks_manager_handle_initial_merge (self, remotes_updated);
-
-  return ephy_bookmarks_manager_handle_regular_merge (self, remotes_updated, remotes_deleted);
+    to_upload = ephy_bookmarks_manager_handle_initial_merge (self,
+                                                             remotes_updated);
+  else
+    to_upload = ephy_bookmarks_manager_handle_regular_merge (self,
+                                                             remotes_updated,
+                                                             remotes_deleted);
+
+  callback (to_upload, user_data);
 }
 
 static void
diff --git a/src/ephy-shell.c b/src/ephy-shell.c
index 6f917a5..0310705 100644
--- a/src/ephy-shell.c
+++ b/src/ephy-shell.c
@@ -346,13 +346,15 @@ ephy_shell_startup (GApplication *application)
                               G_BINDING_SYNC_CREATE);
     }
 
-    ephy_shell->password_manager = ephy_password_manager_new ();
-
     /* Create the sync service and register synchronizable managers. */
-    ephy_shell->sync_service = ephy_sync_service_new ();
+    ephy_shell->sync_service = ephy_sync_service_new (TRUE);
     if (g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_BOOKMARKS_ENABLED))
       ephy_sync_service_register_manager (ephy_shell->sync_service,
                                           EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_bookmarks_manager 
(ephy_shell)));
+    if (g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_PASSWORDS_ENABLED))
+      ephy_sync_service_register_manager (ephy_shell->sync_service,
+                                          EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_password_manager 
(ephy_shell)));
+
     gtk_application_set_app_menu (GTK_APPLICATION (application),
                                   G_MENU_MODEL (gtk_builder_get_object (builder, "app-menu")));
   } else {
@@ -821,6 +823,9 @@ ephy_shell_get_password_manager (EphyShell *shell)
 {
   g_return_val_if_fail (EPHY_IS_SHELL (shell), NULL);
 
+  if (shell->password_manager == NULL)
+    shell->password_manager = ephy_password_manager_new ();
+
   return shell->password_manager;
 }
 
diff --git a/src/passwords-dialog.c b/src/passwords-dialog.c
index 2f0637f..3023dc2 100644
--- a/src/passwords-dialog.c
+++ b/src/passwords-dialog.c
@@ -62,16 +62,6 @@ struct _EphyPasswordsDialog {
 
 G_DEFINE_TYPE (EphyPasswordsDialog, ephy_passwords_dialog, GTK_TYPE_DIALOG)
 
-static void populate_model (EphyPasswordsDialog *dialog);
-
-static void
-reload_model (EphyPasswordsDialog *dialog)
-{
-  gtk_list_store_clear (GTK_LIST_STORE (dialog->liststore));
-  dialog->filled = FALSE;
-  populate_model (dialog);
-}
-
 static void
 ephy_passwords_dialog_dispose (GObject *object)
 {
@@ -140,10 +130,10 @@ forget (GSimpleAction *action,
     gtk_tree_model_get_value (model, &iter, COL_PASSWORDS_DATA, &val);
     record = g_value_get_object (&val);
     ephy_password_manager_forget (dialog->manager,
-                                  ephy_password_record_get_origin (record),
-                                  ephy_password_record_get_form_username (record),
-                                  ephy_password_record_get_form_password (record),
-                                  ephy_password_record_get_username (record));
+                                  ephy_password_record_get_hostname (record),
+                                  ephy_password_record_get_username (record),
+                                  ephy_password_record_get_username_field (record),
+                                  ephy_password_record_get_password_field (record));
     dialog->records = g_slist_remove (dialog->records, record);
     g_object_unref (record);
     g_value_unset (&val);
@@ -351,10 +341,12 @@ forget_all (GSimpleAction *action,
   EphyPasswordsDialog *dialog = EPHY_PASSWORDS_DIALOG (user_data);
 
   ephy_password_manager_forget_all (dialog->manager);
+
+  gtk_list_store_clear (GTK_LIST_STORE (dialog->liststore));
+  dialog->filled = FALSE;
+
   g_slist_free_full (dialog->records, g_object_unref);
   dialog->records = NULL;
-
-  reload_model (dialog);
 }
 
 static void
@@ -370,7 +362,7 @@ populate_model_cb (GSList   *records,
     gtk_list_store_insert_with_values (GTK_LIST_STORE (dialog->liststore),
                                        &iter,
                                        -1,
-                                       COL_PASSWORDS_ORIGIN, ephy_password_record_get_origin (record),
+                                       COL_PASSWORDS_ORIGIN, ephy_password_record_get_hostname (record),
                                        COL_PASSWORDS_USER, ephy_password_record_get_username (record),
                                        COL_PASSWORDS_PASSWORD, ephy_password_record_get_password (record),
                                        COL_PASSWORDS_INVISIBLE, "●●●●●●●●",
diff --git a/src/prefs-dialog.c b/src/prefs-dialog.c
index a4f9457..72ee246 100644
--- a/src/prefs-dialog.c
+++ b/src/prefs-dialog.c
@@ -121,6 +121,7 @@ struct _PrefsDialog {
   GtkWidget *sync_options_box;
   GtkWidget *sync_with_firefox_checkbutton;
   GtkWidget *sync_bookmarks_checkbutton;
+  GtkWidget *sync_passwords_checkbutton;
   GtkWidget *sync_frequency_5_min_radiobutton;
   GtkWidget *sync_frequency_15_min_radiobutton;
   GtkWidget *sync_frequency_30_min_radiobutton;
@@ -175,19 +176,20 @@ prefs_dialog_finalize (GObject *object)
 }
 
 static void
-sync_bookmarks_toggled_cb (GtkToggleButton *button,
-                           PrefsDialog     *dialog)
+sync_collection_toggled_cb (GtkToggleButton *button,
+                            PrefsDialog     *dialog)
 {
-  EphyBookmarksManager *manager;
+  EphySynchronizableManager *manager = NULL;
 
-  manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
+  if (GTK_WIDGET (button) == dialog->sync_bookmarks_checkbutton)
+    manager = EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_bookmarks_manager (ephy_shell_get_default ()));
+  else if (GTK_WIDGET (button) == dialog->sync_passwords_checkbutton)
+    manager = EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_password_manager (ephy_shell_get_default ()));
 
   if (gtk_toggle_button_get_active (button))
-    ephy_sync_service_register_manager (dialog->sync_service,
-                                        EPHY_SYNCHRONIZABLE_MANAGER (manager));
+    ephy_sync_service_register_manager (dialog->sync_service, manager);
   else
-    ephy_sync_service_unregister_manager (dialog->sync_service,
-                                          EPHY_SYNCHRONIZABLE_MANAGER (manager));
+    ephy_sync_service_unregister_manager (dialog->sync_service, manager);
 }
 
 static void
@@ -234,6 +236,7 @@ sync_secrets_store_finished_cb (EphySyncService *service,
                                 PrefsDialog     *dialog)
 {
   EphyBookmarksManager *bookmarks_manager;
+  EphyPasswordManager *password_manager;
 
   g_assert (EPHY_IS_SYNC_SERVICE (service));
   g_assert (EPHY_IS_PREFS_DIALOG (dialog));
@@ -264,6 +267,10 @@ sync_secrets_store_finished_cb (EphySyncService *service,
       bookmarks_manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
       ephy_sync_service_register_manager (service, EPHY_SYNCHRONIZABLE_MANAGER (bookmarks_manager));
     }
+    if (g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_PASSWORDS_ENABLED)) {
+      password_manager = ephy_shell_get_password_manager (ephy_shell_get_default ());
+      ephy_sync_service_register_manager (service, EPHY_SYNCHRONIZABLE_MANAGER (password_manager));
+    }
 
     g_free (text);
     g_free (user);
@@ -593,6 +600,7 @@ prefs_dialog_class_init (PrefsDialogClass *klass)
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_options_box);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_with_firefox_checkbutton);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_bookmarks_checkbutton);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_passwords_checkbutton);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_frequency_5_min_radiobutton);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_frequency_15_min_radiobutton);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_frequency_30_min_radiobutton);
@@ -1647,7 +1655,10 @@ setup_sync_page (PrefsDialog *dialog)
                            G_CALLBACK (sync_finished_cb),
                            dialog, 0);
   g_signal_connect_object (dialog->sync_bookmarks_checkbutton, "toggled",
-                           G_CALLBACK (sync_bookmarks_toggled_cb),
+                           G_CALLBACK (sync_collection_toggled_cb),
+                           dialog, 0);
+  g_signal_connect_object (dialog->sync_passwords_checkbutton, "toggled",
+                           G_CALLBACK (sync_collection_toggled_cb),
                            dialog, 0);
 
   g_settings_bind (sync_settings,
@@ -1660,6 +1671,11 @@ setup_sync_page (PrefsDialog *dialog)
                    dialog->sync_bookmarks_checkbutton,
                    "active",
                    G_SETTINGS_BIND_DEFAULT);
+  g_settings_bind (sync_settings,
+                   EPHY_PREFS_SYNC_PASSWORDS_ENABLED,
+                   dialog->sync_passwords_checkbutton,
+                   "active",
+                   G_SETTINGS_BIND_DEFAULT);
   g_settings_bind_with_mapping (sync_settings,
                                 EPHY_PREFS_SYNC_FREQUENCY,
                                 dialog->sync_frequency_5_min_radiobutton,
diff --git a/src/profile-migrator/ephy-profile-migrator.c b/src/profile-migrator/ephy-profile-migrator.c
index 1ce5335..42a65c3 100644
--- a/src/profile-migrator/ephy-profile-migrator.c
+++ b/src/profile-migrator/ephy-profile-migrator.c
@@ -309,7 +309,7 @@ load_collection_items_cb (SecretCollection *collection,
   SecretValue *secret;
   GList *l;
   GHashTable *attributes, *t;
-  const char *server, *username, *form_username, *form_password, *password;
+  const char *server, *username, *username_field, *password_field, *password;
   char *actual_server;
   SoupURI *uri;
   GError *error = NULL;
@@ -338,20 +338,20 @@ load_collection_items_cb (SecretCollection *collection,
       username = g_hash_table_lookup (attributes, "user");
       uri = soup_uri_new (server);
       t = soup_form_decode (uri->query);
-      form_username = g_hash_table_lookup (t, FORM_USERNAME_KEY);
-      form_password = g_hash_table_lookup (t, FORM_PASSWORD_KEY);
+      username_field = g_hash_table_lookup (t, USERNAME_FIELD_KEY);
+      password_field = g_hash_table_lookup (t, PASSWORD_FIELD_KEY);
       soup_uri_set_query (uri, NULL);
       actual_server = soup_uri_to_string (uri, FALSE);
       secret_item_load_secret_sync (item, NULL, NULL);
       secret = secret_item_get_secret (item);
       password = secret_value_get (secret, NULL);
-      ephy_password_manager_store (actual_server,
-                                   form_username,
-                                   form_password,
-                                   username,
-                                   password,
-                                   (GAsyncReadyCallback)store_form_auth_data_cb,
-                                   g_hash_table_ref (attributes));
+      ephy_password_manager_store_raw (actual_server,
+                                       username,
+                                       password,
+                                       username_field,
+                                       password_field,
+                                       (GAsyncReadyCallback)store_form_auth_data_cb,
+                                       g_hash_table_ref (attributes));
       g_free (actual_server);
       secret_value_unref (secret);
       g_hash_table_unref (t);
@@ -440,7 +440,7 @@ migrate_insecure_password (SecretItem *item)
   const char *original_uri;
 
   attributes = secret_item_get_attributes (item);
-  original_uri = g_hash_table_lookup (attributes, URI_KEY);
+  original_uri = g_hash_table_lookup (attributes, HOSTNAME_KEY);
   original_origin = webkit_security_origin_new_for_uri (original_uri);
   if (original_origin == NULL) {
     g_warning ("Failed to convert URI %s to a security origin, insecure password will not be migrated", 
original_uri);
@@ -459,7 +459,7 @@ migrate_insecure_password (SecretItem *item)
     new_uri = webkit_security_origin_to_string (new_origin);
     webkit_security_origin_unref (new_origin);
 
-    g_hash_table_replace (attributes, g_strdup (URI_KEY), new_uri);
+    g_hash_table_replace (attributes, g_strdup (HOSTNAME_KEY), new_uri);
     secret_item_set_attributes_sync (item, EPHY_FORM_PASSWORD_SCHEMA, attributes, NULL, &error);
     if (error != NULL) {
       g_warning ("Failed to convert URI %s to https://, insecure password will not be migrated: %s", 
original_uri, error->message);
diff --git a/src/resources/gtk/prefs-dialog.ui b/src/resources/gtk/prefs-dialog.ui
index 09c5ac9..ac5bc0f 100644
--- a/src/resources/gtk/prefs-dialog.ui
+++ b/src/resources/gtk/prefs-dialog.ui
@@ -915,6 +915,13 @@
                                 <property name="use-underline">True</property>
                               </object>
                             </child>
+                            <child>
+                              <object class="GtkCheckButton" id="sync_passwords_checkbutton">
+                                <property name="label" translatable="yes">_Passwords</property>
+                                <property name="visible">True</property>
+                                <property name="use-underline">True</property>
+                              </object>
+                            </child>
                           </object>
                         </child>
                         <child>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]