[epiphany] adblock: Postpone navigation decisions until ready



commit 400a32f7ad3a43582714d7b439136d91d279777f
Author: Adrian Perez de Castro <aperez igalia com>
Date:   Sun Aug 11 16:00:26 2019 +0300

    adblock: Postpone navigation decisions until ready
    
    This defers navigation decisions while EphyFiltersManager is performing
    its initial setup, with the aim of avoiding touching the network without
    the adblocker filters being in use. This is particuarly important to
    avoid accidentally making requests to trackers which would be filtered
    by the adblocker in the small time window while the EphyFiltersManager
    initializes.
    
    Blocking of requests is done by tapping into WebKitWebView:decide-policy
    because the signal can be handled asynchronously, and while a decision
    is not made WebKit will not start performing requests. Thus, a list of
    pending decisions is kept and items which were added to it are decided
    when the EphyFiltersManager:is-initialized property changes.
    
    The existing VerifyUrlAsyncData struct (which was being used by the
    safe browsing code) is reused to hold the information about pending
    navigation decisions.

 embed/ephy-embed-shell.c     |  8 ++++
 embed/ephy-embed-shell.h     |  3 ++
 embed/ephy-filters-manager.c | 95 ++++++++++++++++++++++++++++++++++++++++----
 embed/ephy-filters-manager.h |  1 +
 src/ephy-window.c            | 90 ++++++++++++++++++++++++++++++++++-------
 5 files changed, 174 insertions(+), 23 deletions(-)
---
diff --git a/embed/ephy-embed-shell.c b/embed/ephy-embed-shell.c
index 521c3c9ab..c2778d6e1 100644
--- a/embed/ephy-embed-shell.c
+++ b/embed/ephy-embed-shell.c
@@ -1670,6 +1670,14 @@ ephy_embed_shell_clear_cache (EphyEmbedShell *shell)
   webkit_web_context_clear_cache (priv->web_context);
 }
 
+EphyFiltersManager *
+ephy_embed_shell_get_filters_manager (EphyEmbedShell *shell)
+{
+  EphyEmbedShellPrivate *priv = ephy_embed_shell_get_instance_private (shell);
+
+  return priv->filters_manager;
+}
+
 WebKitUserContentManager *
 ephy_embed_shell_get_user_content_manager (EphyEmbedShell *shell)
 {
diff --git a/embed/ephy-embed-shell.h b/embed/ephy-embed-shell.h
index 4d9a351b6..6abfddbc0 100644
--- a/embed/ephy-embed-shell.h
+++ b/embed/ephy-embed-shell.h
@@ -34,6 +34,8 @@
 
 G_BEGIN_DECLS
 
+typedef struct _EphyFiltersManager EphyFiltersManager;
+
 #define EPHY_TYPE_EMBED_SHELL (ephy_embed_shell_get_type ())
 
 G_DECLARE_DERIVABLE_TYPE (EphyEmbedShell, ephy_embed_shell, EPHY, EMBED_SHELL, DzlApplication)
@@ -78,6 +80,7 @@ void               ephy_embed_shell_set_thumbnail_path         (EphyEmbedShell
                                                                 const char       *path);
 void               ephy_embed_shell_schedule_thumbnail_update  (EphyEmbedShell   *shell,
                                                                 EphyHistoryURL   *url);
+EphyFiltersManager       *ephy_embed_shell_get_filters_manager      (EphyEmbedShell *shell);
 WebKitUserContentManager *ephy_embed_shell_get_user_content_manager (EphyEmbedShell *shell);
 EphyDownloadsManager     *ephy_embed_shell_get_downloads_manager    (EphyEmbedShell *shell);
 EphyPermissionsManager   *ephy_embed_shell_get_permissions_manager  (EphyEmbedShell *shell);
diff --git a/embed/ephy-filters-manager.c b/embed/ephy-filters-manager.c
index 2383b472f..dcc57ca23 100644
--- a/embed/ephy-filters-manager.c
+++ b/embed/ephy-filters-manager.c
@@ -35,6 +35,7 @@
 
 struct _EphyFiltersManager {
   GObject parent_instance;
+  gboolean is_initialized;
 
   char *filters_dir;
   GHashTable *filters;  /* (identifier, FilterInfo) */
@@ -57,6 +58,7 @@ static guint s_signals[LAST_SIGNAL];
 enum {
   PROP_0,
   PROP_FILTERS_DIR,
+  PROP_IS_INITIALIZED,
   N_PROPERTIES
 };
 
@@ -69,9 +71,9 @@ typedef struct {
   char *checksum;        /* Saved. */
   gint64 last_update;    /* Saved, seconds since the Epoch. */
 
-  gboolean found   : 1;  /* WebKitUserContentFilter found during lookup. */
-  gboolean enabled : 1;  /* The filter is already enabled. */
-  gboolean local   : 1;  /* The source_uri is a local file URI. */
+  gboolean found : 1;    /* WebKitUserContentFilter found during lookup. */
+  gboolean local : 1;    /* The source_uri is a local file URI. */
+  gboolean done  : 1;    /* Filter setup done (successfully or errored). */
 } FilterInfo;
 
 /* The "saved" fields from the struct above are stored as versioned sidecar
@@ -83,6 +85,8 @@ typedef struct {
 #define FILTER_INFO_VARIANT_VERSION ((uint32_t)2)
 #define FILTER_INFO_VARIANT_FORMAT  "(usmsx)"
 
+static void filter_info_setup_done (FilterInfo *self);
+
 static void
 filter_info_free (FilterInfo *self)
 {
@@ -364,7 +368,6 @@ filter_info_setup_enable_compiled_filter (FilterInfo              *self,
 
   LOG ("Emitting EphyFiltersManager::filter-ready for %s.", filter_info_get_identifier (self));
   g_signal_emit (self->manager, s_signals[FILTER_READY], 0, wk_filter);
-  self->enabled = TRUE;
 }
 
 static gboolean
@@ -465,6 +468,9 @@ filter_saved_cb (WebKitUserContentFilterStore *store,
                filter_info_get_identifier (self), self->source_uri,
                error->message);
   }
+
+  /* In either case, setting up this filter is done. */
+  filter_info_setup_done (self);
 }
 
 static void
@@ -505,6 +511,7 @@ filter_info_setup_load_file (FilterInfo *self,
     g_warning ("Cannot map filter %s source file %s: %s",
                filter_info_get_identifier (self),
                json_file_path, error->message);
+    filter_info_setup_done (self);
     return;
   }
 
@@ -522,6 +529,7 @@ filter_info_setup_load_file (FilterInfo *self,
                               self);
     LOG ("Filter %s not stale, source checksum unchanged (%s), recompilation skipped.",
          filter_info_get_identifier (self), self->checksum);
+    filter_info_setup_done (self);
   } else {
     webkit_user_content_filter_store_save (self->manager->store,
                                            filter_info_get_identifier (self),
@@ -550,6 +558,7 @@ download_completed_cb (EphyDownload *download,
     g_warning ("Filter source %s has invalid MIME type: %s",
                ephy_download_get_destination_uri (download),
                ephy_download_get_content_type (download));
+    filter_info_setup_done (self);
   }
 
   g_object_unref (download);
@@ -567,14 +576,15 @@ download_errored_cb (EphyDownload *download,
   g_signal_handlers_disconnect_by_data (download, self);
 
   if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-    g_warning ("Cannot fetch source for filter %s from <%s>",
-               filter_info_get_identifier (self), self->source_uri);
+    g_warning ("Cannot fetch source for filter %s from <%s>: %s",
+               filter_info_get_identifier (self), self->source_uri,
+               error ? error->message : "Unknown error");
 
   /* There is not much else we can do if the download failed. Note that it
    * is still possible that if a precompiled version of the filter was found
    * that may get used instead.
    */
-  LOG ("Done fetching filter %s", filter_info_get_identifier (self));
+  filter_info_setup_done (self);
 
   g_object_unref (download);
 }
@@ -613,6 +623,7 @@ filter_load_cb (WebKitUserContentFilterStore *store,
          (self->manager->update_time - self->last_update),
          ADBLOCK_FILTER_UPDATE_FREQUENCY);
   } else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+    filter_info_setup_done (self);
     return;
   } else if (g_error_matches (error,
                               WEBKIT_USER_CONTENT_FILTER_ERROR,
@@ -625,8 +636,10 @@ filter_load_cb (WebKitUserContentFilterStore *store,
                error->message);
   }
 
-  if (!filter_info_needs_updating_from_source (self))
+  if (!filter_info_needs_updating_from_source (self)) {
+    filter_info_setup_done (self);
     return;
+  }
 
   /* Even if a compiled filter was found, we may need to compile an updated
    * version if the local file has changed, or the contents of remote URIs
@@ -667,6 +680,7 @@ filter_info_setup_start (FilterInfo *self)
 
   LOG ("Setup started for <%s> id=%s", self->source_uri, filter_info_get_identifier (self));
 
+  self->done = FALSE;
   webkit_user_content_filter_store_load (self->manager->store,
                                          filter_info_get_identifier (self),
                                          self->manager->cancellable,
@@ -674,6 +688,49 @@ filter_info_setup_start (FilterInfo *self)
                                          self);
 }
 
+static void
+filters_manager_ensure_initialized (EphyFiltersManager *manager)
+{
+  g_assert (EPHY_IS_FILTERS_MANAGER (manager));
+  if (manager->is_initialized)
+    return;
+
+  LOG ("Setting EphyFiltersManager as initialized.");
+  manager->is_initialized = TRUE;
+  g_object_notify_by_pspec (G_OBJECT (manager),
+                            object_properties[PROP_IS_INITIALIZED]);
+}
+
+static void
+accumulate_filter_done (const char *identifier,
+                        FilterInfo *filter,
+                        gboolean   *done)
+{
+  g_assert (strcmp (identifier, filter_info_get_identifier (filter)) == 0);
+  g_assert (g_hash_table_contains (filter->manager->filters, identifier));
+
+  *done = *done && filter->done;
+}
+
+static void
+filter_info_setup_done (FilterInfo *self)
+{
+  gboolean done = self->done = TRUE;
+
+  g_hash_table_foreach (self->manager->filters,
+                        (GHFunc)accumulate_filter_done,
+                        &done);
+
+  LOG ("Setup for filter %s from <%s> completed.",
+       filter_info_get_identifier (self), self->source_uri);
+
+  if (done) {
+    LOG ("Setup completed for %u filters.",
+         g_hash_table_size (self->manager->filters));
+    filters_manager_ensure_initialized (self->manager);
+  }
+}
+
 static void
 filter_removed_cb (WebKitUserContentFilterStore *store,
                    GAsyncResult                 *result,
@@ -753,6 +810,8 @@ update_adblock_filter_files_cb (GSettings          *settings,
   if (!g_settings_get_boolean (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_ENABLE_ADBLOCK)) {
     LOG ("Filters are disabled, skipping update.");
     g_signal_emit (manager, s_signals[FILTERS_DISABLED], 0);
+    /* If the ad blocker is disabled, initialization is done. */
+    filters_manager_ensure_initialized (manager);
     return;
   }
 
@@ -889,6 +948,9 @@ ephy_filters_manager_set_property (GObject      *object,
     case PROP_FILTERS_DIR:
       manager->filters_dir = g_value_dup_string (value);
       break;
+    case PROP_IS_INITIALIZED:
+      manager->is_initialized = g_value_get_boolean (value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
   }
@@ -906,6 +968,9 @@ ephy_filters_manager_get_property (GObject    *object,
     case PROP_FILTERS_DIR:
       g_value_set_string (value, manager->filters_dir);
       break;
+    case PROP_IS_INITIALIZED:
+      g_value_set_boolean (value, manager->is_initialized);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
   }
@@ -944,6 +1009,13 @@ ephy_filters_manager_class_init (EphyFiltersManagerClass *klass)
                          "",
                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
 
+  object_properties[PROP_IS_INITIALIZED] =
+    g_param_spec_boolean ("is-initialized",
+                          "Filters manager is initialized",
+                          "Whether initialization was completed",
+                          FALSE,
+                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
   g_object_class_install_properties (object_class,
                                      N_PROPERTIES,
                                      object_properties);
@@ -972,3 +1044,10 @@ ephy_filters_manager_get_adblock_filters_dir (EphyFiltersManager *manager)
 {
   return manager->filters_dir;
 }
+
+gboolean
+ephy_filters_manager_get_is_initialized (EphyFiltersManager *manager)
+{
+  g_return_val_if_fail (EPHY_IS_FILTERS_MANAGER (manager), FALSE);
+  return manager->is_initialized;
+}
diff --git a/embed/ephy-filters-manager.h b/embed/ephy-filters-manager.h
index 6bdc394d2..a6dc4c8d0 100644
--- a/embed/ephy-filters-manager.h
+++ b/embed/ephy-filters-manager.h
@@ -30,5 +30,6 @@ G_DECLARE_FINAL_TYPE (EphyFiltersManager, ephy_filters_manager, EPHY, FILTERS_MA
 
 EphyFiltersManager *ephy_filters_manager_new                     (const char         *adblock_filters_dir);
 const char         *ephy_filters_manager_get_adblock_filters_dir (EphyFiltersManager *manager);
+gboolean            ephy_filters_manager_get_is_initialized      (EphyFiltersManager *manager);
 
 G_END_DECLS
diff --git a/src/ephy-window.c b/src/ephy-window.c
index a33801501..51b0edc8d 100644
--- a/src/ephy-window.c
+++ b/src/ephy-window.c
@@ -34,6 +34,7 @@
 #include "ephy-embed-type-builtins.h"
 #include "ephy-embed-utils.h"
 #include "ephy-file-helpers.h"
+#include "ephy-filters-manager.h"
 #include "ephy-find-toolbar.h"
 #include "ephy-gsb-utils.h"
 #include "ephy-gui.h"
@@ -165,6 +166,9 @@ struct _EphyWindow {
   int last_opened_pos;
   gboolean show_fullscreen_header_bar;
 
+  GList *pending_decisions;
+  gulong filters_initialized_id;
+
   gint current_width;
   gint current_height;
   gint current_x;
@@ -2191,13 +2195,66 @@ verify_url_cb (EphyGSBService     *service,
   verify_url_async_data_free (data);
 }
 
+static gboolean
+decide_navigation (EphyWindow               *window,
+                   WebKitWebView            *web_view,
+                   WebKitPolicyDecision     *decision,
+                   WebKitPolicyDecisionType  decision_type,
+                   const char               *request_uri)
+{
+  EphyGSBService *service;
+
+  if (g_settings_get_boolean (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_ENABLE_SAFE_BROWSING)) {
+    if (ephy_web_view_get_should_bypass_safe_browsing (EPHY_WEB_VIEW (web_view))) {
+      /* This means the user has decided to proceed to an unsafe website. */
+      ephy_web_view_set_should_bypass_safe_browsing (EPHY_WEB_VIEW (web_view), FALSE);
+      return decide_navigation_policy (web_view, decision, decision_type, window);
+    }
+
+    service = ephy_embed_shell_get_global_gsb_service (ephy_embed_shell_get_default ());
+    ephy_gsb_service_verify_url (service, request_uri,
+                                 (GAsyncReadyCallback)verify_url_cb,
+                                 /* Note: this refs the policy decision, so we can complete it 
asynchronously. */
+                                 verify_url_async_data_new (window, web_view,
+                                                            decision, decision_type,
+                                                            request_uri));
+    return TRUE;
+  }
+
+  return decide_navigation_policy (web_view, decision, decision_type, window);
+}
+
+static void
+resolve_pending_decision (VerifyUrlAsyncData *async_data)
+{
+  decide_navigation (async_data->window,
+                     async_data->web_view,
+                     async_data->decision,
+                     async_data->decision_type,
+                     async_data->request_uri);
+}
+
+static void
+filters_initialized_cb (EphyFiltersManager *filters_manager,
+                        GParamSpec         *pspec,
+                        EphyWindow         *window)
+{
+  g_assert (!ephy_filters_manager_get_is_initialized (filters_manager));
+
+  g_signal_handler_disconnect (filters_manager, window->filters_initialized_id);
+
+  g_list_foreach (window->pending_decisions, (GFunc)resolve_pending_decision, NULL);
+  g_list_free_full (window->pending_decisions, (GDestroyNotify)verify_url_async_data_free);
+  window->pending_decisions = NULL;
+}
+
 static gboolean
 decide_policy_cb (WebKitWebView            *web_view,
                   WebKitPolicyDecision     *decision,
                   WebKitPolicyDecisionType  decision_type,
                   EphyWindow               *window)
 {
-  EphyGSBService *service;
+  EphyFiltersManager *filters_manager;
   WebKitNavigationPolicyDecision *navigation_decision;
   WebKitNavigationAction *navigation_action;
   WebKitURIRequest *request;
@@ -2212,24 +2269,27 @@ decide_policy_cb (WebKitWebView            *web_view,
   request = webkit_navigation_action_get_request (navigation_action);
   request_uri = webkit_uri_request_get_uri (request);
 
-  if (g_settings_get_boolean (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_ENABLE_SAFE_BROWSING)) {
-    if (ephy_web_view_get_should_bypass_safe_browsing (EPHY_WEB_VIEW (web_view))) {
-      /* This means the user has decided to proceed to an unsafe website. */
-      ephy_web_view_set_should_bypass_safe_browsing (EPHY_WEB_VIEW (web_view), FALSE);
-      return decide_navigation_policy (web_view, decision, decision_type, window);
+  filters_manager = ephy_embed_shell_get_filters_manager (ephy_embed_shell_get_default ());
+  if (!ephy_filters_manager_get_is_initialized (filters_manager)) {
+    /* Queue request while filters initialization is in progress. */
+    VerifyUrlAsyncData *async_data = verify_url_async_data_new (window,
+                                                                web_view,
+                                                                decision,
+                                                                decision_type,
+                                                                request_uri);
+    window->pending_decisions = g_list_append (window->pending_decisions,
+                                               async_data);
+    if (!window->filters_initialized_id) {
+      window->filters_initialized_id =
+        g_signal_connect_object (filters_manager,
+                                 "notify::is-initialized",
+                                 G_CALLBACK (filters_initialized_cb),
+                                 window, 0);
     }
-
-    service = ephy_embed_shell_get_global_gsb_service (ephy_embed_shell_get_default ());
-    ephy_gsb_service_verify_url (service, request_uri,
-                                 (GAsyncReadyCallback)verify_url_cb,
-                                 /* Note: this refs the policy decision, so we can complete it 
asynchronously. */
-                                 verify_url_async_data_new (window, web_view,
-                                                            decision, decision_type,
-                                                            request_uri));
     return TRUE;
   }
 
-  return decide_navigation_policy (web_view, decision, decision_type, window);
+  return decide_navigation (window, web_view, decision, decision_type, request_uri);
 }
 
 static void


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