[epiphany] Add option to run user scripts



commit ae7e20bfebf8e071d1c2028a8bc403c6ff714af9
Author: Jan-Michael Brummer <jan brummer tabos org>
Date:   Thu May 7 15:41:11 2020 +0200

    Add option to run user scripts
    
    Fixes: https://gitlab.gnome.org/GNOME/epiphany/-/issues/298

 data/org.gnome.epiphany.gschema.xml |   5 ++
 embed/ephy-embed-prefs.c            | 120 ++++++++++++++++++++++++++++++++++++
 embed/ephy-embed-prefs.h            |   2 +
 embed/ephy-embed-shell.c            |   1 +
 lib/ephy-prefs.h                    |   2 +
 src/prefs-dialog.c                  |  66 ++++++++++++++++++++
 src/resources/gtk/prefs-dialog.ui   |  35 +++++++++++
 7 files changed, 231 insertions(+)
---
diff --git a/data/org.gnome.epiphany.gschema.xml b/data/org.gnome.epiphany.gschema.xml
index cb2227038..4e816a1eb 100644
--- a/data/org.gnome.epiphany.gschema.xml
+++ b/data/org.gnome.epiphany.gschema.xml
@@ -131,6 +131,11 @@
                        <summary>Use a custom CSS</summary>
                        <description>Use a custom CSS file to modify websites own CSS.</description>
                </key>
+               <key type="b" name="enable-user-js">
+                       <default>false</default>
+                       <summary>Use a custom JS</summary>
+                       <description>Use a custom JS file to modify websites.</description>
+               </key>
                <key type="b" name="enable-spell-checking">
                        <default>true</default>
                        <summary>Enable spell checking</summary>
diff --git a/embed/ephy-embed-prefs.c b/embed/ephy-embed-prefs.c
index 31b99c4d0..358bbe393 100644
--- a/embed/ephy-embed-prefs.c
+++ b/embed/ephy-embed-prefs.c
@@ -43,6 +43,8 @@ typedef struct {
 static WebKitSettings *webkit_settings = NULL;
 static GFileMonitor *user_style_sheet_monitor = NULL;
 static WebKitUserStyleSheet *style_sheet = NULL;
+static GFileMonitor *user_javascript_monitor = NULL;
+static WebKitUserScript *javascript = NULL;
 static GList *ucm_list = NULL;
 
 static void
@@ -154,6 +156,113 @@ webkit_pref_callback_user_stylesheet (GSettings  *settings,
   }
 }
 
+static void
+update_user_javascript_on_all_ucm (void)
+{
+  GList *list = NULL;
+
+  for (list = ucm_list; list != NULL; list = list->next) {
+    WebKitUserContentManager *ucm = list->data;
+
+    webkit_user_content_manager_remove_all_scripts (ucm);
+    if (javascript)
+      webkit_user_content_manager_add_script (ucm, javascript);
+  }
+}
+
+static void
+user_javascript_output_stream_splice_cb (GOutputStream *output_stream,
+                                         GAsyncResult  *result,
+                                         gpointer       user_data)
+{
+  gssize bytes;
+
+  g_clear_pointer (&javascript, webkit_user_script_unref);
+
+  bytes = g_output_stream_splice_finish (output_stream, result, NULL);
+  if (bytes > 0) {
+    javascript = webkit_user_script_new (g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM 
(output_stream)),
+                                         WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, 
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END,
+                                         NULL, NULL);
+  }
+
+  update_user_javascript_on_all_ucm ();
+
+  g_object_unref (output_stream);
+}
+
+static void
+user_javascript_read_cb (GFile        *file,
+                         GAsyncResult *result,
+                         gpointer      user_data)
+{
+  g_autoptr (GFileInputStream) input_stream = NULL;
+  g_autoptr (GOutputStream) output_stream = NULL;
+
+  input_stream = g_file_read_finish (file, result, NULL);
+  if (!input_stream)
+    return;
+
+  output_stream = g_memory_output_stream_new_resizable ();
+  g_output_stream_splice_async (g_steal_pointer (&output_stream),
+                                G_INPUT_STREAM (input_stream),
+                                G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+                                G_PRIORITY_DEFAULT,
+                                NULL,
+                                (GAsyncReadyCallback)user_javascript_output_stream_splice_cb,
+                                NULL);
+}
+
+static void
+user_javascript_file_changed (GFileMonitor      *monitor,
+                              GFile             *file,
+                              GFile             *other_file,
+                              GFileMonitorEvent  event_type,
+                              gpointer           user_data)
+{
+  if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) {
+    g_file_read_async (file, G_PRIORITY_DEFAULT, NULL,
+                       (GAsyncReadyCallback)user_javascript_read_cb, NULL);
+  }
+}
+
+static void
+webkit_pref_callback_user_javascript (GSettings  *settings,
+                                      const char *key,
+                                      gpointer    data)
+{
+  g_autoptr (GFile) file = NULL;
+  g_autofree char *filename = NULL;
+  gboolean value;
+  g_autoptr (GError) error = NULL;
+
+  value = g_settings_get_boolean (settings, key);
+
+  if (user_javascript_monitor) {
+    g_signal_handlers_disconnect_by_func (user_javascript_monitor, user_javascript_file_changed, NULL);
+    g_clear_object (&user_style_sheet_monitor);
+  }
+
+  if (!value) {
+    update_user_javascript_on_all_ucm ();
+    return;
+  }
+
+  filename = g_build_filename (ephy_profile_dir (), USER_JAVASCRIPT_FILENAME, NULL);
+  file = g_file_new_for_path (filename);
+
+  g_file_read_async (file, G_PRIORITY_DEFAULT, NULL,
+                     (GAsyncReadyCallback)user_javascript_read_cb, NULL);
+
+  user_javascript_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error);
+  if (!user_javascript_monitor) {
+    g_warning ("Could not create a file monitor for %s: %s\n", g_file_get_uri (file), error->message);
+    g_error_free (error);
+  } else {
+    g_signal_connect (user_javascript_monitor, "changed", G_CALLBACK (user_javascript_file_changed), NULL);
+  }
+}
+
 static void
 webkit_pref_callback_user_agent (GSettings  *settings,
                                  const char *key,
@@ -470,6 +579,10 @@ static const PrefData webkit_pref_entries[] = {
     EPHY_PREFS_WEB_ENABLE_USER_CSS,
     "user-stylesheet-uri",
     webkit_pref_callback_user_stylesheet },
+  { EPHY_PREFS_WEB_SCHEMA,
+    EPHY_PREFS_WEB_ENABLE_USER_JS,
+    "user-javascript-uri",
+    webkit_pref_callback_user_javascript },
   { EPHY_PREFS_WEB_SCHEMA,
     EPHY_PREFS_WEB_LANGUAGE,
     "accept-language",
@@ -560,6 +673,13 @@ ephy_embed_prefs_apply_user_style (WebKitUserContentManager *ucm)
     webkit_user_content_manager_add_style_sheet (ucm, style_sheet);
 }
 
+void
+ephy_embed_prefs_apply_user_javascript (WebKitUserContentManager *ucm)
+{
+  if (javascript)
+    webkit_user_content_manager_add_script (ucm, javascript);
+}
+
 void
 ephy_embed_prefs_register_ucm (WebKitUserContentManager *ucm)
 {
diff --git a/embed/ephy-embed-prefs.h b/embed/ephy-embed-prefs.h
index 50f063aad..e3a11dc47 100644
--- a/embed/ephy-embed-prefs.h
+++ b/embed/ephy-embed-prefs.h
@@ -26,6 +26,7 @@
 #include <webkit2/webkit2.h>
 
 #define USER_STYLESHEET_FILENAME       "user-stylesheet.css"
+#define USER_JAVASCRIPT_FILENAME       "user-javascript.js"
 #define FAVICON_SIZE 16
 
 G_BEGIN_DECLS
@@ -34,6 +35,7 @@ WebKitSettings *ephy_embed_prefs_get_settings  (void);
 void ephy_embed_prefs_set_cookie_accept_policy (WebKitCookieManager      *cookie_manager,
                                                 const char               *settings_policy);
 void ephy_embed_prefs_apply_user_style         (WebKitUserContentManager *ucm);
+void ephy_embed_prefs_apply_user_javascript    (WebKitUserContentManager *ucm);
 
 void ephy_embed_prefs_register_ucm             (WebKitUserContentManager *ucm);
 void ephy_embed_prefs_unregister_ucm           (WebKitUserContentManager *ucm);
diff --git a/embed/ephy-embed-shell.c b/embed/ephy-embed-shell.c
index 191b11b62..c7444639d 100644
--- a/embed/ephy-embed-shell.c
+++ b/embed/ephy-embed-shell.c
@@ -1373,6 +1373,7 @@ ephy_embed_shell_register_ucm_handler (EphyEmbedShell           *shell,
 
   /* User Scripts */
   ephy_embed_prefs_apply_user_style (ucm);
+  ephy_embed_prefs_apply_user_javascript (ucm);
 }
 
 void
diff --git a/lib/ephy-prefs.h b/lib/ephy-prefs.h
index 714c2f78c..4f25709b4 100644
--- a/lib/ephy-prefs.h
+++ b/lib/ephy-prefs.h
@@ -104,6 +104,7 @@ static const char * const ephy_prefs_state_schema[] = {
 #define EPHY_PREFS_WEB_SERIF_FONT                   "serif-font"
 #define EPHY_PREFS_WEB_MONOSPACE_FONT               "monospace-font"
 #define EPHY_PREFS_WEB_ENABLE_USER_CSS              "enable-user-css"
+#define EPHY_PREFS_WEB_ENABLE_USER_JS               "enable-user-js"
 #define EPHY_PREFS_WEB_ENABLE_POPUPS                "enable-popups"
 #define EPHY_PREFS_WEB_ENABLE_SPELL_CHECKING        "enable-spell-checking"
 #define EPHY_PREFS_WEB_ENABLE_SMOOTH_SCROLLING      "enable-smooth-scrolling"
@@ -131,6 +132,7 @@ static const char * const ephy_prefs_web_schema[] = {
   EPHY_PREFS_WEB_SERIF_FONT,
   EPHY_PREFS_WEB_MONOSPACE_FONT,
   EPHY_PREFS_WEB_ENABLE_USER_CSS,
+  EPHY_PREFS_WEB_ENABLE_USER_JS,
   EPHY_PREFS_WEB_ENABLE_POPUPS,
   EPHY_PREFS_WEB_ENABLE_SPELL_CHECKING,
   EPHY_PREFS_WEB_ENABLE_SMOOTH_SCROLLING,
diff --git a/src/prefs-dialog.c b/src/prefs-dialog.c
index fa2dc6d03..c8675e703 100644
--- a/src/prefs-dialog.c
+++ b/src/prefs-dialog.c
@@ -109,6 +109,8 @@ struct _PrefsDialog {
   GtkWidget *mono_fontbutton;
   GtkWidget *css_switch;
   GtkWidget *css_edit_button;
+  GtkWidget *js_switch;
+  GtkWidget *js_edit_button;
   GtkWidget *default_zoom_spin_button;
   GtkWidget *reader_mode_box;
   GtkWidget *reader_mode_font_style;
@@ -983,6 +985,8 @@ prefs_dialog_class_init (PrefsDialogClass *klass)
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, mono_fontbutton);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, css_switch);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, css_edit_button);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, js_switch);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, js_edit_button);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, default_zoom_spin_button);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, reader_mode_box);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, reader_mode_font_style);
@@ -1090,6 +1094,52 @@ css_edit_button_clicked_cb (GtkWidget   *button,
   g_file_create_async (css_file, G_FILE_CREATE_NONE, G_PRIORITY_DEFAULT, NULL, css_file_created_cb, NULL);
 }
 
+static void
+js_file_opened_cb (GObject      *source,
+                   GAsyncResult *result,
+                   gpointer      user_data)
+{
+  gboolean ret;
+  g_autoptr (GError) error = NULL;
+
+  ret = ephy_open_file_via_flatpak_portal_finish (result, &error);
+  if (!ret && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    g_warning ("Failed to open JS file: %s", error->message);
+}
+
+static void
+js_file_created_cb (GObject      *source,
+                    GAsyncResult *result,
+                    gpointer      user_data)
+{
+  g_autoptr (GFile) file = G_FILE (source);
+  g_autoptr (GFileOutputStream) stream = NULL;
+  g_autoptr (GError) error = NULL;
+
+  stream = g_file_create_finish (file, result, &error);
+  if (stream == NULL && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+    g_warning ("Failed to create %s: %s", g_file_get_path (file), error->message);
+  else {
+    if (ephy_is_running_inside_flatpak ())
+      ephy_open_file_via_flatpak_portal (g_file_get_path (file), NULL, js_file_opened_cb, NULL);
+    else
+      ephy_file_launch_handler (file, gtk_get_current_event_time ());
+  }
+}
+
+static void
+js_edit_button_clicked_cb (GtkWidget   *button,
+                           PrefsDialog *pd)
+{
+  GFile *js_file;
+
+  js_file = g_file_new_for_path (g_build_filename (ephy_profile_dir (),
+                                                   USER_JAVASCRIPT_FILENAME,
+                                                   NULL));
+
+  g_file_create_async (g_steal_pointer (&js_file), G_FILE_CREATE_NONE, G_PRIORITY_DEFAULT, NULL, 
js_file_created_cb, NULL);
+}
+
 static GtkTargetEntry entries[] = {
   { "GTK_LIST_BOX_ROW", GTK_TARGET_SAME_APP, 0 }
 };
@@ -2355,6 +2405,22 @@ setup_fonts_page (PrefsDialog *dialog)
                     G_CALLBACK (css_edit_button_clicked_cb),
                     dialog);
 
+  g_settings_bind (web_settings,
+                   EPHY_PREFS_WEB_ENABLE_USER_JS,
+                   dialog->js_switch,
+                   "active",
+                   G_SETTINGS_BIND_DEFAULT);
+  g_settings_bind (web_settings,
+                   EPHY_PREFS_WEB_ENABLE_USER_JS,
+                   dialog->js_edit_button,
+                   "sensitive",
+                   G_SETTINGS_BIND_GET);
+
+  g_signal_connect (dialog->js_edit_button,
+                    "clicked",
+                    G_CALLBACK (js_edit_button_clicked_cb),
+                    dialog);
+
   gtk_spin_button_set_value (GTK_SPIN_BUTTON (dialog->default_zoom_spin_button),
                              g_settings_get_double (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_DEFAULT_ZOOM_LEVEL) * 
100);
 }
diff --git a/src/resources/gtk/prefs-dialog.ui b/src/resources/gtk/prefs-dialog.ui
index 673a1aeed..fbfeea744 100644
--- a/src/resources/gtk/prefs-dialog.ui
+++ b/src/resources/gtk/prefs-dialog.ui
@@ -732,6 +732,41 @@
                         </child>
                       </object>
                     </child>
+                    <child>
+                      <object class="HdyActionRow">
+                        <property name="activatable_widget">js_switch</property>
+                        <property name="title" translatable="yes">Use Custom JavaScript</property>
+                        <property name="visible">True</property>
+                        <child type="action">
+                          <object class="GtkButton" id="js_edit_button">
+                            <property name="valign">center</property>
+                            <property name="visible">True</property>
+                            <style>
+                              <class name="image-button"/>
+                            </style>
+                            <child>
+                              <object class="GtkImage">
+                                <property name="icon_name">document-edit-symbolic</property>
+                                <property name="visible">True</property>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                        <child type="action">
+                          <object class="GtkSeparator">
+                            <property name="margin_bottom">8</property>
+                            <property name="margin_top">8</property>
+                            <property name="visible">True</property>
+                          </object>
+                        </child>
+                        <child type="action">
+                          <object class="GtkSwitch" id="js_switch">
+                            <property name="valign">center</property>
+                            <property name="visible">True</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
                     <child>
                       <object class="HdyActionRow">
                         <property name="activatable">False</property>


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