/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
* Copyright © 2014 Igalia S.L.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "config.h"
#include "ephy-web-extension.h"
#include "ephy-dbus-util.h"
#include "ephy-debug.h"
#include "ephy-embed-form-auth.h"
#include "ephy-file-helpers.h"
#include "ephy-form-auth-data.h"
#include "ephy-prefs.h"
#include "ephy-settings.h"
#include "ephy-web-dom-utils.h"
#include "ephy-uri-helpers.h"
#include "ephy-uri-tester.h"
#include "ephy-web-overview.h"
#include "ephy-web-extension-names.h"
#include
#include
#include
#include
#include
#define WEBKIT_DOM_USE_UNSTABLE_API
#include
#include
struct _EphyWebExtension {
GObject parent_instance;
WebKitWebExtension *extension;
gboolean initialized;
GDBusConnection *dbus_connection;
GCancellable *cancellable;
GArray *page_created_signals_pending;
EphyUriTester *uri_tester;
EphyFormAuthDataCache *form_auth_data_cache;
GHashTable *form_auth_data_save_requests;
EphyWebOverviewModel *overview_model;
};
static const char introspection_xml[] =
""
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
"";
G_DEFINE_TYPE (EphyWebExtension, ephy_web_extension, G_TYPE_OBJECT)
static gboolean
web_page_send_request (WebKitWebPage *web_page,
WebKitURIRequest *request,
WebKitURIResponse *redirected_response,
EphyWebExtension *extension)
{
const char *request_uri;
const char *page_uri;
gboolean ret;
request_uri = webkit_uri_request_get_uri (request);
if (g_settings_get_boolean (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_DO_NOT_TRACK)) {
SoupMessageHeaders *headers;
char *new_uri;
headers = webkit_uri_request_get_http_headers (request);
if (headers) {
/* Do Not Track header. '1' means 'opt-out'. See:
* http://tools.ietf.org/id/draft-mayer-do-not-track-00.txt */
soup_message_headers_append (headers, "DNT", "1");
}
/* Remove analytics from URL before loading */
new_uri = ephy_remove_tracking_from_uri (request_uri);
if (new_uri) {
webkit_uri_request_set_uri (request, new_uri);
request_uri = webkit_uri_request_get_uri (request);
}
g_free (new_uri);
}
if (!g_settings_get_boolean (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_ENABLE_ADBLOCK))
return FALSE;
page_uri = webkit_web_page_get_uri (web_page);
/* Always load the main resource. */
if (g_strcmp0 (request_uri, page_uri) == 0)
return FALSE;
/* Always load data requests, as uri_tester won't do any good here. */
if (g_str_has_prefix (request_uri, SOUP_URI_SCHEME_DATA))
return FALSE;
ret = ephy_uri_tester_test_uri (extension->uri_tester, request_uri, page_uri);
if (ret)
g_debug ("Request '%s' blocked (page: '%s')", request_uri, page_uri);
return ret;
}
static GHashTable *
ephy_web_extension_get_form_auth_data_save_requests (EphyWebExtension *extension)
{
if (!extension->form_auth_data_save_requests) {
extension->form_auth_data_save_requests =
g_hash_table_new_full (g_direct_hash,
g_direct_equal,
NULL,
(GDestroyNotify)g_object_unref);
}
return extension->form_auth_data_save_requests;
}
static guint
form_auth_data_save_request_new_id (void)
{
static guint form_auth_data_save_request_id = 0;
return ++form_auth_data_save_request_id;
}
static void
store_password (EphyEmbedFormAuth *form_auth)
{
SoupURI *uri;
char *uri_str;
char *username_field_name = NULL;
char *username_field_value = NULL;
char *password_field_name = NULL;
char *password_field_value = NULL;
WebKitDOMNode *username_node;
EphyWebExtension *extension = ephy_web_extension_get ();
username_node = ephy_embed_form_auth_get_username_node (form_auth);
if (username_node)
g_object_get (username_node,
"name", &username_field_name,
"value", &username_field_value,
NULL);
g_object_get (ephy_embed_form_auth_get_password_node (form_auth),
"name", &password_field_name,
"value", &password_field_value,
NULL);
uri = ephy_embed_form_auth_get_uri (form_auth);
uri_str = soup_uri_to_string (uri, FALSE);
ephy_form_auth_data_store (uri_str,
username_field_name,
password_field_name,
username_field_value,
password_field_value,
NULL, NULL);
g_free (uri_str);
/* Update internal caching */
ephy_form_auth_data_cache_add (extension->form_auth_data_cache,
uri->host,
username_field_name,
password_field_name,
username_field_value);
g_free (username_field_name);
g_free (username_field_value);
g_free (password_field_name);
g_free (password_field_value);
}
static void
request_decision_on_storing (EphyEmbedFormAuth *form_auth)
{
char *username_field_value = NULL;
guint request_id;
SoupURI *uri;
WebKitDOMNode *username_node;
WebKitDOMDOMWindow *dom_window;
GVariant *variant;
gchar *message;
dom_window = webkit_dom_document_get_default_view (ephy_embed_form_auth_get_owner_document (form_auth));
if (!dom_window) {
g_object_unref (form_auth);
return;
}
request_id = form_auth_data_save_request_new_id ();
uri = ephy_embed_form_auth_get_uri (form_auth);
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),
uri ? uri->host : "",
username_field_value ? username_field_value : "");
g_free (username_field_value);
message = 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");
}
g_object_unref (dom_window);
g_free (message);
g_object_unref (form_auth);
}
static void
should_store_cb (const char *username,
const char *password,
gpointer user_data)
{
EphyEmbedFormAuth *form_auth = EPHY_EMBED_FORM_AUTH (user_data);
if (password) {
WebKitDOMNode *username_node;
char *username_field_value = NULL;
char *password_field_value = NULL;
username_node = ephy_embed_form_auth_get_username_node (form_auth);
if (username_node)
g_object_get (username_node, "value", &username_field_value, NULL);
g_object_get (ephy_embed_form_auth_get_password_node (form_auth),
"value", &password_field_value, NULL);
/* FIXME: We use only the first result, for now; We need to do
* something smarter here */
if (g_strcmp0 (username, username_field_value) == 0 &&
g_str_equal (password, password_field_value)) {
LOG ("User/password already stored. Not asking about storing.");
} else {
LOG ("User/password not yet stored. Asking about storing.");
request_decision_on_storing (g_object_ref (form_auth));
}
g_free (username_field_value);
g_free (password_field_value);
} else {
LOG ("No result on query; asking whether we should store.");
request_decision_on_storing (g_object_ref (form_auth));
}
}
static gboolean
form_submitted_cb (WebKitDOMHTMLFormElement *dom_form,
WebKitDOMEvent *dom_event,
WebKitWebPage *web_page)
{
EphyEmbedFormAuth *form_auth;
SoupURI *uri;
WebKitDOMNode *username_node = NULL;
WebKitDOMNode *password_node = NULL;
char *username_field_name = NULL;
char *username_field_value = NULL;
char *password_field_name = NULL;
char *uri_str;
if (!ephy_web_dom_utils_find_form_auth_elements (dom_form, &username_node, &password_node))
return TRUE;
if (username_node) {
g_object_get (username_node,
"value", &username_field_value,
NULL);
}
/* EphyEmbedFormAuth takes ownership of the nodes */
form_auth = ephy_embed_form_auth_new (web_page, username_node, password_node, username_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);
ephy_form_auth_data_query (uri_str,
username_field_name,
password_field_name,
username_field_value,
should_store_cb,
form_auth,
(GDestroyNotify)g_object_unref);
g_free (username_field_name);
g_free (username_field_value);
g_free (password_field_name);
g_free (uri_str);
return TRUE;
}
static void
fill_form_cb (const char *username,
const char *password,
gpointer user_data)
{
EphyEmbedFormAuth *form_auth = EPHY_EMBED_FORM_AUTH (user_data);
WebKitDOMNode *username_node;
if (username == NULL && password == NULL) {
LOG ("No result");
return;
}
username_node = ephy_embed_form_auth_get_username_node (form_auth);
LOG ("Found: user %s pass (hidden)", username_node ? username : "(none)");
if (username_node)
g_object_set (username_node, "value", username, NULL);
g_object_set (ephy_embed_form_auth_get_password_node (form_auth),
"value", password, NULL);
}
static gint
ephy_form_auth_data_compare (EphyFormAuthData *form_data,
EphyEmbedFormAuth *form_auth)
{
WebKitDOMNode *username_node;
char *username_field_name = NULL;
char *password_field_name;
gboolean retval;
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 (ephy_embed_form_auth_get_password_node (form_auth),
"name", &password_field_name, NULL);
retval = g_strcmp0 (username_field_name, form_data->form_username) == 0 &&
g_strcmp0 (password_field_name, form_data->form_password) == 0;
g_free (username_field_name);
g_free (password_field_name);
return retval ? 0 : 1;
}
static void
pre_fill_form (EphyEmbedFormAuth *form_auth)
{
GSList *form_auth_data_list;
GSList *l;
EphyFormAuthData *form_data;
SoupURI *uri;
char *uri_str;
char *username;
WebKitDOMNode *username_node;
EphyWebExtension *extension;
uri = ephy_embed_form_auth_get_uri (form_auth);
if (!uri)
return;
extension = ephy_web_extension_get ();
form_auth_data_list = ephy_form_auth_data_cache_get_list (extension->form_auth_data_cache, uri->host);
l = g_slist_find_custom (form_auth_data_list, form_auth, (GCompareFunc)ephy_form_auth_data_compare);
if (!l)
return;
form_data = (EphyFormAuthData *)l->data;
uri_str = soup_uri_to_string (uri, FALSE);
username_node = ephy_embed_form_auth_get_username_node (form_auth);
if (username_node)
g_object_get (username_node, "value", &username, NULL);
else
username = NULL;
/* The username node is empty, so pre-fill with the default. */
if (username != NULL && g_str_equal (username, ""))
g_clear_pointer (&username, g_free);
ephy_form_auth_data_query (uri_str,
form_data->form_username,
form_data->form_password,
username,
fill_form_cb,
g_object_ref (form_auth),
(GDestroyNotify)g_object_unref);
g_free (username);
g_free (uri_str);
}
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
username_changed_cb (WebKitDOMNode *username_node,
WebKitDOMEvent *dom_event,
EphyEmbedFormAuth *form_auth)
{
pre_fill_form (form_auth);
return TRUE;
}
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);
gtk_style_context_get_background_color (get_entry_style_context (),
GTK_STATE_FLAG_SELECTED,
&color);
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);
gtk_style_context_get_background_color (get_entry_style_context (),
GTK_STATE_FLAG_NORMAL,
&color);
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;
}
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;
GSList *iter;
GSList *auth_data_list;
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);
auth_data_list = (GSList *)g_object_get_data (G_OBJECT (username_node),
"ephy-auth-data-list");
username_node_ever_edited =
GPOINTER_TO_INT (g_object_get_data (G_OBJECT (username_node),
"ephy-user-ever-edited"));
for (iter = auth_data_list; iter; iter = iter->next) {
EphyFormAuthData *data;
WebKitDOMElement *li;
WebKitDOMElement *anchor;
char *child_style;
gboolean is_selected;
data = (EphyFormAuthData *)iter->data;
/* 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 (data->username, username))
continue;
is_selected = !g_strcmp0 (username, data->username);
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),
data->username,
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)
{
WebKitDOMDocument *document;
WebKitDOMElement *main_div;
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 void
form_destroyed_cb (gpointer form_auth, GObject *form)
{
g_object_unref (form_auth);
}
static void
web_page_document_loaded (WebKitWebPage *web_page,
EphyWebExtension *extension)
{
WebKitDOMHTMLCollection *forms = NULL;
WebKitDOMDocument *document = NULL;
gulong forms_n;
guint i;
if (!extension->form_auth_data_cache ||
!g_settings_get_boolean (EPHY_SETTINGS_MAIN, EPHY_PREFS_REMEMBER_PASSWORDS))
return;
document = webkit_web_page_get_dom_document (web_page);
forms = webkit_dom_document_get_forms (document);
forms_n = webkit_dom_html_collection_get_length (forms);
if (forms_n == 0) {
LOG ("No forms found.");
g_object_unref (forms);
return;
}
for (i = 0; i < forms_n; i++) {
WebKitDOMHTMLFormElement *form;
WebKitDOMNode *username_node = NULL;
WebKitDOMNode *password_node = NULL;
form = WEBKIT_DOM_HTML_FORM_ELEMENT (webkit_dom_html_collection_item (forms, i));
/* We have a field that may be the user, and one for a password. */
if (ephy_web_dom_utils_find_form_auth_elements (form, &username_node, &password_node)) {
EphyEmbedFormAuth *form_auth;
GSList *auth_data_list;
const char *uri_string;
SoupURI *uri;
LOG ("Hooking and pre-filling a form");
/* EphyEmbedFormAuth takes ownership of the nodes */
form_auth = ephy_embed_form_auth_new (web_page, username_node, password_node, NULL);
webkit_dom_event_target_add_event_listener (WEBKIT_DOM_EVENT_TARGET (form), "submit",
G_CALLBACK (form_submitted_cb), FALSE,
web_page);
if (username_node) {
webkit_dom_event_target_add_event_listener (WEBKIT_DOM_EVENT_TARGET (username_node), "blur",
G_CALLBACK (username_changed_cb), FALSE,
form_auth);
}
/* Plug in the user autocomplete */
uri_string = webkit_web_page_get_uri (web_page);
uri = soup_uri_new (uri_string);
auth_data_list = ephy_form_auth_data_cache_get_list (extension->form_auth_data_cache, uri->host);
soup_uri_free (uri);
if (auth_data_list && auth_data_list->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-auth-data-list", auth_data_list);
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 auth_data_list, not hooking menu for choosing.");
pre_fill_form (form_auth);
g_object_weak_ref (G_OBJECT (form), form_destroyed_cb, form_auth);
} else
LOG ("No pre-fillable/hookable form found");
}
g_object_unref (forms);
}
static void
web_page_uri_changed (WebKitWebPage *web_page,
GParamSpec *param_spec,
EphyWebExtension *extension)
{
EphyWebOverview *overview = NULL;
if (g_strcmp0 (webkit_web_page_get_uri (web_page), "ephy-about:overview") == 0)
overview = ephy_web_overview_new (web_page, extension->overview_model);
g_object_set_data_full (G_OBJECT (web_page), "ephy-web-overview", overview, g_object_unref);
}
static gboolean
web_page_context_menu (WebKitWebPage *web_page,
WebKitContextMenu *context_menu,
WebKitWebHitTestResult *hit_test_result,
gpointer user_data)
{
char *string;
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);
g_object_unref (window);
if (!selection)
return FALSE;
string = ephy_web_dom_utils_get_selection_as_string (selection);
g_object_unref (selection);
if (!string || *string == '\0') {
g_free (string);
return FALSE;
}
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&builder, "{sv}", "SelectedText", g_variant_new_string (g_strstrip (string)));
webkit_context_menu_set_user_data (context_menu,
g_variant_builder_end (&builder));
g_free (string);
return TRUE;
}
static void
ephy_web_extension_emit_page_created (EphyWebExtension *extension,
guint64 page_id)
{
GError *error = NULL;
g_dbus_connection_emit_signal (extension->dbus_connection,
NULL,
EPHY_WEB_EXTENSION_OBJECT_PATH,
EPHY_WEB_EXTENSION_INTERFACE,
"PageCreated",
g_variant_new ("(t)", page_id),
&error);
if (error) {
g_warning ("Error emitting signal PageCreated: %s\n", error->message);
g_error_free (error);
}
}
static void
ephy_web_extension_emit_page_created_signals_pending (EphyWebExtension *extension)
{
guint i;
if (!extension->page_created_signals_pending)
return;
for (i = 0; i < extension->page_created_signals_pending->len; i++) {
guint64 page_id;
page_id = g_array_index (extension->page_created_signals_pending, guint64, i);
ephy_web_extension_emit_page_created (extension, page_id);
}
g_array_free (extension->page_created_signals_pending, TRUE);
extension->page_created_signals_pending = NULL;
}
static void
ephy_web_extension_queue_page_created_signal_emission (EphyWebExtension *extension,
guint64 page_id)
{
if (!extension->page_created_signals_pending)
extension->page_created_signals_pending = g_array_new (FALSE, FALSE, sizeof (guint64));
extension->page_created_signals_pending = g_array_append_val (extension->page_created_signals_pending, page_id);
}
static void
ephy_web_extension_page_created_cb (EphyWebExtension *extension,
WebKitWebPage *web_page)
{
guint64 page_id;
page_id = webkit_web_page_get_id (web_page);
if (extension->dbus_connection)
ephy_web_extension_emit_page_created (extension, page_id);
else
ephy_web_extension_queue_page_created_signal_emission (extension, page_id);
g_signal_connect (web_page, "send-request",
G_CALLBACK (web_page_send_request),
extension);
g_signal_connect (web_page, "document-loaded",
G_CALLBACK (web_page_document_loaded),
extension);
g_signal_connect (web_page, "notify::uri",
G_CALLBACK (web_page_uri_changed),
extension);
g_signal_connect (web_page, "context-menu",
G_CALLBACK (web_page_context_menu),
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,
const char *object_path,
const char *interface_name,
const char *method_name,
GVariant *parameters,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
EphyWebExtension *extension = EPHY_WEB_EXTENSION (user_data);
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;
char *base_uri = NULL;
char *uri = NULL;
char *color = NULL;
guint64 page_id;
g_variant_get (parameters, "(ts)", &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;
guint request_id;
gboolean should_store;
GHashTable *requests;
requests = ephy_web_extension_get_form_auth_data_save_requests (extension);
g_variant_get (parameters, "(ub)", &request_id, &should_store);
form_auth = g_hash_table_lookup (requests, GINT_TO_POINTER (request_id));
if (!form_auth)
return;
if (should_store)
store_password (form_auth);
g_hash_table_remove (requests, GINT_TO_POINTER (request_id));
} else if (g_strcmp0 (method_name, "HistorySetURLs") == 0) {
if (extension->overview_model) {
GVariantIter iter;
GVariant *array;
const char *url;
const char *title;
GList *items = NULL;
g_variant_get (parameters, "(@a(ss))", &array);
g_variant_iter_init (&iter, array);
while (g_variant_iter_loop (&iter, "(&s&s)", &url, &title))
items = g_list_prepend (items, ephy_web_overview_model_item_new (url, title));
g_variant_unref (array);
ephy_web_overview_model_set_urls (extension->overview_model, g_list_reverse (items));
}
g_dbus_method_invocation_return_value (invocation, NULL);
} else if (g_strcmp0 (method_name, "HistorySetURLThumbnail") == 0) {
if (extension->overview_model) {
const char *url;
const char *path;
g_variant_get (parameters, "(&s&s)", &url, &path);
ephy_web_overview_model_set_url_thumbnail (extension->overview_model, url, path);
}
g_dbus_method_invocation_return_value (invocation, NULL);
} else if (g_strcmp0 (method_name, "HistorySetURLTitle") == 0) {
if (extension->overview_model) {
const char *url;
const char *title;
g_variant_get (parameters, "(&s&s)", &url, &title);
ephy_web_overview_model_set_url_title (extension->overview_model, url, title);
}
g_dbus_method_invocation_return_value (invocation, NULL);
} else if (g_strcmp0 (method_name, "HistoryDeleteURL") == 0) {
if (extension->overview_model) {
const char *url;
g_variant_get (parameters, "(&s)", &url);
ephy_web_overview_model_delete_url (extension->overview_model, url);
}
g_dbus_method_invocation_return_value (invocation, NULL);
} else if (g_strcmp0 (method_name, "HistoryDeleteHost") == 0) {
if (extension->overview_model) {
const char *host;
g_variant_get (parameters, "(&s)", &host);
ephy_web_overview_model_delete_host (extension->overview_model, host);
}
g_dbus_method_invocation_return_value (invocation, NULL);
} else if (g_strcmp0 (method_name, "HistoryClear") == 0) {
if (extension->overview_model)
ephy_web_overview_model_clear (extension->overview_model);
g_dbus_method_invocation_return_value (invocation, NULL);
}
}
static const GDBusInterfaceVTable interface_vtable = {
handle_method_call,
NULL,
NULL
};
static void
ephy_web_extension_dispose (GObject *object)
{
EphyWebExtension *extension = EPHY_WEB_EXTENSION (object);
g_clear_object (&extension->uri_tester);
g_clear_object (&extension->overview_model);
g_clear_pointer (&extension->form_auth_data_cache,
ephy_form_auth_data_cache_free);
if (extension->form_auth_data_save_requests) {
g_hash_table_destroy (extension->form_auth_data_save_requests);
extension->form_auth_data_save_requests = NULL;
}
if (extension->page_created_signals_pending) {
g_array_free (extension->page_created_signals_pending, TRUE);
extension->page_created_signals_pending = NULL;
}
g_clear_object (&extension->cancellable);
g_clear_object (&extension->dbus_connection);
g_clear_object (&extension->extension);
G_OBJECT_CLASS (ephy_web_extension_parent_class)->dispose (object);
}
static void
ephy_web_extension_class_init (EphyWebExtensionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = ephy_web_extension_dispose;
}
static void
ephy_web_extension_init (EphyWebExtension *extension)
{
extension->overview_model = ephy_web_overview_model_new ();
}
static gpointer
ephy_web_extension_create_instance (gpointer data)
{
return g_object_new (EPHY_TYPE_WEB_EXTENSION, NULL);
}
EphyWebExtension *
ephy_web_extension_get (void)
{
static GOnce once_init = G_ONCE_INIT;
return EPHY_WEB_EXTENSION (g_once (&once_init, ephy_web_extension_create_instance, NULL));
}
static void
dbus_connection_created_cb (GObject *source_object,
GAsyncResult *result,
EphyWebExtension *extension)
{
static GDBusNodeInfo *introspection_data = NULL;
GDBusConnection *connection;
guint registration_id;
GError *error = NULL;
if (!introspection_data)
introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
connection = g_dbus_connection_new_for_address_finish (result, &error);
if (error) {
g_warning ("Failed to connect to UI process: %s", error->message);
g_error_free (error);
return;
}
registration_id =
g_dbus_connection_register_object (connection,
EPHY_WEB_EXTENSION_OBJECT_PATH,
introspection_data->interfaces[0],
&interface_vtable,
extension,
NULL,
&error);
if (!registration_id) {
g_warning ("Failed to register web extension object: %s\n", error->message);
g_error_free (error);
g_object_unref (connection);
return;
}
extension->dbus_connection = connection;
ephy_web_extension_emit_page_created_signals_pending (extension);
}
static gboolean
authorize_authenticated_peer_cb (GDBusAuthObserver *observer,
GIOStream *stream,
GCredentials *credentials,
EphyWebExtension *extension)
{
return ephy_dbus_peer_is_authorized (credentials);
}
void
ephy_web_extension_initialize (EphyWebExtension *extension,
WebKitWebExtension *wk_extension,
const char *server_address,
const char *dot_dir,
gboolean is_private_profile)
{
GDBusAuthObserver *observer;
g_return_if_fail (EPHY_IS_WEB_EXTENSION (extension));
if (extension->initialized)
return;
extension->initialized = TRUE;
extension->extension = g_object_ref (wk_extension);
extension->uri_tester = ephy_uri_tester_new (dot_dir);
if (!is_private_profile)
extension->form_auth_data_cache = ephy_form_auth_data_cache_new ();
g_signal_connect_swapped (extension->extension, "page-created",
G_CALLBACK (ephy_web_extension_page_created_cb),
extension);
extension->cancellable = g_cancellable_new ();
observer = g_dbus_auth_observer_new ();
g_signal_connect (observer, "authorize-authenticated-peer",
G_CALLBACK (authorize_authenticated_peer_cb), extension);
g_dbus_connection_new_for_address (server_address,
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
observer,
extension->cancellable,
(GAsyncReadyCallback)dbus_connection_created_cb,
extension);
g_object_unref (observer);
}