[epiphany] web-extension: Use the new JavaScriptCore GLib API instead of DOM API



commit 85f6037a8d6785b6b2a5a4bfd83ea595d35aeb52
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Mon Mar 12 17:21:45 2018 +0100

    web-extension: Use the new JavaScriptCore GLib API instead of DOM API
    
    https://bugzilla.gnome.org/show_bug.cgi?id=794395

 embed/ephy-embed-shell.c                           |   12 +-
 embed/ephy-embed-utils.c                           |   34 +-
 embed/ephy-embed-utils.h                           |    2 -
 embed/ephy-web-extension-proxy.c                   |  186 ---
 embed/ephy-web-extension-proxy.h                   |   27 -
 embed/ephy-web-view.c                              |  134 ++-
 embed/web-extension/ephy-embed-form-auth.c         |  152 ---
 embed/web-extension/ephy-embed-form-auth.h         |   51 -
 embed/web-extension/ephy-web-dom-utils.c           |  804 ------------
 embed/web-extension/ephy-web-dom-utils.h           |   61 -
 embed/web-extension/ephy-web-extension.c           | 1293 +++++---------------
 embed/web-extension/meson.build                    |   12 +-
 .../resources/epiphany-web-extension.gresource.xml |    6 +
 embed/web-extension/resources/js/ephy.js           |  602 +++++++++
 lib/ephy-permissions-manager.c                     |   61 +
 lib/ephy-permissions-manager.h                     |    5 +
 lib/sync/ephy-password-manager.c                   |  149 +++
 lib/sync/ephy-password-manager.h                   |    4 +
 meson.build                                        |    2 +-
 src/prefs-dialog.c                                 |    2 +-
 20 files changed, 1201 insertions(+), 2398 deletions(-)
---
diff --git a/embed/ephy-embed-shell.c b/embed/ephy-embed-shell.c
index 65ef092..fb3767f 100644
--- a/embed/ephy-embed-shell.c
+++ b/embed/ephy-embed-shell.c
@@ -203,7 +203,7 @@ web_extension_form_auth_data_message_received_cb (WebKitUserContentManager *mana
   GVariant *variant;
   gchar *message_str;
 
-  message_str = ephy_embed_utils_get_js_result_as_string (message);
+  message_str = jsc_value_to_string (webkit_javascript_result_get_js_value (message));
   variant = g_variant_parse (G_VARIANT_TYPE ("(utss)"), message_str, NULL, NULL, NULL);
   g_free (message_str);
 
@@ -223,7 +223,7 @@ web_extension_sensitive_form_focused_message_received_cb (WebKitUserContentManag
   GVariant *variant;
   char *message_str;
 
-  message_str = ephy_embed_utils_get_js_result_as_string (message);
+  message_str = jsc_value_to_string (webkit_javascript_result_get_js_value (message));
   variant = g_variant_parse (G_VARIANT_TYPE ("(tb)"), message_str, NULL, NULL, NULL);
   g_free (message_str);
 
@@ -317,7 +317,7 @@ web_extension_overview_message_received_cb (WebKitUserContentManager *manager,
   EphyEmbedShellPrivate *priv = ephy_embed_shell_get_instance_private (shell);
   char *url_to_remove;
 
-  url_to_remove = ephy_embed_utils_get_js_result_as_string (message);
+  url_to_remove = jsc_value_to_string (webkit_javascript_result_get_js_value (message));
 
   priv->hiding_overview_item++;
   ephy_history_service_set_url_hidden (priv->global_history_service,
@@ -341,7 +341,7 @@ web_extension_tls_error_page_message_received_cb (WebKitUserContentManager *mana
 {
   guint64 page_id;
 
-  page_id = ephy_embed_utils_get_js_result_as_number (message);
+  page_id = jsc_value_to_double (webkit_javascript_result_get_js_value (message));
   g_signal_emit (shell, signals[ALLOW_TLS_CERTIFICATE], 0, page_id);
 }
 
@@ -352,7 +352,7 @@ web_extension_unsafe_browsing_error_page_message_received_cb (WebKitUserContentM
 {
   guint64 page_id;
 
-  page_id = ephy_embed_utils_get_js_result_as_number (message);
+  page_id = jsc_value_to_double (webkit_javascript_result_get_js_value (message));
   g_signal_emit (shell, signals[ALLOW_UNSAFE_BROWSING], 0, page_id);
 }
 
@@ -363,7 +363,7 @@ web_extension_about_apps_message_received_cb (WebKitUserContentManager *manager,
 {
   char *app_id;
 
-  app_id = ephy_embed_utils_get_js_result_as_string (message);
+  app_id = jsc_value_to_string (webkit_javascript_result_get_js_value (message));
   ephy_web_application_delete (app_id);
   g_free (app_id);
 }
diff --git a/embed/ephy-embed-utils.c b/embed/ephy-embed-utils.c
index 5a20b9e..ea2e0db 100644
--- a/embed/ephy-embed-utils.c
+++ b/embed/ephy-embed-utils.c
@@ -28,8 +28,8 @@
 #include "ephy-settings.h"
 #include "ephy-string.h"
 
-#include <JavaScriptCore/JavaScript.h>
 #include <glib/gi18n.h>
+#include <jsc/jsc.h>
 #include <libsoup/soup.h>
 #include <string.h>
 
@@ -370,38 +370,6 @@ ephy_embed_utils_urls_have_same_origin (const char *a_url,
   return retval;
 }
 
-char *
-ephy_embed_utils_get_js_result_as_string (WebKitJavascriptResult *js_result)
-{
-  JSValueRef js_value;
-  JSStringRef js_string;
-  size_t max_size;
-  char *retval = NULL;
-
-  js_value = webkit_javascript_result_get_value (js_result);
-  js_string = JSValueToStringCopy (webkit_javascript_result_get_global_context (js_result),
-                                   js_value, NULL);
-  max_size = JSStringGetMaximumUTF8CStringSize (js_string);
-  if (max_size) {
-    retval = g_malloc (max_size);
-    JSStringGetUTF8CString (js_string, retval, max_size);
-  }
-  JSStringRelease (js_string);
-
-  return retval;
-}
-
-double
-ephy_embed_utils_get_js_result_as_number (WebKitJavascriptResult *js_result)
-{
-  JSValueRef js_value;
-
-  js_value = webkit_javascript_result_get_value (js_result);
-
-  return JSValueToNumber (webkit_javascript_result_get_global_context (js_result),
-                          js_value, NULL);
-}
-
 void
 ephy_embed_utils_shutdown (void)
 {
diff --git a/embed/ephy-embed-utils.h b/embed/ephy-embed-utils.h
index 3224876..3f99b7d 100644
--- a/embed/ephy-embed-utils.h
+++ b/embed/ephy-embed-utils.h
@@ -48,8 +48,6 @@ gboolean ephy_embed_utils_is_no_show_address                    (const char *add
 char    *ephy_embed_utils_get_title_from_address                (const char *address);
 gboolean ephy_embed_utils_urls_have_same_origin                 (const char *a_url,
                                                                  const char *b_url);
-char    *ephy_embed_utils_get_js_result_as_string               (WebKitJavascriptResult *js_result);
-double   ephy_embed_utils_get_js_result_as_number               (WebKitJavascriptResult *js_result);
 void     ephy_embed_utils_shutdown                              (void);
 
 G_END_DECLS
diff --git a/embed/ephy-web-extension-proxy.c b/embed/ephy-web-extension-proxy.c
index be0cf97..7d2dc4e 100644
--- a/embed/ephy-web-extension-proxy.c
+++ b/embed/ephy-web-extension-proxy.c
@@ -207,192 +207,6 @@ ephy_web_extension_proxy_form_auth_data_save_confirmation_response (EphyWebExten
                      NULL, NULL);
 }
 
-static void
-has_modified_forms_cb (GDBusProxy   *proxy,
-                       GAsyncResult *result,
-                       GTask        *task)
-{
-  GVariant *return_value;
-  gboolean retval = FALSE;
-
-  return_value = g_dbus_proxy_call_finish (proxy, result, NULL);
-  if (return_value) {
-    g_variant_get (return_value, "(b)", &retval);
-    g_variant_unref (return_value);
-  }
-
-  g_task_return_boolean (task, retval);
-  g_object_unref (task);
-}
-
-void
-ephy_web_extension_proxy_web_page_has_modified_forms (EphyWebExtensionProxy *web_extension,
-                                                      guint64                page_id,
-                                                      GCancellable          *cancellable,
-                                                      GAsyncReadyCallback    callback,
-                                                      gpointer               user_data)
-{
-  GTask *task;
-
-  g_assert (EPHY_IS_WEB_EXTENSION_PROXY (web_extension));
-
-  task = g_task_new (web_extension, cancellable, callback, user_data);
-
-  if (web_extension->proxy) {
-    g_dbus_proxy_call (web_extension->proxy,
-                       "HasModifiedForms",
-                       g_variant_new ("(t)", page_id),
-                       G_DBUS_CALL_FLAGS_NONE,
-                       -1,
-                       web_extension->cancellable,
-                       (GAsyncReadyCallback)has_modified_forms_cb,
-                       g_object_ref (task));
-  } else {
-    g_task_return_boolean (task, FALSE);
-  }
-
-  g_object_unref (task);
-}
-
-gboolean
-ephy_web_extension_proxy_web_page_has_modified_forms_finish (EphyWebExtensionProxy *web_extension,
-                                                             GAsyncResult          *result,
-                                                             GError               **error)
-{
-  g_assert (g_task_is_valid (result, web_extension));
-
-  return g_task_propagate_boolean (G_TASK (result), error);
-}
-
-static void
-get_best_web_app_icon_cb (GDBusProxy   *proxy,
-                          GAsyncResult *result,
-                          GTask        *task)
-{
-  GVariant *retval;
-  GError *error = NULL;
-
-  retval = g_dbus_proxy_call_finish (proxy, result, &error);
-  if (!retval) {
-    g_task_return_error (task, error);
-  } else {
-    g_task_return_pointer (task, retval, (GDestroyNotify)g_variant_unref);
-  }
-  g_object_unref (task);
-}
-
-void
-ephy_web_extension_proxy_get_best_web_app_icon (EphyWebExtensionProxy *web_extension,
-                                                guint64                page_id,
-                                                const char            *base_uri,
-                                                GCancellable          *cancellable,
-                                                GAsyncReadyCallback    callback,
-                                                gpointer               user_data)
-{
-  GTask *task;
-
-  g_assert (EPHY_IS_WEB_EXTENSION_PROXY (web_extension));
-
-  task = g_task_new (web_extension, cancellable, callback, user_data);
-
-  if (web_extension->proxy) {
-    g_dbus_proxy_call (web_extension->proxy,
-                       "GetBestWebAppIcon",
-                       g_variant_new ("(ts)", page_id, base_uri),
-                       G_DBUS_CALL_FLAGS_NONE,
-                       -1,
-                       web_extension->cancellable,
-                       (GAsyncReadyCallback)get_best_web_app_icon_cb,
-                       g_object_ref (task));
-  } else {
-    g_task_return_boolean (task, FALSE);
-  }
-
-  g_object_unref (task);
-}
-
-gboolean
-ephy_web_extension_proxy_get_best_web_app_icon_finish (EphyWebExtensionProxy *web_extension,
-                                                       GAsyncResult          *result,
-                                                       char                 **icon_uri,
-                                                       char                 **icon_color,
-                                                       GError               **error)
-{
-  GVariant *variant;
-  GTask *task = G_TASK (result);
-
-  g_assert (g_task_is_valid (result, web_extension));
-
-  variant = g_task_propagate_pointer (task, error);
-  if (!variant)
-    return FALSE;
-
-  g_variant_get (variant, "(ss)", icon_uri, icon_color);
-  g_variant_unref (variant);
-
-  return TRUE;
-}
-
-static void
-get_web_app_title_cb (GDBusProxy   *proxy,
-                      GAsyncResult *result,
-                      GTask        *task)
-{
-  GVariant *retval;
-  GError *error = NULL;
-
-  retval = g_dbus_proxy_call_finish (proxy, result, &error);
-  if (!retval) {
-    g_task_return_error (task, error);
-  } else {
-    char *title;
-
-    g_variant_get (retval, "(s)", &title);
-    g_task_return_pointer (task, title, (GDestroyNotify)g_free);
-    g_variant_unref (retval);
-  }
-  g_object_unref (task);
-}
-
-void
-ephy_web_extension_proxy_get_web_app_title (EphyWebExtensionProxy *web_extension,
-                                            guint64                page_id,
-                                            GCancellable          *cancellable,
-                                            GAsyncReadyCallback    callback,
-                                            gpointer               user_data)
-{
-  GTask *task;
-
-  g_assert (EPHY_IS_WEB_EXTENSION_PROXY (web_extension));
-
-  task = g_task_new (web_extension, cancellable, callback, user_data);
-
-  if (web_extension->proxy) {
-    g_dbus_proxy_call (web_extension->proxy,
-                       "GetWebAppTitle",
-                       g_variant_new ("(t)", page_id),
-                       G_DBUS_CALL_FLAGS_NONE,
-                       -1,
-                       web_extension->cancellable,
-                       (GAsyncReadyCallback)get_web_app_title_cb,
-                       g_object_ref (task));
-  } else {
-    g_task_return_pointer (task, NULL, NULL);
-  }
-
-  g_object_unref (task);
-}
-
-char *
-ephy_web_extension_proxy_get_web_app_title_finish (EphyWebExtensionProxy *web_extension,
-                                                   GAsyncResult          *result,
-                                                   GError               **error)
-{
-  g_assert (g_task_is_valid (result, web_extension));
-
-  return g_task_propagate_pointer (G_TASK (result), error);
-}
-
 void
 ephy_web_extension_proxy_history_set_urls (EphyWebExtensionProxy *web_extension,
                                            GList                 *urls)
diff --git a/embed/ephy-web-extension-proxy.h b/embed/ephy-web-extension-proxy.h
index 131cb8e..1802246 100644
--- a/embed/ephy-web-extension-proxy.h
+++ b/embed/ephy-web-extension-proxy.h
@@ -32,33 +32,6 @@ EphyWebExtensionProxy *ephy_web_extension_proxy_new
 void                   ephy_web_extension_proxy_form_auth_data_save_confirmation_response 
(EphyWebExtensionProxy *web_extension,
                                                                                            guint             
     request_id,
                                                                                            gboolean          
     response);
-void                   ephy_web_extension_proxy_web_page_has_modified_forms               
(EphyWebExtensionProxy *web_extension,
-                                                                                           guint64           
     page_id,
-                                                                                           GCancellable      
    *cancellable,
-                                                                                           
GAsyncReadyCallback    callback,
-                                                                                           gpointer          
     user_data);
-gboolean               ephy_web_extension_proxy_web_page_has_modified_forms_finish        
(EphyWebExtensionProxy *web_extension,
-                                                                                           GAsyncResult      
    *result,
-                                                                                           GError            
   **error);
-void                   ephy_web_extension_proxy_get_best_web_app_icon                     
(EphyWebExtensionProxy *web_extension,
-                                                                                           guint64           
     page_id,
-                                                                                           const char        
    *base_uri,
-                                                                                           GCancellable      
    *cancellable,
-                                                                                           
GAsyncReadyCallback    callback,
-                                                                                           gpointer          
     user_data);
-gboolean               ephy_web_extension_proxy_get_best_web_app_icon_finish              
(EphyWebExtensionProxy *web_extension,
-                                                                                           GAsyncResult      
    *result,
-                                                                                           char              
   **icon_uri,
-                                                                                           char              
   **icon_color,
-                                                                                           GError            
   **error);
-void                   ephy_web_extension_proxy_get_web_app_title                         
(EphyWebExtensionProxy *web_extension,
-                                                                                           guint64           
     page_id,
-                                                                                           GCancellable      
    *cancellable,
-                                                                                           
GAsyncReadyCallback    callback,
-                                                                                           gpointer          
     user_data);
-char                  *ephy_web_extension_proxy_get_web_app_title_finish                  
(EphyWebExtensionProxy *web_extension,
-                                                                                           GAsyncResult      
    *result,
-                                                                                           GError            
   **error);
 void                   ephy_web_extension_proxy_history_set_urls                          
(EphyWebExtensionProxy *web_extension,
                                                                                            GList             
    *urls);
 void                   ephy_web_extension_proxy_history_set_url_thumbnail                 
(EphyWebExtensionProxy *web_extension,
diff --git a/embed/ephy-web-view.c b/embed/ephy-web-view.c
index 0e114bb..dc73b4f 100644
--- a/embed/ephy-web-view.c
+++ b/embed/ephy-web-view.c
@@ -3093,13 +3093,19 @@ ephy_web_view_set_should_bypass_safe_browsing (EphyWebView *view,
 }
 
 static void
-has_modified_forms_cb (EphyWebExtensionProxy *web_extension,
-                       GAsyncResult          *result,
-                       GTask                 *task)
+has_modified_forms_cb (WebKitWebView *view,
+                       GAsyncResult  *result,
+                       GTask         *task)
 {
-  gboolean retval;
+  WebKitJavascriptResult *js_result;
+  gboolean retval = FALSE;
+
+  js_result = webkit_web_view_run_javascript_finish (view, result, NULL);
+  if (js_result) {
+    retval = jsc_value_to_boolean (webkit_javascript_result_get_js_value (js_result));
+    webkit_javascript_result_unref (js_result);
+  }
 
-  retval = ephy_web_extension_proxy_web_page_has_modified_forms_finish (web_extension, result, NULL);
   g_task_return_boolean (task, retval);
   g_object_unref (task);
 }
@@ -3129,18 +3135,11 @@ ephy_web_view_has_modified_forms (EphyWebView        *view,
   g_assert (EPHY_IS_WEB_VIEW (view));
 
   task = g_task_new (view, cancellable, callback, user_data);
-
-  if (view->web_extension) {
-    ephy_web_extension_proxy_web_page_has_modified_forms (view->web_extension,
-                                                          webkit_web_view_get_page_id (WEBKIT_WEB_VIEW 
(view)),
-                                                          cancellable,
-                                                          (GAsyncReadyCallback)has_modified_forms_cb,
-                                                          g_object_ref (task));
-  } else {
-    g_task_return_boolean (task, FALSE);
-  }
-
-  g_object_unref (task);
+  webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (view),
+                                  "Ephy.hasModifiedForms();",
+                                  cancellable,
+                                  (GAsyncReadyCallback)has_modified_forms_cb,
+                                  task);
 }
 
 gboolean
@@ -3168,22 +3167,35 @@ get_best_web_app_icon_async_data_free (GetBestWebAppIconAsyncData *data)
 }
 
 static void
-get_best_web_app_icon_cb (EphyWebExtensionProxy *web_extension,
-                          GAsyncResult          *result,
-                          GTask                 *task)
+get_best_web_app_icon_cb (WebKitWebView *view,
+                          GAsyncResult  *result,
+                          GTask         *task)
 {
-  char *uri = NULL;
-  char *color = NULL;
+  WebKitJavascriptResult *js_result;
   GError *error = NULL;
 
-  if (!ephy_web_extension_proxy_get_best_web_app_icon_finish (web_extension, result, &uri, &color, &error)) {
-    g_task_return_error (task, error);
-  } else {
-    GetBestWebAppIconAsyncData *data = g_slice_new0 (GetBestWebAppIconAsyncData);
-    data->icon_uri = uri;
-    data->icon_color = color;
+  js_result = webkit_web_view_run_javascript_finish (view, result, &error);
+  if (js_result) {
+    JSCValue *js_value, *js_uri, *js_color;
+    GetBestWebAppIconAsyncData *data;
+
+    data = g_slice_new0 (GetBestWebAppIconAsyncData);
+    js_value = webkit_javascript_result_get_js_value (js_result);
+    g_assert (jsc_value_is_object (js_value));
+
+    js_uri = jsc_value_object_get_property (js_value, "url");
+    data->icon_uri = jsc_value_to_string (js_uri);
+    g_object_unref (js_uri);
+
+    js_color = jsc_value_object_get_property (js_value, "icon");
+    data->icon_color = jsc_value_is_null (js_color) || jsc_value_is_undefined (js_color) ? NULL : 
jsc_value_to_string (js_color);
+    g_object_unref (js_color);
+
     g_task_return_pointer (task, data, (GDestroyNotify)get_best_web_app_icon_async_data_free);
-  }
+    webkit_javascript_result_unref (js_result);
+  } else
+    g_task_return_error (task, error);
+
   g_object_unref (task);
 }
 
@@ -3193,24 +3205,21 @@ ephy_web_view_get_best_web_app_icon (EphyWebView        *view,
                                      GAsyncReadyCallback callback,
                                      gpointer            user_data)
 {
+  WebKitWebView *wk_view;
   GTask *task;
+  char *script;
 
   g_assert (EPHY_IS_WEB_VIEW (view));
+  wk_view = WEBKIT_WEB_VIEW (view);
 
   task = g_task_new (view, cancellable, callback, user_data);
-
-  if (view->web_extension) {
-    ephy_web_extension_proxy_get_best_web_app_icon (view->web_extension,
-                                                    webkit_web_view_get_page_id (WEBKIT_WEB_VIEW (view)),
-                                                    webkit_web_view_get_uri (WEBKIT_WEB_VIEW (view)),
-                                                    cancellable,
-                                                    (GAsyncReadyCallback)get_best_web_app_icon_cb,
-                                                    g_object_ref (task));
-  } else {
-    g_task_return_boolean (task, FALSE);
-  }
-
-  g_object_unref (task);
+  script = g_strdup_printf ("Ephy.getWebAppIcon(\"%s\");", webkit_web_view_get_uri (wk_view));
+  webkit_web_view_run_javascript (wk_view,
+                                  script,
+                                  cancellable,
+                                  (GAsyncReadyCallback)get_best_web_app_icon_cb,
+                                  task);
+  g_free (script);
 }
 
 gboolean
@@ -3243,18 +3252,26 @@ ephy_web_view_get_best_web_app_icon_finish (EphyWebView  *view,
 }
 
 static void
-get_web_app_title_cb (EphyWebExtensionProxy *web_extension,
-                      GAsyncResult          *result,
-                      GTask                 *task)
+get_web_app_title_cb (WebKitWebView *view,
+                      GAsyncResult  *result,
+                      GTask         *task)
 {
-  char *retval;
+  WebKitJavascriptResult *js_result;
   GError *error = NULL;
 
-  retval = ephy_web_extension_proxy_get_web_app_title_finish (web_extension, result, &error);
-  if (!retval)
-    g_task_return_error (task, error);
-  else
+  js_result = webkit_web_view_run_javascript_finish (view, result, &error);
+  if (js_result) {
+    JSCValue *js_value;
+    char *retval = NULL;
+
+    js_value = webkit_javascript_result_get_js_value (js_result);
+    if (!jsc_value_is_null (js_value) && !jsc_value_is_undefined (js_value))
+      retval = jsc_value_to_string (js_value);
     g_task_return_pointer (task, retval, (GDestroyNotify)g_free);
+    webkit_javascript_result_unref (js_result);
+  } else
+    g_task_return_error (task, error);
+
   g_object_unref (task);
 }
 
@@ -3269,18 +3286,11 @@ ephy_web_view_get_web_app_title (EphyWebView        *view,
   g_assert (EPHY_IS_WEB_VIEW (view));
 
   task = g_task_new (view, cancellable, callback, user_data);
-
-  if (view->web_extension) {
-    ephy_web_extension_proxy_get_web_app_title (view->web_extension,
-                                                webkit_web_view_get_page_id (WEBKIT_WEB_VIEW (view)),
-                                                cancellable,
-                                                (GAsyncReadyCallback)get_web_app_title_cb,
-                                                g_object_ref (task));
-  } else {
-    g_task_return_pointer (task, NULL, NULL);
-  }
-
-  g_object_unref (task);
+  webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (view),
+                                  "Ephy.getWebAppTitle();",
+                                  cancellable,
+                                  (GAsyncReadyCallback)get_web_app_title_cb,
+                                  task);
 }
 
 char *
diff --git a/embed/web-extension/ephy-web-extension.c b/embed/web-extension/ephy-web-extension.c
index 018c989..3987cd6 100644
--- a/embed/web-extension/ephy-web-extension.c
+++ b/embed/web-extension/ephy-web-extension.c
@@ -24,7 +24,6 @@
 #include "ephy-dbus-names.h"
 #include "ephy-dbus-util.h"
 #include "ephy-debug.h"
-#include "ephy-embed-form-auth.h"
 #include "ephy-file-helpers.h"
 #include "ephy-password-manager.h"
 #include "ephy-permissions-manager.h"
@@ -34,11 +33,11 @@
 #include "ephy-sync-utils.h"
 #include "ephy-uri-helpers.h"
 #include "ephy-uri-tester.h"
-#include "ephy-web-dom-utils.h"
 #include "ephy-web-overview.h"
 
 #include <gio/gio.h>
 #include <gtk/gtk.h>
+#include <jsc/jsc.h>
 #include <libsoup/soup.h>
 #include <string.h>
 #include <webkit2/webkit-web-extension.h>
@@ -67,20 +66,6 @@ static const char introspection_xml[] =
   "  <signal name='PageCreated'>"
   "   <arg type='t' name='page_id' direction='out'/>"
   "  </signal>"
-  "  <method name='HasModifiedForms'>"
-  "   <arg type='t' name='page_id' direction='in'/>"
-  "   <arg type='b' name='has_modified_forms' direction='out'/>"
-  "  </method>"
-  "  <method name='GetWebAppTitle'>"
-  "   <arg type='t' name='page_id' direction='in'/>"
-  "   <arg type='s' name='title' direction='out'/>"
-  "  </method>"
-  "  <method name='GetBestWebAppIcon'>"
-  "   <arg type='t' name='page_id' direction='in'/>"
-  "   <arg type='s' name='base_uri' direction='in'/>"
-  "   <arg type='s' name='uri' direction='out'/>"
-  "   <arg type='s' name='color' direction='out'/>"
-  "  </method>"
   "  <method name='FormAuthDataSaveConfirmationResponse'>"
   "   <arg type='u' name='request_id' direction='in'/>"
   "   <arg type='b' name='should_store' direction='in'/>"
@@ -199,6 +184,52 @@ web_page_send_request (WebKitWebPage     *web_page,
   return FALSE;
 }
 
+typedef struct {
+  char *origin;
+  char *target_origin;
+  char *username;
+  char *password;
+  char *username_field_name;
+  char *password_field_name;
+  gboolean is_new;
+} SaveAuthRequest;
+
+static SaveAuthRequest *
+save_auth_request_new (const char *origin,
+                       const char *target_origin,
+                       const char *username,
+                       const char *password,
+                       const char *username_field_name,
+                       const char *password_field_name,
+                       gboolean    is_new)
+{
+  SaveAuthRequest *request;
+
+  request = g_new (SaveAuthRequest, 1);
+  request->origin = g_strdup (origin);
+  request->target_origin = g_strdup (target_origin);
+  request->username = g_strdup (username);
+  request->password = g_strdup (password);
+  request->username_field_name = g_strdup (username_field_name);
+  request->password_field_name = g_strdup (password_field_name);
+  request->is_new = is_new;
+
+  return request;
+}
+
+static void
+save_auth_request_free (SaveAuthRequest *request)
+{
+  g_free (request->origin);
+  g_free (request->target_origin);
+  g_free (request->username);
+  g_free (request->password);
+  g_free (request->username_field_name);
+  g_free (request->password_field_name);
+
+  g_free (request);
+}
+
 static GHashTable *
 ephy_web_extension_get_form_auth_data_save_requests (EphyWebExtension *extension)
 {
@@ -207,7 +238,7 @@ ephy_web_extension_get_form_auth_data_save_requests (EphyWebExtension *extension
       g_hash_table_new_full (g_direct_hash,
                              g_direct_equal,
                              NULL,
-                             (GDestroyNotify)g_object_unref);
+                             (GDestroyNotify)save_auth_request_free);
   }
 
   return extension->form_auth_data_save_requests;
@@ -221,266 +252,36 @@ form_auth_data_save_request_new_id (void)
   return ++form_auth_data_save_request_id;
 }
 
-static void
-store_password (EphyEmbedFormAuth *form_auth)
-{
-  SoupURI *uri;
-  char *uri_str;
-  char *origin;
-  char *username_field_name = NULL;
-  char *password_field_name = NULL;
-  const char *username = NULL;
-  const char *password = NULL;
-  const char *target_origin;
-  gboolean password_updated;
-  WebKitDOMNode *username_node;
-  EphyWebExtension *extension = ephy_web_extension_get ();
-
-  g_assert (extension->password_manager);
-
-  username_node = ephy_embed_form_auth_get_username_node (form_auth);
-  if (username_node) {
-    g_object_get (username_node,
-                  "name", &username_field_name,
-                  NULL);
-    username = ephy_embed_form_auth_get_username (form_auth);
-  }
-  g_object_get (ephy_embed_form_auth_get_password_node (form_auth),
-                "name", &password_field_name,
-                NULL);
-  password = ephy_embed_form_auth_get_password (form_auth);
-
-  uri = ephy_embed_form_auth_get_uri (form_auth);
-  uri_str = soup_uri_to_string (uri, FALSE);
-  origin = ephy_uri_to_security_origin (uri_str);
-  target_origin = ephy_embed_form_auth_get_target_origin (form_auth);
-  password_updated = ephy_embed_form_auth_get_password_updated (form_auth);
-  ephy_password_manager_save (extension->password_manager,
-                              origin,
-                              target_origin,
-                              username,
-                              password,
-                              username_field_name,
-                              password_field_name,
-                              !password_updated);
-
-  g_free (uri_str);
-  g_free (origin);
-  g_free (username_field_name);
-  g_free (password_field_name);
-}
-
-static void
-request_decision_on_storing (EphyEmbedFormAuth *form_auth)
+static char *
+save_auth_requester (guint64     page_id,
+                     const char *origin,
+                     const char *target_origin,
+                     const char *username,
+                     const char *password,
+                     const char *username_field_name,
+                     const char *password_field_name,
+                     gboolean    is_new)
 {
-  char *username_field_value = NULL;
-  guint request_id;
-  SoupURI *uri;
-  WebKitDOMNode *username_node;
-  WebKitDOMDOMWindow *dom_window = NULL;
   GVariant *variant;
-  char *message = NULL;
-  char *uri_string = NULL;
-  char *origin = NULL;
-
-  dom_window = webkit_dom_document_get_default_view (ephy_embed_form_auth_get_owner_document (form_auth));
-  if (dom_window == NULL)
-    goto out;
-
-  uri = ephy_embed_form_auth_get_uri (form_auth);
-  if (uri == NULL)
-    goto out;
-
-  uri_string = soup_uri_to_string (uri, FALSE);
-  origin = ephy_uri_to_security_origin (uri_string);
-  if (origin == NULL)
-    goto out;
+  guint request_id;
+  char *retval;
 
   request_id = form_auth_data_save_request_new_id ();
-
-  username_node = ephy_embed_form_auth_get_username_node (form_auth);
-  if (username_node)
-    g_object_get (username_node, "value", &username_field_value, NULL);
-
   variant = g_variant_new ("(utss)",
                            request_id,
-                           ephy_embed_form_auth_get_page_id (form_auth),
+                           page_id,
                            origin,
-                           username_field_value ? username_field_value : "");
-  g_free (username_field_value);
+                           username ? username : "");
 
-  message = g_variant_print (variant, FALSE);
+  retval = g_variant_print (variant, FALSE);
   g_variant_unref (variant);
 
-  if (webkit_dom_dom_window_webkit_message_handlers_post_message (dom_window, "formAuthData", message)) {
-    g_hash_table_insert (ephy_web_extension_get_form_auth_data_save_requests (ephy_web_extension_get ()),
-                         GINT_TO_POINTER (request_id),
-                         g_object_ref (form_auth));
-  } else {
-    g_warning ("Error sending formAuthData message");
-  }
-
-out:
-  if (dom_window != NULL)
-    g_object_unref (dom_window);
-  if (form_auth != NULL)
-    g_object_unref (form_auth);
-  if (message != NULL)
-    g_free (message);
-  if (uri_string != NULL)
-    g_free (uri_string);
-  if (origin != NULL)
-    g_free (origin);
-}
-
-static void
-should_store_cb (GList    *records,
-                 gpointer  user_data)
-{
-  EphyEmbedFormAuth *form_auth = EPHY_EMBED_FORM_AUTH (user_data);
-  EphyWebExtension *web_extension;
-  EphyPermission permission;
-  SoupURI *uri;
-  char *uri_string;
-  const char *password;
-  char *origin = NULL;
-
-  uri = ephy_embed_form_auth_get_uri (form_auth);
-  uri_string = soup_uri_to_string (uri, FALSE);
-  if (uri_string == NULL)
-    return;
-  origin = ephy_uri_to_security_origin (uri_string);
-  if (origin == NULL)
-    goto out;
-
-  web_extension = ephy_web_extension_get ();
-  permission = ephy_permissions_manager_get_permission (web_extension->permissions_manager,
-                                                        EPHY_PERMISSION_TYPE_SAVE_PASSWORD,
-                                                        origin);
-
-  if (permission == EPHY_PERMISSION_DENY) {
-    LOG ("User/password storage permission previously denied. Not asking about storing.");
-    goto out;
-  }
-
-  /* We never ask the user in web applications. */
-  if (permission == EPHY_PERMISSION_UNDECIDED && ephy_dot_dir_is_web_application ())
-    permission = EPHY_PERMISSION_PERMIT;
-
-  password = ephy_embed_form_auth_get_password (form_auth);
-  if (password == NULL || strlen (password) == 0)
-    goto out;
-
-  if (records && records->data) {
-    EphyPasswordRecord *record = EPHY_PASSWORD_RECORD (records->data);
-    const char *stored_username = ephy_password_record_get_username (record);
-    const char *stored_password = ephy_password_record_get_password (record);
-    const char *username = ephy_embed_form_auth_get_username (form_auth);
-
-    /* FIXME: We use only the first result, for now; We need to do
-     * something smarter here */
-    if (g_strcmp0 (stored_username, username) == 0 &&
-        g_strcmp0 (stored_password, password) == 0) {
-      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));
-    }
-
-  } 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));
-  }
-
-out:
-  if (origin != NULL)
-    g_free (origin);
-  g_free (uri_string);
-  g_object_unref (form_auth);
-  g_list_free_full (records, g_object_unref);
-}
-
-static void
-handle_form_submission (WebKitWebPage            *web_page,
-                        WebKitDOMHTMLFormElement *dom_form)
-{
-  EphyWebExtension *extension = ephy_web_extension_get ();
-  EphyEmbedFormAuth *form_auth;
-  SoupURI *uri;
-  char *target_origin;
-  WebKitDOMNode *username_node = NULL;
-  WebKitDOMNode *password_node = NULL;
-  char *username_field_name = NULL;
-  char *username_field_value = NULL;
-  char *password_field_name = NULL;
-  char *password_field_value = NULL;
-  char *uri_str;
-  char *origin;
-  char *form_action;
+  g_hash_table_insert (ephy_web_extension_get_form_auth_data_save_requests (ephy_web_extension_get ()),
+                       GINT_TO_POINTER (request_id), save_auth_request_new (origin, target_origin, username,
+                                                                            password, username_field_name,
+                                                                            password_field_name, is_new));
 
-  if (!extension->password_manager)
-    return;
-
-  if (!ephy_web_dom_utils_find_form_auth_elements (dom_form,
-                                                   &username_node,
-                                                   &password_node,
-                                                   AUTH_CACHE_SUBMIT))
-    return;
-
-  if (username_node) {
-    g_object_get (username_node,
-                  "value", &username_field_value,
-                  NULL);
-  }
-  g_object_get (password_node,
-                "value", &password_field_value,
-                NULL);
-
-  form_action = webkit_dom_html_form_element_get_action (dom_form);
-  if (form_action == NULL)
-    form_action = g_strdup (webkit_web_page_get_uri (web_page));
-  target_origin = ephy_uri_to_security_origin (form_action);
-
-  /* EphyEmbedFormAuth takes ownership of the nodes */
-  form_auth = ephy_embed_form_auth_new (web_page,
-                                        target_origin,
-                                        username_node,
-                                        password_node,
-                                        username_field_value,
-                                        password_field_value);
-  uri = ephy_embed_form_auth_get_uri (form_auth);
-  soup_uri_set_query (uri, NULL);
-
-  if (username_node)
-    g_object_get (username_node, "name", &username_field_name, NULL);
-  g_object_get (password_node, "name", &password_field_name, NULL);
-  uri_str = soup_uri_to_string (uri, FALSE);
-  origin = ephy_uri_to_security_origin (uri_str);
-
-  ephy_password_manager_query (extension->password_manager,
-                               NULL,
-                               origin,
-                               target_origin,
-                               username_field_value,
-                               username_field_name,
-                               password_field_name,
-                               should_store_cb,
-                               form_auth);
-
-  g_free (form_action);
-  g_free (target_origin);
-  g_free (username_field_name);
-  g_free (username_field_value);
-  g_free (password_field_name);
-  g_free (password_field_value);
-  g_free (uri_str);
-  g_free (origin);
+  return retval;
 }
 
 static void
@@ -493,596 +294,57 @@ web_page_will_submit_form (WebKitWebPage            *web_page,
                            GPtrArray                *text_field_values)
 {
   gboolean form_submit_handled;
+  JSCContext *js_context;
+  JSCValue *js_ephy;
+  JSCValue *js_form;
+  JSCValue *js_requester;
+  JSCValue *js_result;
 
   form_submit_handled =
     GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dom_form),
                                         "ephy-form-submit-handled"));
-
-  if (!form_submit_handled) {
-    g_object_set_data (G_OBJECT (dom_form),
-                       "ephy-form-submit-handled",
-                       GINT_TO_POINTER (TRUE));
-    handle_form_submission (web_page, dom_form);
-  }
-}
-
-static void
-fill_form_cb (GList    *records,
-              gpointer  user_data)
-{
-  EphyEmbedFormAuth *form_auth = EPHY_EMBED_FORM_AUTH (user_data);
-  WebKitDOMHTMLInputElement *username_node;
-  WebKitDOMHTMLInputElement *password_node;
-  const char *username;
-  const char *password;
-
-  if (!(records && records->data)) {
-    LOG ("No result");
-    return;
-  }
-
-  username = ephy_password_record_get_username (EPHY_PASSWORD_RECORD (records->data));
-  password = ephy_password_record_get_password (EPHY_PASSWORD_RECORD (records->data));
-  username_node = WEBKIT_DOM_HTML_INPUT_ELEMENT (ephy_embed_form_auth_get_username_node (form_auth));
-  password_node = WEBKIT_DOM_HTML_INPUT_ELEMENT (ephy_embed_form_auth_get_password_node (form_auth));
-
-  LOG ("Found: user %s pass (hidden)", username_node ? username : "(none)");
-  if (username_node) {
-    g_object_set_data (G_OBJECT (username_node), "ephy-is-auto-filling", GINT_TO_POINTER (TRUE));
-    webkit_dom_html_input_element_set_auto_filled (username_node, TRUE);
-    webkit_dom_html_input_element_set_editing_value (username_node, username);
-    g_object_set_data (G_OBJECT (username_node), "ephy-is-auto-filling", GINT_TO_POINTER (FALSE));
-  }
-  webkit_dom_html_input_element_set_auto_filled (password_node, TRUE);
-  webkit_dom_html_input_element_set_editing_value (password_node, password);
-
-  g_list_free_full (records, g_object_unref);
-}
-
-static void
-pre_fill_form (EphyEmbedFormAuth *form_auth)
-{
-  SoupURI *uri;
-  char *uri_str;
-  char *origin;
-  char *username = NULL;
-  char *username_field_name = NULL;
-  char *password_field_name = NULL;
-  const char *target_origin;
-  WebKitDOMNode *username_node;
-  WebKitDOMNode *password_node;
-  EphyWebExtension *extension;
-
-  uri = ephy_embed_form_auth_get_uri (form_auth);
-  if (!uri)
-    return;
-
-  extension = ephy_web_extension_get ();
-  if (!extension->password_manager)
+  if (form_submit_handled)
     return;
 
-  username_node = ephy_embed_form_auth_get_username_node (form_auth);
-  if (username_node) {
-    g_object_get (username_node, "name", &username_field_name, NULL);
-    g_object_get (username_node, "value", &username, NULL);
-  }
-  password_node = ephy_embed_form_auth_get_password_node (form_auth);
-  if (password_node)
-    g_object_get (password_node, "name", &password_field_name, NULL);
-
-  /* The username node is empty, so pre-fill with the default. */
-  if (!g_strcmp0 (username, ""))
-    g_clear_pointer (&username, g_free);
-
-  uri_str = soup_uri_to_string (uri, FALSE);
-  origin = ephy_uri_to_security_origin (uri_str);
-  target_origin = ephy_embed_form_auth_get_target_origin (form_auth);
-
-  ephy_password_manager_query (extension->password_manager,
-                               NULL,
-                               origin,
-                               target_origin,
-                               username,
-                               username_field_name,
-                               password_field_name,
-                               fill_form_cb,
-                               form_auth);
-
-  g_free (uri_str);
-  g_free (origin);
-  g_free (username);
-  g_free (username_field_name);
-  g_free (password_field_name);
-}
-
-static void
-remove_user_choices (WebKitDOMDocument *document)
-{
-  WebKitDOMHTMLElement *body;
-  WebKitDOMElement *user_choices;
-
-  body = webkit_dom_document_get_body (document);
-
-  user_choices = webkit_dom_document_get_element_by_id (document, "ephy-user-choices-container");
-  if (user_choices) {
-    webkit_dom_node_remove_child (WEBKIT_DOM_NODE (body),
-                                  WEBKIT_DOM_NODE (user_choices),
-                                  NULL);
-  }
-}
-
-static gboolean
-user_chosen_cb (WebKitDOMNode  *li,
-                WebKitDOMEvent *dom_event,
-                WebKitDOMNode  *username_node)
-{
-  WebKitDOMElement *anchor;
-  const char *username;
-
-  anchor = webkit_dom_element_get_first_element_child (WEBKIT_DOM_ELEMENT (li));
-
-  username = webkit_dom_node_get_text_content (WEBKIT_DOM_NODE (anchor));
-  webkit_dom_html_input_element_set_value (WEBKIT_DOM_HTML_INPUT_ELEMENT (username_node), username);
-
-  remove_user_choices (webkit_dom_node_get_owner_document (li));
-
-  return TRUE;
-}
-
-GtkStyleContext *global_entry_context = NULL;
-static GtkStyleContext *
-get_entry_style_context (void)
-{
-  GtkWidgetPath *path;
-
-  if (global_entry_context)
-    return global_entry_context;
-
-  path = gtk_widget_path_new ();
-  gtk_widget_path_append_type (path, GTK_TYPE_ENTRY);
-  gtk_widget_path_iter_set_object_name (path, 0, "entry");
-
-  global_entry_context = gtk_style_context_new ();
-  gtk_style_context_set_path (global_entry_context, path);
-  gtk_widget_path_free (path);
-
-  return global_entry_context;
-}
-
-static char *
-get_selected_bgcolor (void)
-{
-  GdkRGBA color;
-  gtk_style_context_set_state (get_entry_style_context (),
-                               GTK_STATE_FLAG_SELECTED);
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-  gtk_style_context_get_background_color (get_entry_style_context (),
-                                          GTK_STATE_FLAG_SELECTED,
-                                          &color);
-#pragma GCC diagnostic pop
-  return gdk_rgba_to_string (&color);
-}
-
-static char *
-get_selected_fgcolor (void)
-{
-  GdkRGBA color;
-  gtk_style_context_set_state (get_entry_style_context (),
-                               GTK_STATE_FLAG_SELECTED);
-  gtk_style_context_get_color (get_entry_style_context (),
-                               GTK_STATE_FLAG_SELECTED,
-                               &color);
-  return gdk_rgba_to_string (&color);
-}
-
-static char *
-get_bgcolor (void)
-{
-  GdkRGBA color;
-  gtk_style_context_set_state (get_entry_style_context (),
-                               GTK_STATE_FLAG_NORMAL);
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-  gtk_style_context_get_background_color (get_entry_style_context (),
-                                          GTK_STATE_FLAG_NORMAL,
-                                          &color);
-#pragma GCC diagnostic pop
-  return gdk_rgba_to_string (&color);
-}
-
-static char *
-get_fgcolor (void)
-{
-  GdkRGBA color;
-  gtk_style_context_set_state (get_entry_style_context (),
-                               GTK_STATE_FLAG_NORMAL);
-  gtk_style_context_get_color (get_entry_style_context (),
-                               GTK_STATE_FLAG_NORMAL,
-                               &color);
-  return gdk_rgba_to_string (&color);
-}
-
-static char *
-get_user_choice_style (gboolean selected)
-{
-  char *style_attribute;
-  char *color;
-
-
-  color = selected ? get_selected_bgcolor () : get_bgcolor ();
-
-  style_attribute = g_strdup_printf ("list-style-type: none ! important;"
-                                     "background-image: none ! important;"
-                                     "padding: 3px 6px ! important;"
-                                     "margin: 0px;"
-                                     "background-color: %s;", color);
-
-  g_free (color);
-
-  return style_attribute;
+  g_object_set_data (G_OBJECT (dom_form),
+                     "ephy-form-submit-handled",
+                     GINT_TO_POINTER (TRUE));
+
+  js_context = webkit_frame_get_js_context (source_frame);
+  js_ephy = jsc_context_get_value (js_context, "Ephy");
+  js_form = webkit_frame_get_js_value_for_dom_object (frame, WEBKIT_DOM_OBJECT (dom_form));
+  js_requester = jsc_value_new_function (js_context,
+                                         "saveAuthRequester",
+                                         G_CALLBACK (save_auth_requester), NULL, NULL,
+                                         G_TYPE_STRING, 8,
+                                         G_TYPE_UINT64, G_TYPE_STRING, G_TYPE_STRING,
+                                         G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+                                         G_TYPE_STRING, G_TYPE_BOOLEAN);
+  js_result = jsc_value_object_invoke_method (js_ephy,
+                                              "handleFormSubmission",
+                                              G_TYPE_UINT64, webkit_web_page_get_id (web_page),
+                                              JSC_TYPE_VALUE, js_form,
+                                              JSC_TYPE_VALUE, js_requester,
+                                              G_TYPE_NONE);
+  g_object_unref (js_result);
+  g_object_unref (js_requester);
+  g_object_unref (js_form);
+  g_object_unref (js_ephy);
+  g_object_unref (js_context);
 }
 
 static char *
-get_user_choice_anchor_style (gboolean selected)
-{
-  char *style_attribute;
-  char *color;
-
-  color = selected ? get_selected_fgcolor () : get_fgcolor ();
-
-  style_attribute = g_strdup_printf ("font-weight: normal ! important;"
-                                     "font-family: sans ! important;"
-                                     "text-decoration: none ! important;"
-                                     "-webkit-user-modify: read-only ! important;"
-                                     "color: %s;", color);
-
-  g_free (color);
-
-  return style_attribute;
-}
-
-static void
-show_user_choices (WebKitDOMDocument *document,
-                   WebKitDOMNode     *username_node)
-{
-  WebKitDOMNode *body;
-  WebKitDOMElement *main_div;
-  WebKitDOMElement *ul;
-  GList *cached_users;
-  gboolean username_node_ever_edited;
-  double x, y;
-  double input_width;
-  char *style_attribute;
-  char *username;
-
-  g_object_get (username_node,
-                "value", &username,
-                NULL);
-
-  input_width = webkit_dom_element_get_offset_width (WEBKIT_DOM_ELEMENT (username_node));
-
-  main_div = webkit_dom_document_create_element (document, "div", NULL);
-  webkit_dom_element_set_attribute (main_div, "id", "ephy-user-choices-container", NULL);
-
-  ephy_web_dom_utils_get_absolute_bottom_for_element (WEBKIT_DOM_ELEMENT (username_node), &x, &y);
-
-  /* 2147483647 is the maximum value browsers will take for z-index.
-   * See http://stackoverflow.com/questions/8565821/css-max-z-index-value
-   */
-  style_attribute = g_strdup_printf ("position: absolute; z-index: 2147483647;"
-                                     "cursor: default;"
-                                     "width: %lfpx;"
-                                     "background-color: white;"
-                                     "box-shadow: 5px 5px 5px black;"
-                                     "border-top: 0;"
-                                     "border-radius: 8px;"
-                                     "-webkit-user-modify: read-only ! important;"
-                                     "left: %lfpx; top: %lfpx;",
-                                     input_width, x, y);
-
-  webkit_dom_element_set_attribute (main_div, "style", style_attribute, NULL);
-  g_free (style_attribute);
-
-  ul = webkit_dom_document_create_element (document, "ul", NULL);
-  webkit_dom_element_set_attribute (ul, "tabindex", "-1", NULL);
-  webkit_dom_node_append_child (WEBKIT_DOM_NODE (main_div),
-                                WEBKIT_DOM_NODE (ul),
-                                NULL);
-
-  webkit_dom_element_set_attribute (ul, "style",
-                                    "margin: 0;"
-                                    "padding: 0;",
-                                    NULL);
-
-  cached_users = (GList *)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 (GList *l = cached_users; l && l->data; l = l->next) {
-    const char *user = l->data;
-    WebKitDOMElement *li;
-    WebKitDOMElement *anchor;
-    char *child_style;
-    gboolean is_selected;
-
-    /* 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 (user, username))
-      continue;
-
-    is_selected = !g_strcmp0 (username, user);
-
-    li = webkit_dom_document_create_element (document, "li", NULL);
-    webkit_dom_element_set_attribute (li, "tabindex", "-1", NULL);
-    webkit_dom_node_append_child (WEBKIT_DOM_NODE (ul),
-                                  WEBKIT_DOM_NODE (li),
-                                  NULL);
-
-    child_style = get_user_choice_style (is_selected);
-    webkit_dom_element_set_attribute (li, "style", child_style, NULL);
-    g_free (child_style);
-
-    /* Store the selected node, if any for ease of querying which user
-     * is currently selected.
-     */
-    if (is_selected)
-      g_object_set_data (G_OBJECT (main_div), "ephy-user-selected", li);
-
-    anchor = webkit_dom_document_create_element (document, "a", NULL);
-    webkit_dom_node_append_child (WEBKIT_DOM_NODE (li),
-                                  WEBKIT_DOM_NODE (anchor),
-                                  NULL);
-
-    child_style = get_user_choice_anchor_style (is_selected);
-    webkit_dom_element_set_attribute (anchor, "style", child_style, NULL);
-    g_free (child_style);
-
-    webkit_dom_event_target_add_event_listener (WEBKIT_DOM_EVENT_TARGET (li), "mousedown",
-                                                G_CALLBACK (user_chosen_cb), TRUE,
-                                                username_node);
-
-    webkit_dom_node_set_text_content (WEBKIT_DOM_NODE (anchor),
-                                      user,
-                                      NULL);
-  }
-
-  g_free (username);
-  body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document));
-  webkit_dom_node_append_child (WEBKIT_DOM_NODE (body),
-                                WEBKIT_DOM_NODE (main_div),
-                                NULL);
-}
-
-static gboolean
-username_node_changed_cb (WebKitDOMNode  *username_node,
-                          WebKitDOMEvent *dom_event,
-                          WebKitWebPage  *web_page)
-{
-  WebKitDOMDocument *document;
-
-  document = webkit_web_page_get_dom_document (web_page);
-  remove_user_choices (document);
-
-  return TRUE;
-}
-
-static gboolean
-username_node_clicked_cb (WebKitDOMNode  *username_node,
-                          WebKitDOMEvent *dom_event,
-                          WebKitWebPage  *web_page)
-{
-  WebKitDOMDocument *document;
-
-  document = webkit_web_page_get_dom_document (web_page);
-  if (webkit_dom_document_get_element_by_id (document, "ephy-user-choices-container"))
-    return TRUE;
-
-  show_user_choices (document, username_node);
-
-  return TRUE;
-}
-
-static void
-clear_password_field (WebKitDOMNode *username_node)
-{
-  EphyEmbedFormAuth *form_auth;
-  WebKitDOMNode *password_node;
-
-  form_auth = (EphyEmbedFormAuth *)g_object_get_data (G_OBJECT (username_node),
-                                                      "ephy-form-auth");
-
-  password_node = ephy_embed_form_auth_get_password_node (form_auth);
-  webkit_dom_html_input_element_set_value (WEBKIT_DOM_HTML_INPUT_ELEMENT (password_node), "");
-}
-
-static void
-pre_fill_password (WebKitDOMNode *username_node)
-{
-  EphyEmbedFormAuth *form_auth;
-
-  form_auth = (EphyEmbedFormAuth *)g_object_get_data (G_OBJECT (username_node),
-                                                      "ephy-form-auth");
-
-  pre_fill_form (form_auth);
-}
-
-static gboolean
-username_node_keydown_cb (WebKitDOMNode  *username_node,
-                          WebKitDOMEvent *dom_event,
-                          WebKitWebPage  *web_page)
-{
-  WebKitDOMDocument *document;
-  WebKitDOMElement *main_div;
-  WebKitDOMElement *container;
-  WebKitDOMElement *selected = NULL;
-  WebKitDOMElement *to_select = NULL;
-  WebKitDOMElement *anchor;
-  WebKitDOMKeyboardEvent *keyboard_event;
-  guint keyval = GDK_KEY_VoidSymbol;
-  char *li_style_attribute;
-  char *anchor_style_attribute;
-  const char *username;
-
-  keyboard_event = WEBKIT_DOM_KEYBOARD_EVENT (dom_event);
-  document = webkit_web_page_get_dom_document (web_page);
-
-  /* U+001B means the Esc key here; we should find a better way of testing which
-   * key has been pressed.
-   */
-  if (!g_strcmp0 (webkit_dom_keyboard_event_get_key_identifier (keyboard_event), "Up"))
-    keyval = GDK_KEY_Up;
-  else if (!g_strcmp0 (webkit_dom_keyboard_event_get_key_identifier (keyboard_event), "Down"))
-    keyval = GDK_KEY_Down;
-  else if (!g_strcmp0 (webkit_dom_keyboard_event_get_key_identifier (keyboard_event), "U+001B")) {
-    remove_user_choices (document);
-    return TRUE;
-  } else
-    return TRUE;
-
-  main_div = webkit_dom_document_get_element_by_id (document, "ephy-user-choices-container");
-
-  if (!main_div) {
-    show_user_choices (document, username_node);
-    return TRUE;
-  }
-
-  /* Grab the selected node. */
-  selected = WEBKIT_DOM_ELEMENT (g_object_get_data (G_OBJECT (main_div), "ephy-user-selected"));
-
-  /* Fetch the ul. */
-  container = webkit_dom_element_get_first_element_child (main_div);
-
-  /* We have a previous selection already, so perform any selection relative to
-   * it.
-   */
-  if (selected) {
-    if (keyval == GDK_KEY_Up)
-      to_select = webkit_dom_element_get_previous_element_sibling (selected);
-    else if (keyval == GDK_KEY_Down)
-      to_select = webkit_dom_element_get_next_element_sibling (selected);
-  }
-
-  if (!to_select) {
-    if (keyval == GDK_KEY_Up)
-      to_select = webkit_dom_element_get_last_element_child (container);
-    else if (keyval == GDK_KEY_Down)
-      to_select = webkit_dom_element_get_first_element_child (container);
-  }
-
-  /* Unselect the selected node. */
-  if (selected) {
-    li_style_attribute = get_user_choice_style (FALSE);
-    webkit_dom_element_set_attribute (selected, "style", li_style_attribute, NULL);
-    g_free (li_style_attribute);
-
-    anchor = webkit_dom_element_get_first_element_child (selected);
-
-    anchor_style_attribute = get_user_choice_anchor_style (FALSE);
-    webkit_dom_element_set_attribute (anchor, "style", anchor_style_attribute, NULL);
-    g_free (anchor_style_attribute);
-  }
-
-  /* Selected the new node. */
-  if (to_select) {
-    g_object_set_data (G_OBJECT (main_div), "ephy-user-selected", to_select);
-
-    li_style_attribute = get_user_choice_style (TRUE);
-    webkit_dom_element_set_attribute (to_select, "style", li_style_attribute, NULL);
-    g_free (li_style_attribute);
-
-    anchor = webkit_dom_element_get_first_element_child (to_select);
-
-    anchor_style_attribute = get_user_choice_anchor_style (TRUE);
-    webkit_dom_element_set_attribute (anchor, "style", anchor_style_attribute, NULL);
-    g_free (anchor_style_attribute);
-
-    username = webkit_dom_node_get_text_content (WEBKIT_DOM_NODE (anchor));
-    webkit_dom_html_input_element_set_value (WEBKIT_DOM_HTML_INPUT_ELEMENT (username_node), username);
-
-    pre_fill_password (username_node);
-  } else
-    clear_password_field (username_node);
-
-  webkit_dom_event_prevent_default (dom_event);
-
-  return TRUE;
-}
-
-static gboolean
-username_node_input_cb (WebKitDOMNode  *username_node,
-                        WebKitDOMEvent *dom_event,
-                        WebKitWebPage  *web_page)
+sensitive_form_message_serializer (guint64  page_id,
+                                   gboolean is_insecure_action)
 {
-  WebKitDOMDocument *document;
-  WebKitDOMElement *main_div;
-
-  if (g_object_get_data (G_OBJECT (username_node), "ephy-is-auto-filling"))
-    return TRUE;
-
-  g_object_set_data (G_OBJECT (username_node), "ephy-user-ever-edited", GINT_TO_POINTER (TRUE));
-  document = webkit_web_page_get_dom_document (web_page);
-  remove_user_choices (document);
-  show_user_choices (document, username_node);
-
-  /* Check if a username has been selected, otherwise clear password field. */
-  main_div = webkit_dom_document_get_element_by_id (document, "ephy-user-choices-container");
-  if (g_object_get_data (G_OBJECT (main_div), "ephy-user-selected"))
-    pre_fill_password (username_node);
-  else
-    clear_password_field (username_node);
-
-  return TRUE;
-}
-
-static gboolean
-sensitive_form_focused_cb (WebKitDOMHTMLFormElement *form,
-                           WebKitDOMEvent           *dom_event,
-                           WebKitWebPage            *web_page)
-{
-  WebKitDOMDOMWindow *dom_window;
   GVariant *variant;
   char *message;
-  char *action;
-  gboolean insecure_action;
 
-  dom_window = webkit_dom_document_get_default_view (webkit_web_page_get_dom_document (web_page));
-  if (dom_window == NULL)
-    return FALSE;
-
-  action = webkit_dom_html_form_element_get_action (form);
-  /* The goal here is to detect insecure forms on secure pages. The action need
-   * not necessarily contain any protocol, so just assume the form is secure
-   * unless it's clearly not. Insecure forms on insecure pages will be detected
-   * in the UI process. Note that basically no websites should actually be dumb
-   * enough to trip this, but no doubt they exist somewhere.... */
-  insecure_action = action != NULL && g_str_has_prefix (action, "http://";);
-
-  variant = g_variant_new ("(tb)",
-                           webkit_web_page_get_id (web_page),
-                           insecure_action);
+  variant = g_variant_new ("(tb)", page_id, is_insecure_action);
   message = g_variant_print (variant, FALSE);
-
-  if (!webkit_dom_dom_window_webkit_message_handlers_post_message (dom_window, "sensitiveFormFocused", 
message))
-    g_warning ("Error sending sensitiveFormFocused message");
-
-  g_free (action);
-  g_free (message);
-  g_object_unref (dom_window);
   g_variant_unref (variant);
 
-  /* Hoping FALSE means "propagate" because there is absolutely no documentation for this. */
-  return FALSE;
-}
-
-static void
-form_destroyed_cb (gpointer form_auth, GObject *form)
-{
-  g_object_unref (form_auth);
+  return message;
 }
 
 static void
@@ -1090,99 +352,45 @@ web_page_form_controls_associated (WebKitWebPage    *web_page,
                                    GPtrArray        *elements,
                                    EphyWebExtension *extension)
 {
-  WebKitDOMDocument *document = NULL;
+  WebKitFrame *frame;
+  GPtrArray *form_controls;
+  JSCContext *js_context;
+  JSCValue *js_ephy;
+  JSCValue *js_serializer;
+  JSCValue *js_result;
+  gboolean remember_passwords;
   guint i;
 
-  document = webkit_web_page_get_dom_document (web_page);
+  frame = webkit_web_page_get_main_frame (web_page);
+  js_context = webkit_frame_get_js_context (frame);
 
+  form_controls = g_ptr_array_new_with_free_func (g_object_unref);
   for (i = 0; i < elements->len; ++i) {
-    WebKitDOMElement *element;
-    WebKitDOMHTMLFormElement *form;
-    WebKitDOMNode *username_node = NULL;
-    WebKitDOMNode *password_node = NULL;
-
-    element = WEBKIT_DOM_ELEMENT (g_ptr_array_index (elements, i));
-    if (!WEBKIT_DOM_IS_HTML_FORM_ELEMENT (element))
-      continue;
-
-    form = WEBKIT_DOM_HTML_FORM_ELEMENT (element);
-
-    if (ephy_web_dom_utils_form_contains_sensitive_element (form)) {
-      LOG ("Sensitive form element detected, hooking sensitive form focused callback");
-      webkit_dom_event_target_add_event_listener (WEBKIT_DOM_EVENT_TARGET (form), "focus",
-                                                  G_CALLBACK (sensitive_form_focused_cb), TRUE,
-                                                  web_page);
-    }
+    WebKitDOMObject *element = WEBKIT_DOM_OBJECT (g_ptr_array_index (elements, i));
 
-    if (!extension->password_manager ||
-        !g_settings_get_boolean (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_REMEMBER_PASSWORDS))
-      continue;
-
-    /* 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,
-                                                    AUTH_CACHE_AUTOFILL)) {
-      EphyEmbedFormAuth *form_auth;
-      GList *cached_users;
-      const char *uri;
-      char *origin;
-      char *form_action;
-      char *target_origin;
-
-      uri = webkit_web_page_get_uri (web_page);
-
-      form_action = webkit_dom_html_form_element_get_action (form);
-      if (form_action == NULL)
-        form_action = g_strdup (uri);
-      target_origin = ephy_uri_to_security_origin (form_action);
-
-      LOG ("Hooking and pre-filling a form");
-
-      /* EphyEmbedFormAuth takes ownership of the nodes */
-      form_auth = ephy_embed_form_auth_new (web_page,
-                                            target_origin,
-                                            username_node,
-                                            password_node,
-                                            NULL,
-                                            NULL);
-
-      /* Plug in the user autocomplete */
-      origin = ephy_uri_to_security_origin (uri);
-      cached_users = ephy_password_manager_get_cached_users (extension->password_manager, origin);
-
-      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-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",
-                                                    G_CALLBACK (username_node_input_cb), TRUE,
-                                                    web_page);
-        webkit_dom_event_target_add_event_listener (WEBKIT_DOM_EVENT_TARGET (username_node), "keydown",
-                                                    G_CALLBACK (username_node_keydown_cb), FALSE,
-                                                    web_page);
-        webkit_dom_event_target_add_event_listener (WEBKIT_DOM_EVENT_TARGET (username_node), "mouseup",
-                                                    G_CALLBACK (username_node_clicked_cb), FALSE,
-                                                    web_page);
-        webkit_dom_event_target_add_event_listener (WEBKIT_DOM_EVENT_TARGET (username_node), "change",
-                                                    G_CALLBACK (username_node_changed_cb), FALSE,
-                                                    web_page);
-        webkit_dom_event_target_add_event_listener (WEBKIT_DOM_EVENT_TARGET (username_node), "blur",
-                                                    G_CALLBACK (username_node_changed_cb), FALSE,
-                                                    web_page);
-      } else
-        LOG ("No items or a single item in cached_users, not hooking menu for choosing.");
-
-      pre_fill_form (form_auth);
-
-      g_free (origin);
-      g_free (form_action);
-      g_free (target_origin);
-      g_object_weak_ref (G_OBJECT (form), form_destroyed_cb, form_auth);
-    } else
-      LOG ("No pre-fillable/hookable form found");
+    g_ptr_array_add (form_controls, webkit_frame_get_js_value_for_dom_object (frame, element));
   }
+
+  js_ephy = jsc_context_get_value (js_context, "Ephy");
+  js_serializer = jsc_value_new_function (js_context,
+                                          "sensitiveFormMessageSerializer",
+                                          G_CALLBACK (sensitive_form_message_serializer), NULL, NULL,
+                                          G_TYPE_STRING, 2,
+                                          G_TYPE_UINT64, G_TYPE_BOOLEAN);
+  remember_passwords = extension->password_manager &&
+                       g_settings_get_boolean (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_REMEMBER_PASSWORDS);
+  js_result = jsc_value_object_invoke_method (js_ephy,
+                                              "formControlsAssociated",
+                                              G_TYPE_UINT64, webkit_web_page_get_id (web_page),
+                                              G_TYPE_PTR_ARRAY, form_controls,
+                                              JSC_TYPE_VALUE, js_serializer,
+                                              G_TYPE_BOOLEAN, remember_passwords,
+                                              G_TYPE_NONE);
+  g_object_unref (js_result);
+  g_ptr_array_unref (form_controls);
+  g_object_unref (js_serializer);
+  g_object_unref (js_ephy);
+  g_object_unref (js_context);
 }
 
 static void
@@ -1204,19 +412,21 @@ web_page_context_menu (WebKitWebPage          *web_page,
                        WebKitWebHitTestResult *hit_test_result,
                        gpointer                user_data)
 {
-  char *string;
+  char *string = NULL;
   GVariantBuilder builder;
-  WebKitDOMDocument *document = webkit_web_page_get_dom_document (web_page);
-  WebKitDOMDOMWindow *window = webkit_dom_document_get_default_view (document);
-  WebKitDOMDOMSelection *selection = webkit_dom_dom_window_get_selection (window);
+  WebKitFrame *frame;
+  JSCContext *js_context;
+  JSCValue *js_value;
 
-  g_object_unref (window);
+  frame = webkit_web_page_get_main_frame (web_page);
+  js_context = webkit_frame_get_js_context (frame);
 
-  if (!selection)
-    return FALSE;
+  js_value = jsc_context_evaluate (js_context, "window.getSelection().toString();", -1);
+  if (!jsc_value_is_null (js_value) && !jsc_value_is_undefined (js_value))
+    string = jsc_value_to_string (js_value);
+  g_object_unref (js_value);
 
-  string = ephy_web_dom_utils_get_selection_as_string (selection);
-  g_object_unref (selection);
+  g_object_unref (js_context);
 
   if (!string || *string == '\0') {
     g_free (string);
@@ -1309,19 +519,6 @@ ephy_web_extension_page_created_cb (EphyWebExtension *extension,
                     extension);
 }
 
-static WebKitWebPage *
-get_webkit_web_page_or_return_dbus_error (GDBusMethodInvocation *invocation,
-                                          WebKitWebExtension    *web_extension,
-                                          guint64                page_id)
-{
-  WebKitWebPage *web_page = webkit_web_extension_get_page (web_extension, page_id);
-  if (!web_page) {
-    g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
-                                           "Invalid page ID: %"G_GUINT64_FORMAT, page_id);
-  }
-  return web_page;
-}
-
 static void
 handle_method_call (GDBusConnection       *connection,
                     const char            *sender,
@@ -1337,62 +534,8 @@ handle_method_call (GDBusConnection       *connection,
   if (g_strcmp0 (interface_name, EPHY_WEB_EXTENSION_INTERFACE) != 0)
     return;
 
-  if (g_strcmp0 (method_name, "HasModifiedForms") == 0) {
-    WebKitWebPage *web_page;
-    WebKitDOMDocument *document;
-    guint64 page_id;
-    gboolean has_modifed_forms;
-
-    g_variant_get (parameters, "(t)", &page_id);
-    web_page = get_webkit_web_page_or_return_dbus_error (invocation, extension->extension, page_id);
-    if (!web_page)
-      return;
-
-    document = webkit_web_page_get_dom_document (web_page);
-    has_modifed_forms = ephy_web_dom_utils_has_modified_forms (document);
-
-    g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", has_modifed_forms));
-  } else if (g_strcmp0 (method_name, "GetWebAppTitle") == 0) {
-    WebKitWebPage *web_page;
-    WebKitDOMDocument *document;
-    char *title = NULL;
-    guint64 page_id;
-
-    g_variant_get (parameters, "(t)", &page_id);
-    web_page = get_webkit_web_page_or_return_dbus_error (invocation, extension->extension, page_id);
-    if (!web_page)
-      return;
-
-    document = webkit_web_page_get_dom_document (web_page);
-    title = ephy_web_dom_utils_get_application_title (document);
-
-    g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", title ? title : ""));
-  } else if (g_strcmp0 (method_name, "GetBestWebAppIcon") == 0) {
-    WebKitWebPage *web_page;
-    WebKitDOMDocument *document;
-    const char *base_uri = NULL;
-    char *uri = NULL;
-    char *color = NULL;
-    guint64 page_id;
-
-    g_variant_get (parameters, "(t&s)", &page_id, &base_uri);
-    web_page = get_webkit_web_page_or_return_dbus_error (invocation, extension->extension, page_id);
-    if (!web_page)
-      return;
-
-    if (base_uri == NULL || *base_uri == '\0') {
-      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
-                                             "Base URI cannot be NULL or empty");
-      return;
-    }
-
-    document = webkit_web_page_get_dom_document (web_page);
-    ephy_web_dom_utils_get_best_icon (document, base_uri, &uri, &color);
-
-    g_dbus_method_invocation_return_value (invocation,
-                                           g_variant_new ("(ss)", uri ? uri : "", color ? color : ""));
-  } else if (g_strcmp0 (method_name, "FormAuthDataSaveConfirmationResponse") == 0) {
-    EphyEmbedFormAuth *form_auth;
+  if (g_strcmp0 (method_name, "FormAuthDataSaveConfirmationResponse") == 0) {
+    SaveAuthRequest *request;
     guint request_id;
     gboolean should_store;
     GHashTable *requests;
@@ -1401,12 +544,20 @@ handle_method_call (GDBusConnection       *connection,
 
     g_variant_get (parameters, "(ub)", &request_id, &should_store);
 
-    form_auth = g_hash_table_lookup (requests, GINT_TO_POINTER (request_id));
-    if (!form_auth)
+    request = g_hash_table_lookup (requests, GINT_TO_POINTER (request_id));
+    if (!request)
       return;
 
-    if (should_store)
-      store_password (form_auth);
+    if (should_store) {
+      ephy_password_manager_save (extension->password_manager,
+                                  request->origin,
+                                  request->target_origin,
+                                  request->username,
+                                  request->password,
+                                  request->username_field_name,
+                                  request->password_field_name,
+                                  request->is_new);
+    }
     g_hash_table_remove (requests, GINT_TO_POINTER (request_id));
   } else if (g_strcmp0 (method_name, "HistorySetURLs") == 0) {
     if (extension->overview_model) {
@@ -1651,6 +802,125 @@ authorize_authenticated_peer_cb (GDBusAuthObserver *observer,
   return ephy_dbus_peer_is_authorized (credentials);
 }
 
+static void
+js_log (const char *message)
+{
+  LOG ("%s", message);
+}
+
+static void
+js_auto_fill (JSCValue   *js_element,
+              const char *value)
+{
+  WebKitDOMNode *node;
+  WebKitDOMElement *element;
+
+  node = webkit_dom_node_for_js_value (js_element);
+  element = WEBKIT_DOM_ELEMENT (node);
+
+  webkit_dom_element_html_input_element_set_auto_filled (element, TRUE);
+  webkit_dom_element_html_input_element_set_editing_value (element, value);
+}
+
+static gboolean
+js_is_edited (JSCValue *js_element)
+{
+  WebKitDOMNode *node = webkit_dom_node_for_js_value (js_element);
+
+  return webkit_dom_element_html_input_element_is_user_edited (WEBKIT_DOM_ELEMENT (node));
+}
+
+static void
+js_exception_handler (JSCContext   *context,
+                      JSCException *exception)
+{
+  JSCValue *js_console;
+  JSCValue *js_result;
+
+  js_console = jsc_context_get_value (context, "console");
+  js_result = jsc_value_object_invoke_method (js_console, "error", JSC_TYPE_EXCEPTION, exception, 
G_TYPE_NONE);
+  g_object_unref (js_result);
+  g_object_unref (js_console);
+
+  g_warning ("JavaScriptException: %s", jsc_exception_get_message (exception));
+
+  jsc_context_throw_exception (context, exception);
+}
+
+static void
+window_object_cleared_cb (WebKitScriptWorld *world,
+                          WebKitWebPage     *page,
+                          WebKitFrame       *frame,
+                          EphyWebExtension  *extension)
+{
+  JSCContext *js_context;
+  GBytes *bytes;
+  const char* data;
+  gsize data_size;
+  JSCValue *js_ephy;
+  JSCValue *js_function;
+  JSCValue *result;
+
+  if (!webkit_frame_is_main_frame (frame))
+    return;
+
+  js_context = webkit_frame_get_js_context_for_script_world (frame, world);
+  jsc_context_push_exception_handler (js_context, (JSCExceptionHandler)js_exception_handler, NULL, NULL);
+
+  bytes = g_resources_lookup_data ("/org/gnome/epiphany-web-extension/js/ephy.js", 
G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
+  data = g_bytes_get_data (bytes, &data_size);
+  result = jsc_context_evaluate_with_source_uri (js_context, data, data_size, 
"resource:///org/gnome/epiphany-web-extension/js/ephy.js");
+  g_bytes_unref (bytes);
+  g_object_unref (result);
+
+  js_ephy = jsc_context_get_value (js_context, "Ephy");
+
+  js_function = jsc_value_new_function (js_context,
+                                        "log",
+                                        G_CALLBACK (js_log), NULL, NULL,
+                                        G_TYPE_NONE, 1,
+                                        G_TYPE_STRING);
+  jsc_value_object_set_property (js_ephy, "log", js_function);
+  g_object_unref (js_function);
+
+  ephy_permissions_manager_export_to_js_context (extension->permissions_manager,
+                                                 js_context,
+                                                 js_ephy);
+
+  if (extension->password_manager) {
+    ephy_password_manager_export_to_js_context (extension->password_manager,
+                                                js_context,
+                                                js_ephy);
+
+    js_function = jsc_value_new_function (js_context,
+                                          "autoFill",
+                                          G_CALLBACK (js_auto_fill), NULL, NULL,
+                                          G_TYPE_NONE, 2,
+                                          JSC_TYPE_VALUE, G_TYPE_STRING);
+    jsc_value_object_set_property (js_ephy, "autoFill", js_function);
+    g_object_unref (js_function);
+  }
+
+  js_function = jsc_value_new_function (js_context,
+                                        "isWebApplication",
+                                        G_CALLBACK (ephy_dot_dir_is_web_application), NULL, NULL,
+                                        G_TYPE_BOOLEAN, 0,
+                                        G_TYPE_NONE);
+  jsc_value_object_set_property (js_ephy, "isWebApplication", js_function);
+  g_object_unref (js_function);
+
+  js_function = jsc_value_new_function (js_context,
+                                        "isEdited",
+                                        G_CALLBACK (js_is_edited), NULL, NULL,
+                                        G_TYPE_BOOLEAN, 1,
+                                        JSC_TYPE_VALUE);
+  jsc_value_object_set_property (js_ephy, "isEdited", js_function);
+  g_object_unref (js_function);
+
+  g_object_unref (js_ephy);
+  g_object_unref (js_context);
+}
+
 void
 ephy_web_extension_initialize (EphyWebExtension   *extension,
                                WebKitWebExtension *wk_extension,
@@ -1668,6 +938,11 @@ ephy_web_extension_initialize (EphyWebExtension   *extension,
 
   extension->initialized = TRUE;
 
+  g_signal_connect (webkit_script_world_get_default (),
+                    "window-object-cleared",
+                    G_CALLBACK (window_object_cleared_cb),
+                    extension);
+
   extension->extension = g_object_ref (wk_extension);
   if (!is_private_profile) {
     extension->password_manager = ephy_password_manager_new ();
diff --git a/embed/web-extension/meson.build b/embed/web-extension/meson.build
index 242c3bb..06368d9 100644
--- a/embed/web-extension/meson.build
+++ b/embed/web-extension/meson.build
@@ -1,11 +1,17 @@
+resource_files = files('resources/epiphany-web-extension.gresource.xml')
+resources = gnome.compile_resources('epiphany-web-extension-resources',
+    resource_files,
+    c_name: 'epiphany_web_extension',
+    source_dir: 'resources'
+)
+
 web_extension_sources = [
-  'ephy-embed-form-auth.c',
   'ephy-uri-tester.c',
-  'ephy-web-dom-utils.c',
   'ephy-web-extension.c',
   'ephy-web-extension-main.c',
   'ephy-web-overview.c',
-  'ephy-web-overview-model.c'
+  'ephy-web-overview-model.c',
+  resources
 ]
 
 web_extension_deps = [
diff --git a/embed/web-extension/resources/epiphany-web-extension.gresource.xml 
b/embed/web-extension/resources/epiphany-web-extension.gresource.xml
new file mode 100644
index 0000000..657d576
--- /dev/null
+++ b/embed/web-extension/resources/epiphany-web-extension.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/epiphany-web-extension">
+    <file compressed="true">js/ephy.js</file>
+  </gresource>
+</gresources>
diff --git a/embed/web-extension/resources/js/ephy.js b/embed/web-extension/resources/js/ephy.js
new file mode 100644
index 0000000..b4c3f68
--- /dev/null
+++ b/embed/web-extension/resources/js/ephy.js
@@ -0,0 +1,602 @@
+var Ephy = {};
+
+Ephy.formControlsAssociated = function(pageID, forms, serializer, rememberPasswords)
+{
+    Ephy.formManagers = [];
+
+    for (let i = 0; i < forms.length; i++) {
+        if (!(forms[i] instanceof HTMLFormElement))
+            continue;
+        let formManager = new Ephy.FormManager(pageID, forms[i]);
+        formManager.handleSensitiveElement(serializer);
+        if (rememberPasswords)
+            formManager.preFillForms();
+        Ephy.formManagers.push(formManager);
+    }
+}
+
+Ephy.handleFormSubmission = function(pageID, form, formAuthRequester)
+{
+    let formManager = null;
+    for (let i = 0; i < Ephy.formManagers.length; i++) {
+        let manager = Ephy.formManagers[i];
+        if (manager.pageID() == pageID && manager.form() == form) {
+            formManager = manager;
+            break;
+        }
+    }
+
+    if (!formManager) {
+        formManager = new Ephy.FormManager(pageID, form);
+        Ephy.formManagers.push(formManager);
+    }
+
+    formManager.handleFormSubmission(formAuthRequester);
+}
+
+Ephy.hasModifiedForms = function()
+{
+    for (let i = 0; i < document.forms.length; i++) {
+        let form = document.forms[i];
+        let modifiedInputElement = false;
+        for (let j = 0; j < form.elements.length; j++) {
+            let element = form.elements[j];
+            if (!Ephy.isEdited(element))
+                continue;
+
+            if (element instanceof HTMLTextAreaElement)
+                return !!element.value;
+
+            if (element instanceof HTMLInputElement) {
+                // A small heuristic here. If there's only one input element
+                // modified and it does not have a lot of text the user is
+                // likely not very interested in saving this work, so do
+                // nothing (eg, google search input).
+                if (element.value.length > 50)
+                    return true;
+                if (modifiedInputElement)
+                    return true;
+                modifiedInputElement = true;
+            }
+        }
+    }
+}
+
+Ephy.getWebAppTitle = function()
+{
+    let metas = document.getElementsByTagName('meta');
+    for (let i = 0; i < metas.length; i++) {
+        let meta = metas[i];
+        if (meta.name == 'application-name')
+            return meta.content;
+
+        // og:site_name is read from the property attribute (standard), but is
+        // commonly seen on the web in the name attribute. Both are supported.
+        if (meta.getAttribute('property') == 'og:site_name' || meta.name == 'og:site_name')
+            return meta.content;
+    }
+    return null;
+}
+
+Ephy.getWebAppIcon = function(baseURL)
+{
+    // FIXME: This function could be improved considerably. See the first two answers at:
+    // 
http://stackoverflow.com/questions/21991044/how-to-get-high-resolution-website-logo-favicon-for-a-given-url
+    //
+    // Also check out: https://www.slightfuture.com/webdev/gnome-web-app-icons
+    let iconURL = null;
+    let appleTouchIconURL = null;
+    let largestIconSize = 0;
+    let links = document.getElementsByTagName('link');
+    for (let i = 0; i < links.length; i++) {
+        let link = links[i];
+        if (link.rel == 'icon' || link.rel == 'shortcut icon' || link.rel == 'icon shortcut' || link.rel == 
'shortcut-icon') {
+            let sizes = link.getAttribute('sizes');
+            if (!sizes)
+                continue;
+
+            if (sizes == 'any') {
+                // "any" means a vector, and thus it will always be the largest icon.
+                iconURL = link.href;
+                break;
+            }
+
+            let sizesList = size.split(' ');
+            for (let j = 0; j < sizesList.length; j++) {
+                let size = sizesList[j].toLowerCase().split('x');
+
+                // Only accept square icons.
+                if (size.length != 2 || size[0] != size[1])
+                    continue;
+
+                // Only accept icons of 96 px (smallest GNOME HIG app icon) or larger.
+                // It's better to defer to other icon discovery methods if smaller
+                // icons are returned here.
+                if (size[0] >= 92 && size[0] > largestIconSize) {
+                    iconURL = link.href;
+                    largestIconSize = size[0];
+                }
+            }
+        } else if (link.rel == 'apple-touch-icon' || link.rel == 'apple-touch-icon-precomposed') {
+            // TODO: support more than one possible icon.
+            // apple-touch-icon is best touch-icon candidate.
+            if (link.rel == 'apple-touch-icon' || !appleTouchIconURL)
+                appleTouchIconURL = link.href;
+            // TODO: Try to retrieve /apple-touch-icon.png, and return it if it exist.
+        }
+    }
+
+    // HTML icon.
+    if (iconURL)
+        return { 'url' : new URL(iconURL, baseURL).href, 'color' : null };
+
+    let iconColor = null;
+    let ogpIcon = null;
+    let metas = document.getElementsByTagName('meta');
+    for (let i = 0; i < metas.length; i++) {
+        let meta = metas[i];
+        // FIXME: Ought to also search browserconfig.xml
+        // See: http://stackoverflow.com/questions/24625305/msapplication-tileimage-favicon-backup
+        if (meta.name == 'msapplication-TileImage')
+            iconURL = meta.content;
+        else if (meta.name == 'msapplication-TileColor')
+            iconColor = meta.content;
+        else if (meta.getAttribute('property') == 'og:image' || meta.getAttribute('itemprop') == 'image')
+            ogpIcon = meta.content;
+    }
+
+    // msapplication icon.
+    if (iconURL)
+        return { 'url' : new URL(iconURL, baseURL).href, 'color' : iconColor };
+
+    // Apple touch icon.
+    if (appleTouchIconURL)
+        return { 'url' : new URL(appleTouchIconURL, baseURL).href, 'color' : null };
+
+    // ogp icon.
+    if (ogpIcon)
+        return { 'url' : new URL(ogpIcon, baseURL).href, 'color' : null };
+
+    // Last ditch effort: just fallback to the default favicon location.
+    return { 'url' : new URL('./favicon.ico', baseURL).href, 'color' : null };
+}
+
+Ephy.PreFillUserMenu = class PreFillUserMenu
+{
+    constructor(manager, userElement, users, passwordElement)
+    {
+        this._manager = manager;
+        this._userElement = userElement;
+        this._users = users;
+        this._passwordElement = passwordElement;
+        this._selected = null;
+        this._wasEdited = false;
+
+        this._userElement.addEventListener('input', this._onInput.bind(this), true);
+        this._userElement.addEventListener('mouseup', this._onMouseUp.bind(this), false);
+        this._userElement.addEventListener('keydown', this._onKeyDown.bind(this), false);
+        this._userElement.addEventListener('change', this._removeMenu, false);
+        this._userElement.addEventListener('blur', this._removeMenu, false);
+    }
+
+    // Private
+
+    _onInput(event)
+    {
+        if (this._manager.isAutoFilling(this._userElement))
+            return;
+
+        this._wasEdited = true;
+        this._removeMenu();
+        this._showMenu(false);
+    }
+
+    _onMouseUp(event)
+    {
+        if (document.getElementById('ephy-user-choices-container'))
+            return;
+
+        this._showMenu(!this._wasEdited);
+    }
+
+    _onKeyDown(event)
+    {
+        if (event.key == 'Escape') {
+            this._removeMenu();
+            return;
+        }
+
+        if (event.key != 'ArrowDown' && event.key != 'ArrowUp')
+            return;
+
+        let container = document.getElementById('ephy-user-choices-container');
+        if (!container) {
+            this._showMenu(!this._wasEdited);
+            return;
+        }
+
+        let newSelect = null;
+        if (this._selected)
+            newSelect = event.key != 'ArrowUp' ? this._selected.previousSibling : this._selected.nextSibling;
+
+        if (!newSelect)
+            newSelect = event.key != 'ArrowUp' ? container.firstElementChild.lastElementChild : 
container.firstElementChild.firstElementChild;
+
+        if (newSelect) {
+            this._selected = newSelect;
+            this._userElement.value = this._selected.firstElementChild.textContent;
+            this._manager.preFill();
+        } else {
+            this._passwordElement.value = '';
+        }
+
+        event.preventDefault();
+    }
+
+    _showMenu(showAll)
+    {
+        let mainDiv = document.createElement('div');
+        mainDiv.id = 'ephy-user-choices-container';
+
+        let elementRect = this._userElement.getBoundingClientRect();
+
+        // 2147483647 is the maximum value browsers will take for z-index.
+        // See http://stackoverflow.com/questions/8565821/css-max-z-index-value
+        mainDiv.style.cssText = 'position: absolute;' +
+            'z-index: 2147483647;' +
+            'cursor: default;' +
+            'background-color: white;' +
+            'box-shadow: 5px 5px 5px black;' +
+            'border-top: 0px;' +
+            'border-radius: 8px;' +
+            '-webkit-user-modify: read-only ! important;';
+        mainDiv.style.width = this._userElement.offsetWidth;
+        mainDiv.style.left = elementRect.left + document.body.scrollLeft;
+        mainDiv.style.top = elementRect.top + document.body.scrollTop;
+
+        let ul = document.createElement('ul');
+        ul.style.cssText = 'margin: 0; padding: 0;';
+        ul.tabindex = -1;
+        mainDiv.appendChild(ul);
+
+        this._selected = null;
+        for (let i = 0; i < this._users.length; i++) {
+            let user = this._users[i];
+            if (!showAll && !user.startsWith(this._userElement.value))
+                continue;
+
+            let li = document.createElement('li');
+            li.style.cssText = 'list-style-type: none ! important;' +
+                'background-image: none ! important;' +
+                'padding: 3px 6px ! important;' +
+                'margin: 0px;';
+            // FIXME: selection colors.
+            li.tabindex = -1;
+            ul.appendChild(li);
+
+            if (user == this._userElement.value)
+                this._selected = li;
+
+            let anchor = document.createElement('a');
+            anchor.style.cssText = 'font-weight: normal ! important;' +
+                'font-family: sans ! important;' +
+                'text-decoration: none ! important;' +
+                '-webkit-user-modify: read-only ! important;';
+            // FIXME: selection colors.
+            anchor.textContent = user;
+            li.appendChild(anchor);
+
+            const self = this;
+            li.addEventListener('mousedown', function (event) {
+                self._userElement.value = user;
+                self._selected = li;
+                self._removeMenu();
+                self._manager.preFill();
+            }, true);
+        }
+
+        document.body.appendChild(mainDiv);
+
+        if (!this._selected)
+            this._passwordElement.value = '';
+    }
+
+    _removeMenu()
+    {
+        let menu = document.getElementById('ephy-user-choices-container');
+        if (menu)
+            menu.parentNode.removeChild(menu);
+    }
+};
+
+Ephy.FormManager = class FormManager
+{
+    constructor(pageID, form)
+    {
+        this._pageID = pageID;
+        this._form = form;
+        this._sensitiveElementMessageSerializer = null;
+        this._formAuth = null;
+        this._preFillUserMenu = null;
+        this._elementBeingAutoFilled = null;
+    }
+
+    // Public
+
+    pageID()
+    {
+        return this._pageID;
+    }
+
+    form()
+    {
+        return this._form;
+    }
+
+    handleSensitiveElement(serializer)
+    {
+        if (!this._containsSensitiveElement())
+            return;
+
+        Ephy.log('Sensitive form element detected, hooking sensitive form focused callback');
+        this._sensitiveElementMessageSerializer = serializer;
+        this._form.addEventListener('focus', this._sensitiveElementFocused.bind(this), true);
+    }
+
+    isAutoFilling(element)
+    {
+        return this._elementBeingAutoFilled === element;
+    }
+
+    preFillForms()
+    {
+        this._formAuth = this._findFormAuthElements(true);
+        if (!this._formAuth || !this._formAuth.passwordNode) {
+            Ephy.log('No pre-fillable/hookable form found');
+            this._formAuth = null;
+            return;
+        }
+
+        this._formAuth.url = new URL(String(window.location));
+        try {
+            this._formAuth.targetURL = new URL(this._form.action);
+        } catch(err) {
+            this._formAuth.targetURL = this._formAuth.url;
+        }
+
+        Ephy.log('Hooking and pre-filling a form');
+
+        if (this._formAuth.usernameNode) {
+            let users = Ephy.passwordManager.cachedUsers(this._formAuth.url.origin);
+            if (users.length > 1) {
+                Ephy.log('More than one password saved, hooking menu for choosing which on focus');
+                this._preFillUserMenu = new Ephy.PreFillUserMenu(this, this._formAuth.usernameNode, users, 
this._formAuth.passwordNode);
+            } else {
+                Ephy.log('Single item in cached_users, not hooking menu for choosing.');
+            }
+        } else {
+            Ephy.log('No items in cached_users, not hooking menu for choosing.');
+        }
+
+        this.preFill();
+    }
+
+    preFill()
+    {
+        const self = this;
+        Ephy.passwordManager.query(
+            this._formAuth.url.origin,
+            this._formAuth.targetURL.origin,
+            this._formAuth.usernameNode && this._formAuth.usernameNode.value ? 
this._formAuth.usernameNode.value : null,
+            this._formAuth.usernameNode ? this._formAuth.usernameNode.name : null,
+            this._formAuth.passwordNode.name ? this._formAuth.passwordNode.name : null).then(function 
(authInfo) {
+                if (!authInfo) {
+                    Ephy.log('No result');
+                    return;
+                }
+
+                Ephy.log('Found: user ' + authInfo.username + ' pass (hidden)');
+
+                if (self._formAuth.usernameNode && authInfo.username) {
+                    self._elementBeingAutoFilled = self._formAuth.usernameNode;
+                    Ephy.autoFill(self._formAuth.usernameNode, authInfo.username);
+                    self._elementBeingAutoFilled = null;
+                }
+
+                if (authInfo.password) {
+                    self._elementBeingAutoFilled = self._formAuth.passwordNode;
+                    Ephy.autoFill(self._formAuth.passwordNode, authInfo.password);
+                    self._elementBeingAutoFilled = null;
+                }
+            }
+        );
+    }
+
+    handleFormSubmission(saveAuthRequester)
+    {
+        if (!this._formAuth)
+            return;
+
+        this._formAuth = this._findFormAuthElements(false);
+        if (!this._formAuth || !this._formAuth.passwordNode) {
+            this._formAuth = null;
+            return;
+        }
+
+        if (!this._formAuth.passwordNode.value)
+            return;
+
+        this._formAuth.url = new URL(String(window.location));
+        try {
+            this._formAuth.targetURL = new URL(this._form.action);
+        } catch {
+            this._formAuth.targetURL = this._formAuth.url;
+        }
+
+        let permission = Ephy.permissionsManager.permission(Ephy.PermissionType.SAVE_PASSWORD, 
this._formAuth.url.origin);
+        if (permission == Ephy.Permission.DENY) {
+            Ephy.log('User/password storage permission previously denied. Not asking about storing.');
+            return;
+        }
+
+        if (permission == Ephy.Permission.UNDECIDED && Ephy.isWebApplication())
+            permission = Ephy.Permission.PERMIT;
+
+        const self = this;
+        Ephy.passwordManager.query(
+            this._formAuth.url.origin,
+            this._formAuth.targetURL.origin,
+            this._formAuth.usernameNode && this._formAuth.usernameNode.value ? 
this._formAuth.usernameNode.value : null,
+            this._formAuth.usernameNode ? this._formAuth.usernameNode.name : null,
+            this._formAuth.passwordNode.name ? this._formAuth.passwordNode.name : null).then(function 
(authInfo) {
+                if (authInfo) {
+                    if (authInfo.username == self._formAuth.usernameNode.value &&
+                        authInfo.password == self._formAuth.passwordNode.value) {
+                        Ephy.log('User/password already stored. Not asking about storing.');
+                        return;
+                    }
+
+                    if (permission == Ephy.Permission.PERMIT) {
+                        Ephy.log('User/password not yet stored. Storing.');
+                        Ephy.passwordManager.save(self._formAuth.url.origin, self._formAuth.targetURL.origin,
+                                                  self._formAuth.usernameNode && 
self._formAuth.usernameNode.value ? self._formAuth.usernameNode.value : null,
+                                                  self._formAuth.passwordNode.value ? 
self._formAuth.passwordNode.value : null,
+                                                  self._formAuth.usernameNode ? 
self._formAuth.usernameNode.name : null,
+                                                  self._formAuth.passwordNode.name ? 
self._formAuth.passwordNode.name : null,
+                                                  false);
+                        return;
+                    }
+
+                    Ephy.log('User/password not yet stored. Asking about storing.');
+                } else {
+                    Ephy.log('No result on query; asking whether we should store.');
+                }
+
+                window.webkit.messageHandlers.formAuthData.postMessage(saveAuthRequester(self._pageID,
+                    self._formAuth.url.origin, self._formAuth.targetURL.origin,
+                    self._formAuth.usernameNode && self._formAuth.usernameNode.value ? 
self._formAuth.usernameNode.value : null,
+                    self._formAuth.passwordNode.value ? self._formAuth.passwordNode.value : null,
+                    self._formAuth.usernameNode ? self._formAuth.usernameNode.name : null,
+                    self._formAuth.passwordNode.name ? self._formAuth.passwordNode.name : null,
+                    authInfo == null));
+            }
+        );
+    }
+
+    // Private
+
+    _containsSensitiveElement()
+    {
+        for (let i = 0; i < this._form.elements.length; i++) {
+            let element = this._form.elements[i];
+            if (element instanceof HTMLInputElement) {
+                if (element.type === 'password' || element.type === 'adminpw')
+                    return true;
+            }
+        }
+        return false;
+    }
+
+    _sensitiveElementFocused(event)
+    {
+        let isInsecureAction = this._form.action.startsWith('http://');
+        
window.webkit.messageHandlers.sensitiveFormFocused.postMessage(this._sensitiveElementMessageSerializer(this._pageID,
 isInsecureAction));
+    }
+
+    _findPasswordFields(forAutofill)
+    {
+        let passwordFields = [];
+        for (let i = 0; i < this._form.elements.length; i++) {
+            let element = this._form.elements[i];
+            if (element instanceof HTMLInputElement && element.type === 'password') {
+                // We only want to process forms with 1-3 fields. A common
+                // case is to have a "change password" form with 3 fields:
+                // Old password, New password, Confirm new password.
+                // Forms with more than 3 password fields are unlikely,
+                // and we don't know how to process them, so reject them
+                if (passwordFields.length == 3)
+                    return null;
+                passwordFields.push({ 'element' : element, 'index' : i });
+            }
+        }
+        return passwordFields;
+    }
+
+    _findFormAuthElements(forAutofill)
+    {
+        let passwordNodes = this._findPasswordFields(forAutofill);
+        if (!passwordNodes || !passwordNodes.length)
+            return null;
+
+        // Start at the first found password field and search backwards.
+        // Assume the first eligible field to contain username.
+        let usernameNode = null;
+        let firstPasswordNodeData = passwordNodes[0];
+        for (let i = firstPasswordNodeData.index; i >= 0; i--) {
+            let element = this._form.elements[i];
+            if (element instanceof HTMLInputElement) {
+                if (element.type === 'text' || element.type === 'email' ||
+                    element.type === 'tel' || element.type === 'url' ||
+                    element.type === 'number') {
+                    usernameNode = element;
+                    break;
+                }
+            }
+        }
+
+        // Choose password field that contains the password that we want to store
+        // To do that, we compare the field values. We can only do this when user
+        // submits login data, because otherwise all the fields are empty. In that
+        // case just pick the first field.
+        let passwordNodeIndex = 0;
+        if (!forAutofill && passwordNodes.length != 1) {
+            // Get values of all password fields.
+            let passwords = [];
+            for (let i = passwordNodes.length - 1; i >= 0; i--)
+                passwords[i] = passwordNodes[i].element.value;
+
+            if (passwordNodes.length == 2) {
+                // If there are two password fields, assume that the form has either
+                // Password and Confirm password fields, or Old password and New password.
+                // That can be guessed by comparing values in the fields. If they are
+                // different, we assume that the second password is "new" and use it.
+                // If they match, then just take the first field.
+                if (passwords[0] == passwords[1]) {
+                    // Password / Confirm password.
+                    passwordNodeIndex = 0;
+                } else {
+                    // Old password / New password.
+                    passwordNodeIndex = 1;
+                }
+            } else if (passwordNodes.length == 3) {
+                // This is probably a complete Old password, New password, Confirm
+                // new password case. Here we assume that if two fields have the same
+                // value, then it's the new password and we should take it. A special
+                // case is when all 3 passwords are different. We don't know what to
+                // do in this case, so just reject the form.
+                if (passwords[0] == passwords[1] && passwords[1] == passwords[2]) {
+                    // All values are same.
+                    passwordNodeIndex = 0;
+                } else if (passwords[0] == passwords[1]) {
+                    // New password / Confirm new password / Old password.
+                    passwordNodeIndex = 0;
+                } else if (passwords[0] == passwords[2]) {
+                    // New password / Old password / Confirm new password.
+                    passwordNodeIndex = 0;
+                } else if (passwords[1] == passwords[2]) {
+                    // Old password / New password / Confirm new password.
+                    passwordNodeIndex = 1;
+                } else {
+                    // All values are different. Reject the form.
+                    passwordNodeIndex = -1;
+                }
+            }
+        }
+
+        let passwordNode = null;
+        if (passwordNodeIndex >= 0)
+            passwordNode = passwordNodes[passwordNodeIndex].element;
+
+        return { 'usernameNode' : usernameNode, 'passwordNode' : passwordNode };
+    }
+};
diff --git a/lib/ephy-permissions-manager.c b/lib/ephy-permissions-manager.c
index 101156a..3751626 100644
--- a/lib/ephy-permissions-manager.c
+++ b/lib/ephy-permissions-manager.c
@@ -417,3 +417,64 @@ ephy_permissions_manager_get_denied_origins (EphyPermissionsManager *manager,
 {
   return ephy_permissions_manager_get_matching_origins (manager, type, FALSE);
 }
+
+static EphyPermission
+js_permissions_manager_get_permission (EphyPermissionsManager *manager,
+                                       EphyPermissionType      type,
+                                       const char             *origin)
+{
+  return ephy_permissions_manager_get_permission (manager, type, origin);
+}
+
+void
+ephy_permissions_manager_export_to_js_context (EphyPermissionsManager *manager,
+                                               JSCContext             *js_context,
+                                               JSCValue               *js_namespace)
+{
+  JSCClass *js_class;
+  JSCValue *js_enum, *js_enum_value;
+  JSCValue *js_permissions_manager;
+
+  js_class = jsc_context_register_class (js_context, "PermissionsManager", NULL, NULL, NULL);
+  jsc_class_add_method (js_class,
+                        "permission",
+                        G_CALLBACK (js_permissions_manager_get_permission), NULL, NULL,
+                        G_TYPE_INT, 2,
+                        G_TYPE_INT, G_TYPE_STRING);
+
+  js_permissions_manager = jsc_value_new_object (js_context, manager, js_class);
+  jsc_value_object_set_property (js_namespace, "permissionsManager", js_permissions_manager);
+  g_object_unref (js_permissions_manager);
+
+  js_enum = jsc_value_new_object (js_context, NULL, NULL);
+  js_enum_value = jsc_value_new_number (js_context, EPHY_PERMISSION_UNDECIDED);
+  jsc_value_object_set_property (js_enum, "UNDECIDED", js_enum_value);
+  g_object_unref (js_enum_value);
+  js_enum_value = jsc_value_new_number (js_context, EPHY_PERMISSION_DENY);
+  jsc_value_object_set_property (js_enum, "DENY", js_enum_value);
+  g_object_unref (js_enum_value);
+  js_enum_value = jsc_value_new_number (js_context, EPHY_PERMISSION_PERMIT);
+  jsc_value_object_set_property (js_enum, "PERMIT", js_enum_value);
+  g_object_unref (js_enum_value);
+  jsc_value_object_set_property (js_namespace, "Permission", js_enum);
+  g_object_unref (js_enum);
+
+  js_enum = jsc_value_new_object (js_context, NULL, NULL);
+  js_enum_value = jsc_value_new_number (js_context, EPHY_PERMISSION_TYPE_SHOW_NOTIFICATIONS);
+  jsc_value_object_set_property (js_enum, "SHOW_NOTIFICATIONS", js_enum_value);
+  g_object_unref (js_enum_value);
+  js_enum_value = jsc_value_new_number (js_context, EPHY_PERMISSION_TYPE_SAVE_PASSWORD);
+  jsc_value_object_set_property (js_enum, "SAVE_PASSWORD", js_enum_value);
+  g_object_unref (js_enum_value);
+  js_enum_value = jsc_value_new_number (js_context, EPHY_PERMISSION_TYPE_ACCESS_LOCATION);
+  jsc_value_object_set_property (js_enum, "ACCESS_LOCATION", js_enum_value);
+  g_object_unref (js_enum_value);
+  js_enum_value = jsc_value_new_number (js_context, EPHY_PERMISSION_TYPE_ACCESS_MICROPHONE);
+  jsc_value_object_set_property (js_enum, "ACCESS_MICROPHONE", js_enum_value);
+  g_object_unref (js_enum_value);
+  js_enum_value = jsc_value_new_number (js_context, EPHY_PERMISSION_TYPE_ACCESS_WEBCAM);
+  jsc_value_object_set_property (js_enum, "ACCESS_WEBCAM", js_enum_value);
+  g_object_unref (js_enum_value);
+  jsc_value_object_set_property (js_namespace, "PermissionType", js_enum);
+  g_object_unref (js_enum);
+}
diff --git a/lib/ephy-permissions-manager.h b/lib/ephy-permissions-manager.h
index eeeb8b1..a9f1a60 100644
--- a/lib/ephy-permissions-manager.h
+++ b/lib/ephy-permissions-manager.h
@@ -22,6 +22,7 @@
 #pragma once
 
 #include <glib-object.h>
+#include <jsc/jsc.h>
 
 G_BEGIN_DECLS
 
@@ -58,4 +59,8 @@ GList                  *ephy_permissions_manager_get_permitted_origins (EphyPerm
 GList                  *ephy_permissions_manager_get_denied_origins    (EphyPermissionsManager *manager,
                                                                         EphyPermissionType      type);
 
+void                    ephy_permissions_manager_export_to_js_context  (EphyPermissionsManager *manager,
+                                                                        JSCContext             *js_context,
+                                                                        JSCValue               
*js_namespace);
+
 G_END_DECLS
diff --git a/lib/sync/ephy-password-manager.c b/lib/sync/ephy-password-manager.c
index 250a416..8efd1f4 100644
--- a/lib/sync/ephy-password-manager.c
+++ b/lib/sync/ephy-password-manager.c
@@ -1106,3 +1106,152 @@ ephy_synchronizable_manager_iface_init (EphySynchronizableManagerInterface *ifac
   iface->save = synchronizable_manager_save;
   iface->merge = synchronizable_manager_merge;
 }
+
+typedef struct {
+  EphyPasswordManager *password_manager;
+  const char *origin;
+  const char *target_origin;
+  const char *username;
+  const char *username_field;
+  const char *password_field;
+} PasswordManagerPromiseData;
+
+static void
+js_password_manager_query_finished_cb (GList    *records,
+                                       JSCValue *resolve)
+{
+  JSCContext *js_context;
+  JSCValue *result, *value;
+  EphyPasswordRecord *record;
+
+  record = records && records->data ? EPHY_PASSWORD_RECORD (records->data) : NULL;
+  js_context = jsc_value_get_context (resolve);
+
+  if (record) {
+    JSCValue *property;
+
+    result = jsc_value_new_object (js_context, NULL, NULL);
+
+    property = jsc_value_new_string (js_context, ephy_password_record_get_username (record));
+    jsc_value_object_set_property (result, "username", property);
+    g_object_unref (property);
+
+    property = jsc_value_new_string (js_context, ephy_password_record_get_password (record));
+    jsc_value_object_set_property (result, "password", property);
+    g_object_unref (property);
+  } else {
+    result = jsc_value_new_null (js_context);
+  }
+
+  value = jsc_value_function_call (resolve, JSC_TYPE_VALUE, result, G_TYPE_NONE);
+  g_object_unref (value);
+  g_object_unref (result);
+
+  g_list_free_full (records, g_object_unref);
+  g_object_unref (resolve);
+}
+
+static void
+js_password_manager_query_resolve (JSCValue                   *resolve,
+                                   JSCValue                   *reject,
+                                   PasswordManagerPromiseData *data)
+{
+  ephy_password_manager_query (data->password_manager,
+                               NULL,
+                               data->origin,
+                               data->target_origin,
+                               data->username,
+                               data->username_field,
+                               data->password_field,
+                               (EphyPasswordManagerQueryCallback)js_password_manager_query_finished_cb,
+                               g_object_ref (resolve));
+}
+
+static JSCValue *
+js_password_manager_query (EphyPasswordManager *self,
+                           const char          *origin,
+                           const char          *target_origin,
+                           const char          *username,
+                           const char          *username_field,
+                           const char          *password_field)
+{
+  JSCContext *js_context = jsc_context_get_current ();
+  JSCValue *executor, *promise, *retval;
+  PasswordManagerPromiseData data = { self, origin, target_origin, username, username_field, password_field 
};
+
+  executor = jsc_value_new_function (js_context, NULL,
+                                     G_CALLBACK (js_password_manager_query_resolve),
+                                     &data, NULL,
+                                     G_TYPE_NONE, 2,
+                                     JSC_TYPE_VALUE,
+                                     JSC_TYPE_VALUE);
+  promise = jsc_context_get_value (js_context, "Promise");
+  retval = jsc_value_constructor_call (promise, JSC_TYPE_VALUE, executor, G_TYPE_NONE);
+  g_object_unref (executor);
+  g_object_unref (promise);
+
+  return retval;
+}
+
+static void
+js_password_manager_save (EphyPasswordManager *self,
+                          const char          *origin,
+                          const char          *target_origin,
+                          const char          *username,
+                          const char          *password,
+                          const char          *username_field,
+                          const char          *password_field,
+                          gboolean             is_new)
+{
+  ephy_password_manager_save (self, origin, target_origin, username, password, username_field, 
password_field, is_new);
+}
+
+static GPtrArray *
+js_password_manager_cached_users (EphyPasswordManager *self,
+                                  const char          *origin)
+{
+  JSCContext *js_context = jsc_context_get_current ();
+  GPtrArray *retval;
+  GList *cached_users, *l;
+
+  cached_users = ephy_password_manager_get_cached_users (self, origin);
+  retval = g_ptr_array_new_with_free_func (g_object_unref);
+
+  for (l = cached_users; l && l->data; l = g_list_next (l))
+    g_ptr_array_add (retval, jsc_value_new_string (js_context, l->data));
+
+  return retval;
+}
+
+void
+ephy_password_manager_export_to_js_context (EphyPasswordManager *self,
+                                            JSCContext          *js_context,
+                                            JSCValue            *js_namespace)
+{
+  JSCClass *js_class;
+  JSCValue *js_password_manager;
+
+  js_class = jsc_context_register_class (js_context, "PasswordManager", NULL, NULL, NULL);
+  jsc_class_add_method (js_class,
+                        "query",
+                        G_CALLBACK (js_password_manager_query), NULL, NULL,
+                        JSC_TYPE_VALUE, 5,
+                        G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+                        G_TYPE_STRING, G_TYPE_STRING);
+  jsc_class_add_method (js_class,
+                        "save",
+                        G_CALLBACK (js_password_manager_save), NULL, NULL,
+                        G_TYPE_NONE, 7,
+                        G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+                        G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+                        G_TYPE_BOOLEAN);
+  jsc_class_add_method (js_class,
+                        "cachedUsers",
+                        G_CALLBACK (js_password_manager_cached_users), NULL, NULL,
+                        G_TYPE_PTR_ARRAY, 1,
+                        G_TYPE_STRING);
+
+  js_password_manager = jsc_value_new_object (js_context, self, js_class);
+  jsc_value_object_set_property (js_namespace, "passwordManager", js_password_manager);
+  g_object_unref (js_password_manager);
+}
diff --git a/lib/sync/ephy-password-manager.h b/lib/sync/ephy-password-manager.h
index 969651e..2f79b82 100644
--- a/lib/sync/ephy-password-manager.h
+++ b/lib/sync/ephy-password-manager.h
@@ -23,6 +23,7 @@
 #include "ephy-password-record.h"
 
 #include <glib-object.h>
+#include <jsc/jsc.h>
 #include <libsecret/secret.h>
 
 G_BEGIN_DECLS
@@ -68,5 +69,8 @@ void                 ephy_password_manager_query                    (EphyPasswor
 void                 ephy_password_manager_forget                    (EphyPasswordManager *self,
                                                                       const char          *id);
 void                 ephy_password_manager_forget_all                (EphyPasswordManager *self);
+void                 ephy_password_manager_export_to_js_context      (EphyPasswordManager *self,
+                                                                      JSCContext          *js_context,
+                                                                      JSCValue            *js_namespace);
 
 G_END_DECLS
diff --git a/meson.build b/meson.build
index 26a6fd4..375b941 100644
--- a/meson.build
+++ b/meson.build
@@ -64,7 +64,7 @@ endif
 glib_requirement = '>= 2.52.0'
 gtk_requirement = '>= 3.22.13'
 nettle_requirement = '>= 3.2'
-webkitgtk_requirement = '>= 2.19.4'
+webkitgtk_requirement = '>= 2.21.1'
 
 cairo_dep = dependency('cairo', version: '>= 1.2')
 gcr_dep = dependency('gcr-3', version: '>= 3.5.5')
diff --git a/src/prefs-dialog.c b/src/prefs-dialog.c
index 90a6c50..eb866cf 100644
--- a/src/prefs-dialog.c
+++ b/src/prefs-dialog.c
@@ -463,7 +463,7 @@ sync_message_from_fxa_content_cb (WebKitUserContentManager *manager,
   char *error_msg = NULL;
   gboolean is_error = FALSE;
 
-  message = ephy_embed_utils_get_js_result_as_string (result);
+  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;


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