[epiphany] Add the Firefox Sync button in the page menu



commit 489e58e9451b399e0fd3d18a68322997f8c0e2b4
Author: Yetizone <andreii lisita gmail com>
Date:   Mon Sep 28 17:01:55 2020 +0300

    Add the Firefox Sync button in the page menu

 src/ephy-firefox-sync-dialog.c           | 802 +++++++++++++++++++++++++++++++
 src/ephy-firefox-sync-dialog.h           |  34 ++
 src/ephy-shell.c                         |  34 ++
 src/ephy-shell.h                         |  99 ++--
 src/meson.build                          |   1 +
 src/resources/epiphany.gresource.xml     |   1 +
 src/resources/gtk/firefox-sync-dialog.ui | 226 +++++++++
 src/resources/gtk/page-menu-popover.ui   |  16 +
 src/window-commands.c                    |  15 +
 src/window-commands.h                    |   3 +
 10 files changed, 1182 insertions(+), 49 deletions(-)
---
diff --git a/src/ephy-firefox-sync-dialog.c b/src/ephy-firefox-sync-dialog.c
new file mode 100644
index 000000000..08c057333
--- /dev/null
+++ b/src/ephy-firefox-sync-dialog.c
@@ -0,0 +1,802 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2000-2003 Marco Pesenti Gritti
+ *  Copyright © 2003, 2004, 2005 Christian Persch
+ *  Copyright © 2010, 2017 Igalia S.L.
+ *
+ *  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 "ephy-firefox-sync-dialog.h"
+
+#include "ephy-debug.h"
+#include "ephy-embed-prefs.h"
+#include "ephy-embed-shell.h"
+#include "ephy-prefs.h"
+#include "ephy-settings.h"
+#include "ephy-shell.h"
+#include "ephy-sync-service.h"
+#include "ephy-sync-utils.h"
+#include "ephy-time-helpers.h"
+#include "synced-tabs-dialog.h"
+
+#define FXA_IFRAME_URL "https://accounts.firefox.com/signin?service=sync&context=fx_desktop_v3";
+
+struct _EphyFirefoxSyncDialog {
+  HdyWindow parent_instance;
+
+  GtkWidget *sync_page_box;
+  GtkWidget *sync_firefox_iframe_box;
+  GtkWidget *sync_firefox_iframe_label;
+  GtkWidget *sync_firefox_account_box;
+  GtkWidget *sync_firefox_account_row;
+  GtkWidget *sync_options_box;
+  GtkWidget *sync_bookmarks_switch;
+  GtkWidget *sync_passwords_switch;
+  GtkWidget *sync_history_switch;
+  GtkWidget *sync_open_tabs_switch;
+  GtkWidget *sync_frequency_row;
+  GtkWidget *sync_now_button;
+  GtkWidget *synced_tabs_button;
+  GtkWidget *sync_device_name_entry;
+  GtkWidget *sync_device_name_change_button;
+  GtkWidget *sync_device_name_save_button;
+  GtkWidget *sync_device_name_cancel_button;
+
+  WebKitWebView *fxa_web_view;
+  WebKitUserContentManager *fxa_manager;
+  WebKitUserScript *fxa_script;
+};
+
+G_DEFINE_TYPE (EphyFirefoxSyncDialog, ephy_firefox_sync_dialog, HDY_TYPE_WINDOW)
+
+static const guint sync_frequency_minutes[] = { 5, 15, 30, 60 };
+
+static void
+sync_collection_toggled_cb (GtkWidget             *sw,
+                            gboolean               sw_active,
+                            EphyFirefoxSyncDialog *sync_dialog)
+{
+  EphySynchronizableManager *manager = NULL;
+  EphyShell *shell = ephy_shell_get_default ();
+  EphySyncService *service = ephy_shell_get_sync_service (shell);
+
+  if (GTK_WIDGET (sw) == sync_dialog->sync_bookmarks_switch) {
+    manager = EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_bookmarks_manager (shell));
+  } else if (GTK_WIDGET (sw) == sync_dialog->sync_passwords_switch) {
+    manager = EPHY_SYNCHRONIZABLE_MANAGER (ephy_embed_shell_get_password_manager (EPHY_EMBED_SHELL (shell)));
+  } else if (GTK_WIDGET (sw) == sync_dialog->sync_history_switch) {
+    manager = EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_history_manager (shell));
+  } else if (GTK_WIDGET (sw) == sync_dialog->sync_open_tabs_switch) {
+    manager = EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_open_tabs_manager (shell));
+    ephy_open_tabs_manager_clear_cache (EPHY_OPEN_TABS_MANAGER (manager));
+  } else {
+    g_assert_not_reached ();
+  }
+
+  if (sw_active) {
+    ephy_sync_service_register_manager (service, manager);
+  } else {
+    ephy_sync_service_unregister_manager (service, manager);
+    ephy_synchronizable_manager_set_is_initial_sync (manager, TRUE);
+  }
+}
+
+static void
+sync_set_last_sync_time (EphyFirefoxSyncDialog *sync_dialog)
+{
+  gint64 sync_time = ephy_sync_utils_get_sync_time ();
+
+  if (sync_time) {
+    char *time = ephy_time_helpers_utf_friendly_time (sync_time);
+    /* Translators: the %s refers to the time at which the last sync was made.
+     * For example: Today 04:34 PM, Sun 11:25 AM, May 31 06:41 PM.
+     */
+    char *text = g_strdup_printf (_("Last synchronized: %s"), time);
+
+    hdy_action_row_set_subtitle (HDY_ACTION_ROW (sync_dialog->sync_firefox_account_row), text);
+
+    g_free (text);
+    g_free (time);
+  }
+}
+
+static void
+sync_finished_cb (EphySyncService       *service,
+                  EphyFirefoxSyncDialog *sync_dialog)
+{
+  g_assert (EPHY_IS_SYNC_SERVICE (service));
+  g_assert (EPHY_IS_FIREFOX_SYNC_DIALOG (sync_dialog));
+
+  gtk_widget_set_sensitive (sync_dialog->sync_now_button, TRUE);
+  sync_set_last_sync_time (sync_dialog);
+}
+
+static void
+sync_sign_in_details_show (EphyFirefoxSyncDialog *sync_dialog,
+                           const char            *text)
+{
+  char *message;
+
+  g_assert (EPHY_IS_FIREFOX_SYNC_DIALOG (sync_dialog));
+
+  message = g_strdup_printf ("<span fgcolor='#e6780b'>%s</span>", text);
+  gtk_label_set_markup (GTK_LABEL (sync_dialog->sync_firefox_iframe_label), message);
+  gtk_widget_set_visible (sync_dialog->sync_firefox_iframe_label, TRUE);
+
+  g_free (message);
+}
+
+static void
+sync_sign_in_error_cb (EphySyncService       *service,
+                       const char            *error,
+                       EphyFirefoxSyncDialog *sync_dialog)
+{
+  g_assert (EPHY_IS_SYNC_SERVICE (service));
+  g_assert (EPHY_IS_FIREFOX_SYNC_DIALOG (sync_dialog));
+
+  /* Display the error message and reload the iframe. */
+  sync_sign_in_details_show (sync_dialog, error);
+  webkit_web_view_load_uri (sync_dialog->fxa_web_view, FXA_IFRAME_URL);
+}
+
+static void
+sync_secrets_store_finished_cb (EphySyncService       *service,
+                                GError                *error,
+                                EphyFirefoxSyncDialog *sync_dialog)
+{
+  g_assert (EPHY_IS_SYNC_SERVICE (service));
+  g_assert (EPHY_IS_FIREFOX_SYNC_DIALOG (sync_dialog));
+
+  if (!error) {
+    hdy_preferences_row_set_title (HDY_PREFERENCES_ROW (sync_dialog->sync_firefox_account_row),
+                                   ephy_sync_utils_get_sync_user ());
+    gtk_widget_hide (sync_dialog->sync_page_box);
+    gtk_widget_show (sync_dialog->sync_firefox_account_box);
+    gtk_widget_show (sync_dialog->sync_options_box);
+  } else {
+    /* Display the error message and reload the iframe. */
+    sync_sign_in_details_show (sync_dialog, error->message);
+    webkit_web_view_load_uri (sync_dialog->fxa_web_view, FXA_IFRAME_URL);
+  }
+}
+
+static void
+sync_message_to_fxa_content (EphyFirefoxSyncDialog *sync_dialog,
+                             const char            *web_channel_id,
+                             const char            *command,
+                             const char            *message_id,
+                             JsonObject            *data)
+{
+  JsonNode *node;
+  JsonObject *detail;
+  JsonObject *message;
+  char *detail_str;
+  char *script;
+  const char *type;
+
+  g_assert (EPHY_FIREFOX_SYNC_DIALOG (sync_dialog));
+  g_assert (web_channel_id);
+  g_assert (command);
+  g_assert (message_id);
+  g_assert (data);
+
+  message = json_object_new ();
+  json_object_set_string_member (message, "command", command);
+  json_object_set_string_member (message, "messageId", message_id);
+  json_object_set_object_member (message, "data", json_object_ref (data));
+  detail = json_object_new ();
+  json_object_set_string_member (detail, "id", web_channel_id);
+  json_object_set_object_member (detail, "message", message);
+  node = json_node_new (JSON_NODE_OBJECT);
+  json_node_set_object (node, detail);
+
+  type = "WebChannelMessageToContent";
+  detail_str = json_to_string (node, FALSE);
+  script = g_strdup_printf ("let e = new window.CustomEvent(\"%s\", {detail: %s});"
+                            "window.dispatchEvent(e);",
+                            type, detail_str);
+
+  /* We don't expect any response from the server. */
+  webkit_web_view_run_javascript (sync_dialog->fxa_web_view, script, NULL, NULL, NULL);
+
+  g_free (script);
+  g_free (detail_str);
+  json_object_unref (detail);
+  json_node_unref (node);
+}
+
+static gboolean
+sync_parse_message_from_fxa_content (const char  *message,
+                                     char       **web_channel_id,
+                                     char       **message_id,
+                                     char       **command,
+                                     JsonObject **data,
+                                     char       **error_msg)
+{
+  JsonNode *node;
+  JsonObject *object;
+  JsonObject *detail;
+  JsonObject *msg;
+  JsonObject *msg_data = NULL;
+  const char *type;
+  const char *channel_id;
+  const char *cmd;
+  const char *error = NULL;
+  gboolean success = FALSE;
+
+  g_assert (message);
+  g_assert (web_channel_id);
+  g_assert (message_id);
+  g_assert (command);
+  g_assert (data);
+  g_assert (error_msg);
+
+  /* Expected message format is:
+   * {
+   *   type: "WebChannelMessageToChrome",
+   *   detail: {
+   *     id: <id> (string, the id of the WebChannel),
+   *     message: {
+   *       messageId: <messageId> (optional string, the message id),
+   *       command: <command> (string, the message command),
+   *       data: <data> (optional JSON object, the message data)
+   *     }
+   *   }
+   * }
+   */
+
+  node = json_from_string (message, NULL);
+  if (!node) {
+    error = "Message is not a valid JSON";
+    goto out_error;
+  }
+  object = json_node_get_object (node);
+  if (!object) {
+    error = "Message is not a JSON object";
+    goto out_error;
+  }
+  type = json_object_get_string_member (object, "type");
+  if (!type) {
+    error = "Message has missing or invalid 'type' member";
+    goto out_error;
+  } else if (strcmp (type, "WebChannelMessageToChrome")) {
+    error = "Message type is not WebChannelMessageToChrome";
+    goto out_error;
+  }
+  detail = json_object_get_object_member (object, "detail");
+  if (!detail) {
+    error = "Message has missing or invalid 'detail' member";
+    goto out_error;
+  }
+  channel_id = json_object_get_string_member (detail, "id");
+  if (!channel_id) {
+    error = "'Detail' object has missing or invalid 'id' member";
+    goto out_error;
+  }
+  msg = json_object_get_object_member (detail, "message");
+  if (!msg) {
+    error = "'Detail' object has missing or invalid 'message' member";
+    goto out_error;
+  }
+  cmd = json_object_get_string_member (msg, "command");
+  if (!cmd) {
+    error = "'Message' object has missing or invalid 'command' member";
+    goto out_error;
+  }
+
+  *web_channel_id = g_strdup (channel_id);
+  *command = g_strdup (cmd);
+  *message_id = json_object_has_member (msg, "messageId") ?
+                g_strdup (json_object_get_string_member (msg, "messageId")) :
+                NULL;
+  if (json_object_has_member (msg, "data"))
+    msg_data = json_object_get_object_member (msg, "data");
+  *data = msg_data ? json_object_ref (msg_data) : NULL;
+
+  success = TRUE;
+  *error_msg = NULL;
+  goto out_no_error;
+
+out_error:
+  *web_channel_id = NULL;
+  *command = NULL;
+  *message_id = NULL;
+  *error_msg = g_strdup (error);
+
+out_no_error:
+  json_node_unref (node);
+
+  return success;
+}
+
+static void
+sync_message_from_fxa_content_cb (WebKitUserContentManager *manager,
+                                  WebKitJavascriptResult   *result,
+                                  EphyFirefoxSyncDialog    *sync_dialog)
+{
+  JsonObject *data = NULL;
+  char *message = NULL;
+  char *web_channel_id = NULL;
+  char *message_id = NULL;
+  char *command = NULL;
+  char *error_msg = NULL;
+  gboolean is_error = FALSE;
+
+  message = jsc_value_to_string (webkit_javascript_result_get_js_value (result));
+  if (!message) {
+    g_warning ("Failed to get JavaScript result as string");
+    is_error = TRUE;
+    goto out;
+  }
+
+  if (!sync_parse_message_from_fxa_content (message, &web_channel_id,
+                                            &message_id, &command,
+                                            &data, &error_msg)) {
+    g_warning ("Failed to parse message from FxA Content Server: %s", error_msg);
+    is_error = TRUE;
+    goto out;
+  }
+
+  LOG ("WebChannelMessageToChrome: received %s command", command);
+
+  if (!g_strcmp0 (command, "fxaccounts:can_link_account")) {
+    /* Confirm a relink. Respond with {ok: true}. */
+    JsonObject *response = json_object_new ();
+    json_object_set_boolean_member (response, "ok", TRUE);
+    sync_message_to_fxa_content (sync_dialog, web_channel_id, command, message_id, response);
+    json_object_unref (response);
+  } else if (!g_strcmp0 (command, "fxaccounts:login")) {
+    /* Extract sync tokens and pass them to the sync service. */
+    const char *email = json_object_get_string_member (data, "email");
+    const char *uid = json_object_get_string_member (data, "uid");
+    const char *session_token = json_object_get_string_member (data, "sessionToken");
+    const char *key_fetch_token = json_object_get_string_member (data, "keyFetchToken");
+    const char *unwrap_kb = json_object_get_string_member (data, "unwrapBKey");
+
+    if (!email || !uid || !session_token || !key_fetch_token || !unwrap_kb) {
+      g_warning ("Message data has missing or invalid members");
+      is_error = TRUE;
+      goto out;
+    }
+    if (!json_object_has_member (data, "verified") ||
+        !JSON_NODE_HOLDS_VALUE (json_object_get_member (data, "verified"))) {
+      g_warning ("Message data has missing or invalid 'verified' member");
+      is_error = TRUE;
+      goto out;
+    }
+
+    ephy_sync_service_sign_in (ephy_shell_get_sync_service (ephy_shell_get_default ()),
+                               email, uid, session_token, key_fetch_token, unwrap_kb);
+  }
+
+out:
+  if (data)
+    json_object_unref (data);
+  g_free (message);
+  g_free (web_channel_id);
+  g_free (message_id);
+  g_free (command);
+  g_free (error_msg);
+
+  if (is_error) {
+    sync_sign_in_details_show (sync_dialog, _("Something went wrong, please try again later."));
+    webkit_web_view_load_uri (sync_dialog->fxa_web_view, FXA_IFRAME_URL);
+  }
+}
+
+static void
+sync_open_webmail_clicked_cb (WebKitUserContentManager *manager,
+                              WebKitJavascriptResult   *result,
+                              EphyFirefoxSyncDialog    *sync_page)
+{
+  EphyShell *shell;
+  EphyEmbed *embed;
+  GtkWindow *window;
+  GtkWidget *prefs_dialog;
+  char *url;
+
+  url = jsc_value_to_string (webkit_javascript_result_get_js_value (result));
+  if (url) {
+    /* Open a new tab to the webmail URL. */
+    shell = ephy_shell_get_default ();
+    window = gtk_application_get_active_window (GTK_APPLICATION (shell));
+    embed = ephy_shell_new_tab (shell, EPHY_WINDOW (window),
+                                NULL, EPHY_NEW_TAB_JUMP);
+    ephy_web_view_load_url (ephy_embed_get_web_view (embed), url);
+
+    /* Close the preferences dialog. */
+    prefs_dialog = gtk_widget_get_toplevel (GTK_WIDGET (sync_page));
+    gtk_widget_destroy (GTK_WIDGET (prefs_dialog));
+
+    g_free (url);
+  }
+}
+
+static void
+sync_setup_firefox_iframe (EphyFirefoxSyncDialog *sync_dialog)
+{
+  EphyEmbedShell *shell;
+  WebKitWebsiteDataManager *manager;
+  WebKitWebContext *embed_context;
+  WebKitWebContext *sync_context;
+  GtkWidget *frame;
+  const char *script;
+
+  if (!sync_dialog->fxa_web_view) {
+    script =
+      /* Handle sign-in messages from the FxA content server. */
+      "function handleToChromeMessage(event) {"
+      "  let e = JSON.stringify({type: event.type, detail: event.detail});"
+      "  window.webkit.messageHandlers.toChromeMessageHandler.postMessage(e);"
+      "};"
+      "window.addEventListener('WebChannelMessageToChrome', handleToChromeMessage);"
+      /* Handle open-webmail click event. */
+      "function handleOpenWebmailClick(event) {"
+      "  if (event.target.id == 'open-webmail' && event.target.hasAttribute('href'))"
+      "    
window.webkit.messageHandlers.openWebmailClickHandler.postMessage(event.target.getAttribute('href'));"
+      "};"
+      "var stage = document.getElementById('stage');"
+      "if (stage)"
+      "  stage.addEventListener('click', handleOpenWebmailClick);";
+
+    sync_dialog->fxa_script = webkit_user_script_new (script,
+                                                      WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
+                                                      WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END,
+                                                      NULL, NULL);
+    sync_dialog->fxa_manager = webkit_user_content_manager_new ();
+    webkit_user_content_manager_add_script (sync_dialog->fxa_manager, sync_dialog->fxa_script);
+    g_signal_connect (sync_dialog->fxa_manager,
+                      "script-message-received::toChromeMessageHandler",
+                      G_CALLBACK (sync_message_from_fxa_content_cb),
+                      sync_dialog);
+    g_signal_connect (sync_dialog->fxa_manager,
+                      "script-message-received::openWebmailClickHandler",
+                      G_CALLBACK (sync_open_webmail_clicked_cb),
+                      sync_dialog);
+    webkit_user_content_manager_register_script_message_handler (sync_dialog->fxa_manager,
+                                                                 "toChromeMessageHandler");
+    webkit_user_content_manager_register_script_message_handler (sync_dialog->fxa_manager,
+                                                                 "openWebmailClickHandler");
+
+    shell = ephy_embed_shell_get_default ();
+    embed_context = ephy_embed_shell_get_web_context (shell);
+    manager = webkit_web_context_get_website_data_manager (embed_context);
+    sync_context = webkit_web_context_new_with_website_data_manager (manager);
+    webkit_web_context_set_preferred_languages (sync_context,
+                                                g_object_get_data (G_OBJECT (embed_context), 
"preferred-languages"));
+
+    sync_dialog->fxa_web_view = WEBKIT_WEB_VIEW (g_object_new (WEBKIT_TYPE_WEB_VIEW,
+                                                               "user-content-manager", 
sync_dialog->fxa_manager,
+                                                               "settings", ephy_embed_prefs_get_settings (),
+                                                               "web-context", sync_context,
+                                                               NULL));
+    gtk_widget_set_vexpand (GTK_WIDGET (sync_dialog->fxa_web_view), TRUE);
+    gtk_widget_set_visible (GTK_WIDGET (sync_dialog->fxa_web_view), TRUE);
+    frame = gtk_frame_new (NULL);
+    gtk_widget_set_visible (frame, TRUE);
+    gtk_container_add (GTK_CONTAINER (frame),
+                       GTK_WIDGET (sync_dialog->fxa_web_view));
+    gtk_box_pack_start (GTK_BOX (sync_dialog->sync_firefox_iframe_box),
+                        frame,
+                        TRUE, TRUE, 0);
+
+    g_object_unref (sync_context);
+  }
+
+  webkit_web_view_load_uri (sync_dialog->fxa_web_view, FXA_IFRAME_URL);
+  gtk_widget_set_visible (sync_dialog->sync_firefox_iframe_label, FALSE);
+}
+
+static void
+on_sync_sign_out_button_clicked (GtkWidget             *button,
+                                 EphyFirefoxSyncDialog *sync_dialog)
+{
+  EphySyncService *service = ephy_shell_get_sync_service (ephy_shell_get_default ());
+
+  ephy_sync_service_sign_out (service);
+
+  /* Show Firefox Accounts iframe. */
+  sync_setup_firefox_iframe (sync_dialog);
+  gtk_widget_hide (sync_dialog->sync_firefox_account_box);
+  gtk_widget_hide (sync_dialog->sync_options_box);
+  gtk_widget_show (sync_dialog->sync_page_box);
+  hdy_action_row_set_subtitle (HDY_ACTION_ROW (sync_dialog->sync_firefox_account_row), NULL);
+}
+
+static void
+on_sync_sync_now_button_clicked (GtkWidget             *button,
+                                 EphyFirefoxSyncDialog *sync_dialog)
+{
+  EphySyncService *service = ephy_shell_get_sync_service (ephy_shell_get_default ());
+
+  gtk_widget_set_sensitive (button, FALSE);
+  ephy_sync_service_sync (service);
+}
+
+static void
+on_sync_synced_tabs_button_clicked (GtkWidget             *button,
+                                    EphyFirefoxSyncDialog *sync_dialog)
+{
+  EphyOpenTabsManager *manager;
+  SyncedTabsDialog *synced_tabs_dialog;
+
+  manager = ephy_shell_get_open_tabs_manager (ephy_shell_get_default ());
+  synced_tabs_dialog = synced_tabs_dialog_new (manager);
+  gtk_window_set_transient_for (GTK_WINDOW (synced_tabs_dialog), GTK_WINDOW (sync_dialog));
+  gtk_window_set_modal (GTK_WINDOW (synced_tabs_dialog), TRUE);
+  gtk_window_present_with_time (GTK_WINDOW (synced_tabs_dialog), gtk_get_current_event_time ());
+}
+
+static void
+on_sync_device_name_change_button_clicked (GtkWidget             *button,
+                                           EphyFirefoxSyncDialog *sync_dialog)
+{
+  gtk_widget_set_sensitive (GTK_WIDGET (sync_dialog->sync_device_name_entry), TRUE);
+  gtk_widget_set_visible (GTK_WIDGET (sync_dialog->sync_device_name_change_button), FALSE);
+  gtk_widget_set_visible (GTK_WIDGET (sync_dialog->sync_device_name_save_button), TRUE);
+  gtk_widget_set_visible (GTK_WIDGET (sync_dialog->sync_device_name_cancel_button), TRUE);
+}
+
+static void
+on_sync_device_name_save_button_clicked (GtkWidget             *button,
+                                         EphyFirefoxSyncDialog *sync_dialog)
+{
+  EphySyncService *service = ephy_shell_get_sync_service (ephy_shell_get_default ());
+  const char *text;
+
+  text = gtk_entry_get_text (GTK_ENTRY (sync_dialog->sync_device_name_entry));
+  if (!g_strcmp0 (text, "")) {
+    char *name = ephy_sync_utils_get_device_name ();
+    gtk_entry_set_text (GTK_ENTRY (sync_dialog->sync_device_name_entry), name);
+    g_free (name);
+  } else {
+    ephy_sync_service_update_device_name (service, text);
+  }
+
+  gtk_widget_set_sensitive (GTK_WIDGET (sync_dialog->sync_device_name_entry), FALSE);
+  gtk_widget_set_visible (GTK_WIDGET (sync_dialog->sync_device_name_change_button), TRUE);
+  gtk_widget_set_visible (GTK_WIDGET (sync_dialog->sync_device_name_save_button), FALSE);
+  gtk_widget_set_visible (GTK_WIDGET (sync_dialog->sync_device_name_cancel_button), FALSE);
+}
+
+static void
+on_sync_device_name_cancel_button_clicked (GtkWidget             *button,
+                                           EphyFirefoxSyncDialog *sync_dialog)
+{
+  char *name;
+
+  name = ephy_sync_utils_get_device_name ();
+  gtk_entry_set_text (GTK_ENTRY (sync_dialog->sync_device_name_entry), name);
+
+  gtk_widget_set_sensitive (GTK_WIDGET (sync_dialog->sync_device_name_entry), FALSE);
+  gtk_widget_set_visible (GTK_WIDGET (sync_dialog->sync_device_name_change_button), TRUE);
+  gtk_widget_set_visible (GTK_WIDGET (sync_dialog->sync_device_name_save_button), FALSE);
+  gtk_widget_set_visible (GTK_WIDGET (sync_dialog->sync_device_name_cancel_button), FALSE);
+
+  g_free (name);
+}
+
+static void
+prefs_sync_page_finalize (GObject *object)
+{
+  EphyFirefoxSyncDialog *sync_dialog = EPHY_FIREFOX_SYNC_DIALOG (object);
+
+  if (sync_dialog->fxa_web_view != NULL) {
+    webkit_user_content_manager_unregister_script_message_handler (sync_dialog->fxa_manager,
+                                                                   "toChromeMessageHandler");
+    webkit_user_content_manager_unregister_script_message_handler (sync_dialog->fxa_manager,
+                                                                   "openWebmailClickHandler");
+    webkit_user_script_unref (sync_dialog->fxa_script);
+    g_object_unref (sync_dialog->fxa_manager);
+  }
+
+  G_OBJECT_CLASS (ephy_firefox_sync_dialog_parent_class)->finalize (object);
+}
+
+static void
+ephy_firefox_sync_dialog_class_init (EphyFirefoxSyncDialogClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+                                               "/org/gnome/epiphany/gtk/firefox-sync-dialog.ui");
+
+  object_class->finalize = prefs_sync_page_finalize;
+
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_page_box);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_firefox_iframe_box);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_firefox_iframe_label);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_firefox_account_box);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_firefox_account_row);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_options_box);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_bookmarks_switch);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_passwords_switch);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_history_switch);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_open_tabs_switch);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_frequency_row);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_now_button);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, synced_tabs_button);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_device_name_entry);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_device_name_change_button);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_device_name_save_button);
+  gtk_widget_class_bind_template_child (widget_class, EphyFirefoxSyncDialog, sync_device_name_cancel_button);
+
+  gtk_widget_class_bind_template_callback (widget_class, on_sync_sign_out_button_clicked);
+  gtk_widget_class_bind_template_callback (widget_class, on_sync_sync_now_button_clicked);
+  gtk_widget_class_bind_template_callback (widget_class, on_sync_synced_tabs_button_clicked);
+  gtk_widget_class_bind_template_callback (widget_class, on_sync_device_name_change_button_clicked);
+  gtk_widget_class_bind_template_callback (widget_class, on_sync_device_name_save_button_clicked);
+  gtk_widget_class_bind_template_callback (widget_class, on_sync_device_name_cancel_button_clicked);
+}
+
+static gboolean
+sync_frequency_get_mapping (GValue   *value,
+                            GVariant *variant,
+                            gpointer  user_data)
+{
+  uint minutes = g_variant_get_uint32 (variant);
+
+  for (gint i = 0; i < (gint)G_N_ELEMENTS (sync_frequency_minutes); i++) {
+    if (sync_frequency_minutes[i] != minutes)
+      continue;
+
+    g_value_set_int (value, i);
+
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+static GVariant *
+sync_frequency_set_mapping (const GValue       *value,
+                            const GVariantType *expected_type,
+                            gpointer            user_data)
+{
+  gint i = g_value_get_int (value);
+
+  if (i >= (gint)G_N_ELEMENTS (sync_frequency_minutes))
+    return NULL;
+
+  return g_variant_new_uint32 (sync_frequency_minutes[i]);
+}
+
+static GListModel *
+create_sync_frequency_minutes_model ()
+{
+  GListStore *list_store = g_list_store_new (HDY_TYPE_VALUE_OBJECT);
+  HdyValueObject *obj;
+  g_auto (GValue) value = G_VALUE_INIT;
+  guint i;
+
+  g_value_init (&value, G_TYPE_UINT);
+
+  for (i = 0; i < G_N_ELEMENTS (sync_frequency_minutes); i++) {
+    g_value_set_uint (&value, sync_frequency_minutes[i]);
+    obj = hdy_value_object_new (&value);
+    g_list_store_insert (list_store, i, obj);
+    g_clear_object (&obj);
+  }
+
+  return G_LIST_MODEL (list_store);
+}
+
+static gchar *
+get_sync_frequency_minutes_name (HdyValueObject *value)
+{
+  return g_strdup_printf ("%u min", g_value_get_uint (hdy_value_object_get_value (value)));
+}
+
+void
+ephy_firefox_sync_dialog_setup (EphyFirefoxSyncDialog *sync_dialog)
+{
+  EphySyncService *service = ephy_shell_get_sync_service (ephy_shell_get_default ());
+  GSettings *sync_settings = ephy_settings_get (EPHY_PREFS_SYNC_SCHEMA);
+  char *user = ephy_sync_utils_get_sync_user ();
+  char *name = ephy_sync_utils_get_device_name ();
+  g_autoptr (GListModel) sync_frequency_minutes_model = create_sync_frequency_minutes_model ();
+
+  gtk_entry_set_text (GTK_ENTRY (sync_dialog->sync_device_name_entry), name);
+
+  if (!user) {
+    sync_setup_firefox_iframe (sync_dialog);
+    gtk_widget_hide (sync_dialog->sync_firefox_account_box);
+    gtk_widget_hide (sync_dialog->sync_options_box);
+  } else {
+    sync_set_last_sync_time (sync_dialog);
+    hdy_preferences_row_set_title (HDY_PREFERENCES_ROW (sync_dialog->sync_firefox_account_row), user);
+    gtk_widget_hide (sync_dialog->sync_page_box);
+  }
+
+  g_settings_bind (sync_settings,
+                   EPHY_PREFS_SYNC_BOOKMARKS_ENABLED,
+                   sync_dialog->sync_bookmarks_switch,
+                   "active",
+                   G_SETTINGS_BIND_DEFAULT);
+  g_settings_bind (sync_settings,
+                   EPHY_PREFS_SYNC_PASSWORDS_ENABLED,
+                   sync_dialog->sync_passwords_switch,
+                   "active",
+                   G_SETTINGS_BIND_DEFAULT);
+  g_settings_bind (sync_settings,
+                   EPHY_PREFS_SYNC_HISTORY_ENABLED,
+                   sync_dialog->sync_history_switch,
+                   "active",
+                   G_SETTINGS_BIND_DEFAULT);
+  g_settings_bind (sync_settings,
+                   EPHY_PREFS_SYNC_OPEN_TABS_ENABLED,
+                   sync_dialog->sync_open_tabs_switch,
+                   "active",
+                   G_SETTINGS_BIND_DEFAULT);
+
+  hdy_combo_row_bind_name_model (HDY_COMBO_ROW (sync_dialog->sync_frequency_row),
+                                 sync_frequency_minutes_model,
+                                 (HdyComboRowGetNameFunc)get_sync_frequency_minutes_name,
+                                 NULL,
+                                 NULL);
+  g_settings_bind_with_mapping (sync_settings,
+                                EPHY_PREFS_SYNC_FREQUENCY,
+                                sync_dialog->sync_frequency_row,
+                                "selected-index",
+                                G_SETTINGS_BIND_DEFAULT,
+                                sync_frequency_get_mapping,
+                                sync_frequency_set_mapping,
+                                NULL, NULL);
+
+  g_object_bind_property (sync_dialog->sync_open_tabs_switch, "active",
+                          sync_dialog->synced_tabs_button, "sensitive",
+                          G_BINDING_SYNC_CREATE);
+
+  g_signal_connect_object (service, "sync-secrets-store-finished",
+                           G_CALLBACK (sync_secrets_store_finished_cb),
+                           sync_dialog, 0);
+  g_signal_connect_object (service, "sync-sign-in-error",
+                           G_CALLBACK (sync_sign_in_error_cb),
+                           sync_dialog, 0);
+  g_signal_connect_object (service, "sync-finished",
+                           G_CALLBACK (sync_finished_cb),
+                           sync_dialog, 0);
+  g_signal_connect_object (sync_dialog->sync_bookmarks_switch, "notify::active",
+                           G_CALLBACK (sync_collection_toggled_cb),
+                           sync_dialog, 0);
+  g_signal_connect_object (sync_dialog->sync_passwords_switch, "notify::active",
+                           G_CALLBACK (sync_collection_toggled_cb),
+                           sync_dialog, 0);
+  g_signal_connect_object (sync_dialog->sync_history_switch, "notify::active",
+                           G_CALLBACK (sync_collection_toggled_cb),
+                           sync_dialog, 0);
+  g_signal_connect_object (sync_dialog->sync_open_tabs_switch, "notify::active",
+                           G_CALLBACK (sync_collection_toggled_cb),
+                           sync_dialog, 0);
+
+  g_free (user);
+  g_free (name);
+}
+
+static void
+ephy_firefox_sync_dialog_init (EphyFirefoxSyncDialog *sync_dialog)
+{
+  gtk_widget_init_template (GTK_WIDGET (sync_dialog));
+  ephy_firefox_sync_dialog_setup (sync_dialog);
+}
+
+GtkWidget *
+ephy_firefox_sync_dialog_new ()
+{
+  return GTK_WIDGET (g_object_new (EPHY_TYPE_FIREFOX_SYNC_DIALOG, NULL));
+}
diff --git a/src/ephy-firefox-sync-dialog.h b/src/ephy-firefox-sync-dialog.h
new file mode 100644
index 000000000..9e20ed484
--- /dev/null
+++ b/src/ephy-firefox-sync-dialog.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2020 Epiphany Developers
+ *
+ *  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 <handy.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_FIREFOX_SYNC_DIALOG (ephy_firefox_sync_dialog_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphyFirefoxSyncDialog, ephy_firefox_sync_dialog, EPHY, FIREFOX_SYNC_DIALOG, HdyWindow)
+
+GtkWidget *ephy_firefox_sync_dialog_new ();
+
+G_END_DECLS
diff --git a/src/ephy-shell.c b/src/ephy-shell.c
index 01854f42b..2ee5684f4 100644
--- a/src/ephy-shell.c
+++ b/src/ephy-shell.c
@@ -28,6 +28,7 @@
 #include "ephy-embed-container.h"
 #include "ephy-embed-utils.h"
 #include "ephy-file-helpers.h"
+#include "ephy-firefox-sync-dialog.h"
 #include "ephy-gui.h"
 #include "ephy-header-bar.h"
 #include "ephy-history-dialog.h"
@@ -62,6 +63,7 @@ struct _EphyShell {
   EphyOpenTabsManager *open_tabs_manager;
   GNetworkMonitor *network_monitor;
   GtkWidget *history_dialog;
+  GtkWidget *firefox_sync_dialog;
   GObject *prefs_dialog;
   EphyShellStartupContext *local_startup_context;
   EphyShellStartupContext *remote_startup_context;
@@ -241,6 +243,18 @@ show_history (GSimpleAction *action,
   window_cmd_show_history (NULL, NULL, EPHY_WINDOW (window));
 }
 
+static void
+show_firefox_sync (GSimpleAction *action,
+                   GVariant      *parameter,
+                   gpointer       user_data)
+{
+  GtkWindow *window;
+
+  window = gtk_application_get_active_window (GTK_APPLICATION (ephy_shell));
+
+  window_cmd_show_firefox_sync (NULL, NULL, EPHY_WINDOW (window));
+}
+
 static void
 show_preferences (GSimpleAction *action,
                   GVariant      *parameter,
@@ -349,6 +363,7 @@ static GActionEntry app_entries[] = {
   { "export-bookmarks", export_bookmarks, NULL, NULL, NULL },
   { "import-passwords", import_passwords, NULL, NULL, NULL },
   { "history", show_history, NULL, NULL, NULL },
+  { "firefox-sync-dialog", show_firefox_sync, NULL, NULL, NULL },
   { "preferences", show_preferences, NULL, NULL, NULL },
   { "shortcuts", show_shortcuts, NULL, NULL, NULL },
   { "help", show_help, NULL, NULL, NULL },
@@ -1216,6 +1231,25 @@ ephy_shell_get_history_dialog (EphyShell *shell)
   return shell->history_dialog;
 }
 
+/**
+ * ephy_shell_get_firefox_sync_dialog:
+ *
+ * Return value: (transfer none):
+ **/
+GtkWidget *
+ephy_shell_get_firefox_sync_dialog (EphyShell *shell)
+{
+  if (shell->firefox_sync_dialog == NULL) {
+    shell->firefox_sync_dialog = ephy_firefox_sync_dialog_new ();
+    g_signal_connect (shell->firefox_sync_dialog,
+                      "destroy",
+                      G_CALLBACK (gtk_widget_destroyed),
+                      &shell->firefox_sync_dialog);
+  }
+
+  return shell->firefox_sync_dialog;
+}
+
 /**
  * ephy_shell_get_prefs_dialog:
  *
diff --git a/src/ephy-shell.h b/src/ephy-shell.h
index e7a63f944..ed39f695c 100644
--- a/src/ephy-shell.h
+++ b/src/ephy-shell.h
@@ -79,54 +79,55 @@ typedef struct {
   guint32 user_time;
 } EphyShellStartupContext;
 
-EphyShell               *ephy_shell_get_default           (void);
-
-EphyEmbed               *ephy_shell_new_tab               (EphyShell        *shell,
-                                                           EphyWindow       *parent_window,
-                                                           EphyEmbed        *previous_embed,
-                                                           EphyNewTabFlags   flags);
-
-EphyEmbed               *ephy_shell_new_tab_full          (EphyShell        *shell,
-                                                           const char       *title,
-                                                           WebKitWebView    *related_view,
-                                                           EphyWindow       *parent_window,
-                                                           EphyEmbed        *previous_embed,
-                                                           EphyNewTabFlags   flags,
-                                                           guint32           user_time);
-
-EphySession             *ephy_shell_get_session           (EphyShell        *shell);
-GNetworkMonitor         *ephy_shell_get_net_monitor       (EphyShell        *shell);
-EphyBookmarksManager    *ephy_shell_get_bookmarks_manager (EphyShell        *shell);
-EphyHistoryManager      *ephy_shell_get_history_manager   (EphyShell        *shell);
-EphyOpenTabsManager     *ephy_shell_get_open_tabs_manager (EphyShell        *shell);
-EphySyncService         *ephy_shell_get_sync_service      (EphyShell        *shell);
-
-GtkWidget               *ephy_shell_get_history_dialog    (EphyShell        *shell);
-GObject                 *ephy_shell_get_prefs_dialog      (EphyShell        *shell);
-
-guint                    ephy_shell_get_n_windows         (EphyShell        *shell);
-gboolean                 ephy_shell_close_all_windows     (EphyShell        *shell);
-
-void                     ephy_shell_try_quit              (EphyShell        *shell);
-
-void                     ephy_shell_open_uris             (EphyShell        *shell,
-                                                           const char      **uris,
-                                                           EphyStartupMode   startup_mode,
-                                                           guint32           user_time);
-
-void                     ephy_shell_set_startup_context   (EphyShell                *shell,
-                                                           EphyShellStartupContext  *ctx);
-EphyShellStartupContext *ephy_shell_startup_context_new   (EphyStartupMode           startup_mode,
-                                                           char                     *session_filename,
-                                                           char                    **arguments,
-                                                           guint32                   user_time);
-
-void                     _ephy_shell_create_instance      (EphyEmbedShellMode mode);
-
-void                     ephy_shell_send_notification     (EphyShell        *shell,
-                                                           gchar            *id,
-                                                           GNotification    *notification);
-
-gboolean                 ephy_shell_startup_finished      (EphyShell *shell);
+EphyShell               *ephy_shell_get_default             (void);
+
+EphyEmbed               *ephy_shell_new_tab                 (EphyShell        *shell,
+                                                             EphyWindow       *parent_window,
+                                                             EphyEmbed        *previous_embed,
+                                                             EphyNewTabFlags   flags);
+
+EphyEmbed               *ephy_shell_new_tab_full            (EphyShell        *shell,
+                                                             const char       *title,
+                                                             WebKitWebView    *related_view,
+                                                             EphyWindow       *parent_window,
+                                                             EphyEmbed        *previous_embed,
+                                                             EphyNewTabFlags   flags,
+                                                             guint32           user_time);
+
+EphySession             *ephy_shell_get_session             (EphyShell        *shell);
+GNetworkMonitor         *ephy_shell_get_net_monitor         (EphyShell        *shell);
+EphyBookmarksManager    *ephy_shell_get_bookmarks_manager   (EphyShell        *shell);
+EphyHistoryManager      *ephy_shell_get_history_manager     (EphyShell        *shell);
+EphyOpenTabsManager     *ephy_shell_get_open_tabs_manager   (EphyShell        *shell);
+EphySyncService         *ephy_shell_get_sync_service        (EphyShell        *shell);
+
+GtkWidget               *ephy_shell_get_history_dialog      (EphyShell        *shell);
+GtkWidget               *ephy_shell_get_firefox_sync_dialog (EphyShell        *shell);
+GObject                 *ephy_shell_get_prefs_dialog        (EphyShell        *shell);
+
+guint                    ephy_shell_get_n_windows           (EphyShell        *shell);
+gboolean                 ephy_shell_close_all_windows       (EphyShell        *shell);
+
+void                     ephy_shell_try_quit                (EphyShell        *shell);
+
+void                     ephy_shell_open_uris               (EphyShell        *shell,
+                                                             const char      **uris,
+                                                             EphyStartupMode   startup_mode,
+                                                             guint32           user_time);
+
+void                     ephy_shell_set_startup_context     (EphyShell                *shell,
+                                                             EphyShellStartupContext  *ctx);
+EphyShellStartupContext *ephy_shell_startup_context_new     (EphyStartupMode           startup_mode,
+                                                             char                     *session_filename,
+                                                             char                    **arguments,
+                                                             guint32                   user_time);
+
+void                     _ephy_shell_create_instance        (EphyEmbedShellMode mode);
+
+void                     ephy_shell_send_notification       (EphyShell        *shell,
+                                                             gchar            *id,
+                                                             GNotification    *notification);
+
+gboolean                 ephy_shell_startup_finished        (EphyShell *shell);
 
 G_END_DECLS
diff --git a/src/meson.build b/src/meson.build
index c2a63ff2e..09697cafa 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -26,6 +26,7 @@ libephymain_sources = [
   'ephy-desktop-utils.c',
   'ephy-encoding-dialog.c',
   'ephy-encoding-row.c',
+  'ephy-firefox-sync-dialog.c',
   'ephy-header-bar.c',
   'ephy-history-dialog.c',
   'ephy-link.c',
diff --git a/src/resources/epiphany.gresource.xml b/src/resources/epiphany.gresource.xml
index d65cdc58d..1a2466c16 100644
--- a/src/resources/epiphany.gresource.xml
+++ b/src/resources/epiphany.gresource.xml
@@ -22,6 +22,7 @@
     <file preprocess="xml-stripblanks" compressed="true">gtk/data-view.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">gtk/encoding-dialog.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">gtk/encoding-row.ui</file>
+    <file preprocess="xml-stripblanks" compressed="true">gtk/firefox-sync-dialog.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">gtk/history-dialog.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">gtk/lang-row.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">gtk/notebook-context-menu.ui</file>
diff --git a/src/resources/gtk/firefox-sync-dialog.ui b/src/resources/gtk/firefox-sync-dialog.ui
new file mode 100644
index 000000000..1227113fc
--- /dev/null
+++ b/src/resources/gtk/firefox-sync-dialog.ui
@@ -0,0 +1,226 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.0"/>
+  <template class="EphyFirefoxSyncDialog" parent="HdyWindow">
+    <property name="visible">True</property>
+    <property name="modal">True</property>
+    <property name="window_position">center-on-parent</property>
+    <property name="default-width">640</property>
+    <property name="default-height">800</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="type_hint">dialog</property>
+    <child>
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="HdyHeaderBar">
+            <property name="visible">True</property>
+            <property name="decoration-layout">:close</property>
+            <property name="show-close-button">True</property>
+            <property name="title" translatable="yes">Firefox Sync</property>
+          </object>
+        </child>
+        <child>
+          <object class="HdyClamp">
+            <property name="visible">True</property>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="HdyPreferencesGroup" id="sync_page_box">
+                    <property name="description" translatable="yes">Sign in with your Firefox account to 
sync your data with Web and Firefox on other computers. Web is not Firefox and is not produced or endorsed by 
Mozilla.</property>
+                    <property name="visible">True</property>
+                    <child>
+                      <object class="GtkBox" id="sync_firefox_iframe_box">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkLabel" id="sync_firefox_iframe_label">
+                            <property name="visible">False</property>
+                            <property name="halign">start</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="HdyPreferencesGroup" id="sync_firefox_account_box">
+                    <property name="title" translatable="yes">Firefox Account</property>
+                    <property name="visible">True</property>
+                    <child>
+                      <object class="HdyActionRow" id="sync_firefox_account_row">
+                        <property name="subtitle" translatable="yes">Logged in</property>
+                        <property name="use_underline">True</property>
+                        <property name="visible">True</property>
+                        <child>
+                          <object class="GtkButton" id="sync_sign_out_button">
+                            <property name="label" translatable="yes">Sign _out</property>
+                            <property name="use-underline">True</property>
+                            <property name="valign">center</property>
+                            <property name="visible">True</property>
+                            <signal name="clicked" handler="on_sync_sign_out_button_clicked"/>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="HdyPreferencesGroup" id="sync_options_box">
+                    <property name="title" translatable="yes">Sync Options</property>
+                    <property name="visible">True</property>
+                    <child>
+                      <object class="HdyActionRow">
+                        <property name="activatable_widget">sync_bookmarks_switch</property>
+                        <property name="title" translatable="yes">Sync _Bookmarks</property>
+                        <property name="use_underline">True</property>
+                        <property name="visible">True</property>
+                        <child>
+                          <object class="GtkSwitch" id="sync_bookmarks_switch">
+                            <property name="valign">center</property>
+                            <property name="visible">True</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="HdyActionRow">
+                        <property name="activatable_widget">sync_passwords_switch</property>
+                        <property name="title" translatable="yes">Sync _Passwords</property>
+                        <property name="use_underline">True</property>
+                        <property name="visible">True</property>
+                        <child>
+                          <object class="GtkSwitch" id="sync_passwords_switch">
+                            <property name="valign">center</property>
+                            <property name="visible">True</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="HdyActionRow">
+                        <property name="activatable_widget">sync_history_switch</property>
+                        <property name="title" translatable="yes">Sync _History</property>
+                        <property name="use_underline">True</property>
+                        <property name="visible">True</property>
+                        <child>
+                          <object class="GtkSwitch" id="sync_history_switch">
+                            <property name="valign">center</property>
+                            <property name="visible">True</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="HdyActionRow">
+                        <property name="activatable_widget">sync_open_tabs_switch</property>
+                        <property name="title" translatable="yes">Sync Open _Tabs</property>
+                        <property name="use_underline">True</property>
+                        <property name="visible">True</property>
+                        <child>
+                          <object class="GtkSwitch" id="sync_open_tabs_switch">
+                            <property name="valign">center</property>
+                            <property name="visible">True</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="synced_tabs_button">
+                            <property name="label" translatable="yes">S_ynced tabs</property>
+                            <property name="use-underline">True</property>
+                            <property name="valign">center</property>
+                            <property name="visible">True</property>
+                            <signal name="clicked" handler="on_sync_synced_tabs_button_clicked"/>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="HdyComboRow" id="sync_frequency_row">
+                        <property name="title" translatable="yes">Frequency</property>
+                        <property name="visible">True</property>
+                        <child>
+                          <object class="GtkSeparator">
+                            <property name="margin_bottom">8</property>
+                            <property name="margin_top">8</property>
+                            <property name="visible">True</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="sync_now_button">
+                            <property name="label" translatable="yes">Sync _now</property>
+                            <property name="use-underline">True</property>
+                            <property name="valign">center</property>
+                            <property name="visible">True</property>
+                            <signal name="clicked" handler="on_sync_sync_now_button_clicked"/>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="HdyActionRow">
+                        <property name="activatable">False</property>
+                        <property name="title" translatable="yes">Device name</property>
+                        <property name="use_underline">True</property>
+                        <property name="visible">True</property>
+                        <child>
+                          <object class="GtkButton" id="sync_device_name_cancel_button">
+                            <property name="label" translatable="yes">_Cancel</property>
+                            <property name="use-underline">True</property>
+                            <property name="valign">center</property>
+                            <property name="visible">False</property>
+                            <signal name="clicked" handler="on_sync_device_name_cancel_button_clicked"/>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="sync_device_name_save_button">
+                            <property name="label" translatable="yes">_Save</property>
+                            <property name="use-underline">True</property>
+                            <property name="valign">center</property>
+                            <property name="visible">False</property>
+                            <signal name="clicked" handler="on_sync_device_name_save_button_clicked"/>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="sync_device_name_change_button">
+                            <property name="label" translatable="yes">_Change</property>
+                            <property name="use-underline">True</property>
+                            <property name="valign">center</property>
+                            <property name="visible">True</property>
+                            <signal name="clicked" handler="on_sync_device_name_change_button_clicked"/>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="sync_device_name_entry">
+                            <property name="sensitive">False</property>
+                            <property name="margin-start">12</property>
+                            <property name="margin-end">12</property>
+                            <property name="margin-top">8</property>
+                            <property name="margin-bottom">8</property>
+                            <property name="visible">True</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkSizeGroup">
+    <property name="mode">horizontal</property>
+    <widgets>
+      <widget name="sync_sign_out_button"/>
+      <widget name="synced_tabs_button"/>
+      <widget name="sync_now_button"/>
+      <widget name="sync_device_name_change_button"/>
+    </widgets>
+  </object>
+</interface>
diff --git a/src/resources/gtk/page-menu-popover.ui b/src/resources/gtk/page-menu-popover.ui
index 6942c99a2..366b2ddc5 100644
--- a/src/resources/gtk/page-menu-popover.ui
+++ b/src/resources/gtk/page-menu-popover.ui
@@ -386,6 +386,22 @@
             <property name="visible">True</property>
           </object>
         </child>
+        <child>
+          <object class="GtkSeparator">
+            <property name="orientation">horizontal</property>
+            <property name="margin-top">6</property>
+            <property name="margin-bottom">6</property>
+            <property name="visible">True</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkModelButton">
+            <property name="can_focus">True</property>
+            <property name="text" translatable="yes">Firefox _Sync</property>
+            <property name="action-name">app.firefox-sync-dialog</property>
+            <property name="visible">True</property>
+          </object>
+        </child>
       </object>
       <packing>
         <property name="submenu">import_export</property>
diff --git a/src/window-commands.c b/src/window-commands.c
index 31460d0a7..c4ed583d5 100644
--- a/src/window-commands.c
+++ b/src/window-commands.c
@@ -870,6 +870,21 @@ window_cmd_show_history (GSimpleAction *action,
   gtk_window_present_with_time (GTK_WINDOW (dialog), gtk_get_current_event_time ());
 }
 
+void
+window_cmd_show_firefox_sync (GSimpleAction *action,
+                              GVariant      *parameter,
+                              gpointer       user_data)
+{
+  GtkWidget *dialog;
+
+  dialog = ephy_shell_get_firefox_sync_dialog (ephy_shell_get_default ());
+
+  if (GTK_WINDOW (user_data) != gtk_window_get_transient_for (GTK_WINDOW (dialog)))
+    gtk_window_set_transient_for (GTK_WINDOW (dialog),
+                                  GTK_WINDOW (user_data));
+  gtk_window_present_with_time (GTK_WINDOW (dialog), gtk_get_current_event_time ());
+}
+
 void
 window_cmd_show_preferences (GSimpleAction *action,
                              GVariant      *parameter,
diff --git a/src/window-commands.h b/src/window-commands.h
index cab9e75f9..353251e7f 100644
--- a/src/window-commands.h
+++ b/src/window-commands.h
@@ -41,6 +41,9 @@ void window_cmd_export_bookmarks                (GSimpleAction *action,
 void window_cmd_show_history                    (GSimpleAction *action,
                                                  GVariant      *parameter,
                                                  gpointer       user_data);
+void window_cmd_show_firefox_sync               (GSimpleAction *action,
+                                                 GVariant      *parameter,
+                                                 gpointer       user_data);
 void window_cmd_show_preferences                (GSimpleAction *action,
                                                  GVariant      *parameter,
                                                  gpointer       user_data);


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