[epiphany] Add URI handler for view source mode



commit 2b7dc7df660081c5eb4fae9b8ccda23deff82550
Author: Michael Catanzaro <mcatanzaro gnome org>
Date:   Sun Sep 11 15:03:13 2016 -0500

    Add URI handler for view source mode
    
    This brings back the view source handler that lived in master in late 2016
    and early 2017, so we don't have to open page source in gedit anymore.
    
    It's not perfect, in that it could get the source code from the wrong
    tab if two different tabs are displaying the same URI, but let not
    perfect be the enemy of the good. Content will be fetched from the
    network if no tab is displaying the desired URI.
    
    No syntax highlighting because I never found a library that handles this
    efficiently enough for our needs.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=738475

 embed/ephy-embed-shell.c         |  18 +++
 embed/ephy-embed-utils.c         |   7 +-
 embed/ephy-view-source-handler.c | 327 +++++++++++++++++++++++++++++++++++++++
 embed/ephy-view-source-handler.h |  37 +++++
 embed/ephy-web-view.c            |   2 +
 embed/meson.build                |   1 +
 po/POTFILES.in                   |   1 +
 src/window-commands.c            |  32 ++--
 8 files changed, 414 insertions(+), 11 deletions(-)
---
diff --git a/embed/ephy-embed-shell.c b/embed/ephy-embed-shell.c
index cc2336611..818178b27 100644
--- a/embed/ephy-embed-shell.c
+++ b/embed/ephy-embed-shell.c
@@ -39,6 +39,7 @@
 #include "ephy-snapshot-service.h"
 #include "ephy-tabs-catalog.h"
 #include "ephy-uri-tester-shared.h"
+#include "ephy-view-source-handler.h"
 #include "ephy-web-app-utils.h"
 #include "ephy-web-extension-proxy.h"
 
@@ -61,6 +62,7 @@ typedef struct {
   EphyDownloadsManager *downloads_manager;
   EphyPermissionsManager *permissions_manager;
   EphyAboutHandler *about_handler;
+  EphyViewSourceHandler *source_handler;
   GDBusServer *dbus_server;
   GList *web_extensions;
   EphyFiltersManager *filters_manager;
@@ -160,6 +162,7 @@ ephy_embed_shell_dispose (GObject *object)
   g_clear_object (&priv->global_history_service);
   g_clear_object (&priv->global_gsb_service);
   g_clear_object (&priv->about_handler);
+  g_clear_object (&priv->source_handler);
   g_clear_object (&priv->user_content);
   g_clear_object (&priv->downloads_manager);
   g_clear_object (&priv->permissions_manager);
@@ -602,6 +605,15 @@ about_request_cb (WebKitURISchemeRequest *request,
   ephy_about_handler_handle_request (priv->about_handler, request);
 }
 
+static void
+source_request_cb (WebKitURISchemeRequest *request,
+                   EphyEmbedShell         *shell)
+{
+  EphyEmbedShellPrivate *priv = ephy_embed_shell_get_instance_private (shell);
+
+  ephy_view_source_handler_handle_request (priv->source_handler, request);
+}
+
 static void
 ephy_resource_request_cb (WebKitURISchemeRequest *request)
 {
@@ -970,6 +982,12 @@ ephy_embed_shell_startup (GApplication *application)
   webkit_security_manager_register_uri_scheme_as_local (webkit_web_context_get_security_manager 
(priv->web_context),
                                                         EPHY_ABOUT_SCHEME);
 
+  /* view source handler */
+  priv->source_handler = ephy_view_source_handler_new ();
+  webkit_web_context_register_uri_scheme (priv->web_context, EPHY_VIEW_SOURCE_SCHEME,
+                                          (WebKitURISchemeRequestCallback)source_request_cb,
+                                          shell, NULL);
+
   /* ephy-resource handler */
   webkit_web_context_register_uri_scheme (priv->web_context, "ephy-resource",
                                           (WebKitURISchemeRequestCallback)ephy_resource_request_cb,
diff --git a/embed/ephy-embed-utils.c b/embed/ephy-embed-utils.c
index 2399ef850..29f6634cc 100644
--- a/embed/ephy-embed-utils.c
+++ b/embed/ephy-embed-utils.c
@@ -28,6 +28,7 @@
 #include "ephy-prefs.h"
 #include "ephy-settings.h"
 #include "ephy-string.h"
+#include "ephy-view-source-handler.h"
 
 #include <glib/gi18n.h>
 #include <jsc/jsc.h>
@@ -116,7 +117,7 @@ ephy_embed_utils_address_has_web_scheme (const char *address)
   if (address == NULL)
     return FALSE;
 
-  colonpos = (int)((g_strstr_len (address, 11, ":")) - address);
+  colonpos = (int)((g_strstr_len (address, 12, ":")) - address);
 
   if (colonpos < 0)
     return FALSE;
@@ -130,6 +131,7 @@ ephy_embed_utils_address_has_web_scheme (const char *address)
                      g_ascii_strncasecmp (address, "blob", colonpos) &&
                      g_ascii_strncasecmp (address, "about", colonpos) &&
                      g_ascii_strncasecmp (address, "ephy-about", colonpos) &&
+                     g_ascii_strncasecmp (address, "ephy-source", colonpos) &&
                      g_ascii_strncasecmp (address, "gopher", colonpos) &&
                      g_ascii_strncasecmp (address, "inspector", colonpos));
 
@@ -336,6 +338,9 @@ ephy_embed_utils_is_no_show_address (const char *address)
     if (!strcmp (address, do_not_show_address[i]))
       return TRUE;
 
+  if (strstr (address, EPHY_VIEW_SOURCE_SCHEME) == address)
+    return TRUE;
+
   return FALSE;
 }
 
diff --git a/embed/ephy-view-source-handler.c b/embed/ephy-view-source-handler.c
new file mode 100644
index 000000000..f88b53d25
--- /dev/null
+++ b/embed/ephy-view-source-handler.c
@@ -0,0 +1,327 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2016 Igalia S.L.
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-view-source-handler.h"
+
+#include "ephy-embed-container.h"
+#include "ephy-embed-shell.h"
+#include "ephy-web-view.h"
+
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <string.h>
+
+struct _EphyViewSourceHandler {
+  GObject parent_instance;
+
+  GList *outstanding_requests;
+};
+
+G_DEFINE_TYPE (EphyViewSourceHandler, ephy_view_source_handler, G_TYPE_OBJECT)
+
+typedef struct {
+  EphyViewSourceHandler *source_handler;
+  WebKitURISchemeRequest *scheme_request;
+  WebKitWebView *web_view;
+  GCancellable *cancellable;
+  guint load_changed_id;
+} EphyViewSourceRequest;
+
+static EphyViewSourceRequest *
+ephy_view_source_request_new (EphyViewSourceHandler  *handler,
+                              WebKitURISchemeRequest *request)
+{
+  EphyViewSourceRequest *view_source_request;
+
+  view_source_request = g_slice_new (EphyViewSourceRequest);
+  view_source_request->source_handler = g_object_ref (handler);
+  view_source_request->scheme_request = g_object_ref (request);
+  view_source_request->web_view = NULL; /* created only if required */
+  view_source_request->cancellable = g_cancellable_new ();
+  view_source_request->load_changed_id = 0;
+
+  return view_source_request;
+}
+
+static void
+ephy_view_source_request_free (EphyViewSourceRequest *request)
+{
+  if (request->load_changed_id > 0)
+    g_signal_handler_disconnect (request->web_view, request->load_changed_id);
+
+  g_object_unref (request->source_handler);
+  g_object_unref (request->scheme_request);
+  g_clear_object (&request->web_view);
+
+  g_cancellable_cancel (request->cancellable);
+  g_object_unref (request->cancellable);
+
+  g_slice_free (EphyViewSourceRequest, request);
+}
+
+static void
+finish_uri_scheme_request (EphyViewSourceRequest *request,
+                           gchar                 *data,
+                           GError                *error)
+{
+  GInputStream *stream;
+  gssize data_length;
+
+  g_assert ((data && !error) || (!data && error));
+
+  if (error) {
+    webkit_uri_scheme_request_finish_error (request->scheme_request, error);
+  } else {
+    data_length = MIN (strlen (data), G_MAXSSIZE);
+    stream = g_memory_input_stream_new_from_data (data, data_length, g_free);
+    webkit_uri_scheme_request_finish (request->scheme_request, stream, data_length, "text/html");
+    g_object_unref (stream);
+  }
+
+  request->source_handler->outstanding_requests =
+      g_list_remove (request->source_handler->outstanding_requests,
+                     request);
+
+  ephy_view_source_request_free (request);
+}
+
+static void
+web_resource_data_cb (WebKitWebResource     *resource,
+                      GAsyncResult          *result,
+                      EphyViewSourceRequest *request)
+{
+  guchar *data;
+  char *data_str;
+  char *escaped_str;
+  char *html;
+  gsize length;
+  GError *error = NULL;
+
+  data = webkit_web_resource_get_data_finish (resource, result, &length, &error);
+  if (error) {
+    finish_uri_scheme_request (request, NULL, error);
+    g_error_free (error);
+    return;
+  }
+
+  data_str = g_malloc (length + 1);
+  strncpy (data_str, (const char *)data, length);
+  data_str[length] = '\0';
+  g_free (data);
+
+  escaped_str = g_markup_escape_text (data_str, -1);
+  g_free (data_str);
+
+  html = g_strdup_printf ("<body>"
+                            "<pre>"
+                              "<code class=\"language-html\">%s</code>"
+                            "</pre>"
+                          "</body>",
+                          escaped_str);
+  g_free (escaped_str);
+
+  finish_uri_scheme_request (request, html, NULL);
+}
+
+static void
+ephy_view_source_request_begin_get_source_from_web_view (EphyViewSourceRequest *request,
+                                                         WebKitWebView         *web_view)
+{
+  WebKitWebResource *resource = webkit_web_view_get_main_resource (web_view);
+  g_assert (resource);
+  webkit_web_resource_get_data (resource,
+                                request->cancellable,
+                                (GAsyncReadyCallback)(web_resource_data_cb),
+                                request);
+}
+
+static void
+load_changed_cb (WebKitWebView         *web_view,
+                 WebKitLoadEvent        load_event,
+                 EphyViewSourceRequest *request)
+{
+  if (load_event == WEBKIT_LOAD_FINISHED)
+    ephy_view_source_request_begin_get_source_from_web_view (request, web_view);
+}
+
+static void
+ephy_view_source_request_begin_get_source_from_uri (EphyViewSourceRequest *request,
+                                                    const char            *uri)
+{
+  EphyEmbedShell *shell = ephy_embed_shell_get_default ();
+  WebKitWebContext *context = ephy_embed_shell_get_web_context (shell);
+
+  request->web_view = WEBKIT_WEB_VIEW (g_object_ref_sink (webkit_web_view_new_with_context (context)));
+
+  g_assert (request->load_changed_id == 0);
+  request->load_changed_id = g_signal_connect (request->web_view, "load-changed",
+                                               G_CALLBACK (load_changed_cb),
+                                               request);
+
+  webkit_web_view_load_uri (request->web_view, uri);
+}
+
+static gint
+embed_is_displaying_matching_uri (EphyEmbed *embed,
+                                  SoupURI   *uri)
+{
+  EphyWebView *web_view;
+  SoupURI *view_uri;
+  gint ret = -1;
+
+  if (ephy_embed_has_load_pending (embed))
+    return -1;
+
+  web_view = ephy_embed_get_web_view (embed);
+  if (ephy_web_view_is_loading (web_view))
+    return -1;
+
+  view_uri = soup_uri_new (ephy_web_view_get_address (web_view));
+  if (!view_uri)
+    return -1;
+
+  soup_uri_set_fragment (view_uri, NULL);
+  ret = soup_uri_equal (view_uri, uri) ? 0 : -1;
+
+  soup_uri_free (view_uri);
+
+  return ret;
+}
+
+static WebKitWebView *
+get_web_view_matching_uri (SoupURI *uri)
+{
+  EphyEmbedShell *shell;
+  GtkWindow *window;
+  GList *embeds = NULL;
+  GList *found;
+  EphyEmbed *embed = NULL;
+
+  shell = ephy_embed_shell_get_default ();
+  window = gtk_application_get_active_window (GTK_APPLICATION (shell));
+
+  if (!EPHY_IS_EMBED_CONTAINER (window))
+    goto out;
+
+  embeds = ephy_embed_container_get_children (EPHY_EMBED_CONTAINER (window));
+  found = g_list_find_custom (embeds, uri, (GCompareFunc)embed_is_displaying_matching_uri);
+
+  if (found)
+    embed = found->data;
+
+out:
+  g_list_free (embeds);
+
+  return embed ? WEBKIT_WEB_VIEW (ephy_embed_get_web_view (embed)) : NULL;
+}
+
+static void
+ephy_view_source_request_start (EphyViewSourceRequest *request)
+{
+  SoupURI *soup_uri;
+  char *modified_uri;
+  char *decoded_fragment;
+  const char *original_uri;
+  WebKitWebView *web_view;
+
+  request->source_handler->outstanding_requests =
+      g_list_prepend (request->source_handler->outstanding_requests, request);
+
+  original_uri = webkit_uri_scheme_request_get_uri (request->scheme_request);
+  soup_uri = soup_uri_new (original_uri);
+
+  if (!soup_uri || !soup_uri->fragment) {
+    /* Can't assert because user could theoretically input something weird */
+    GError *error = g_error_new (WEBKIT_NETWORK_ERROR,
+                                 WEBKIT_NETWORK_ERROR_FAILED,
+                                 _("%s is not a valid URI"),
+                                 original_uri);
+    finish_uri_scheme_request (request, NULL, error);
+    g_error_free (error);
+    return;
+  }
+
+  /* Convert e.g. ephy-source://gnome.org#https to https://gnome.org */
+  decoded_fragment = soup_uri_decode (soup_uri->fragment);
+  soup_uri_set_scheme (soup_uri, decoded_fragment);
+  soup_uri_set_fragment (soup_uri, NULL);
+  modified_uri = soup_uri_to_string (soup_uri, FALSE);
+  g_assert (modified_uri);
+
+  web_view = get_web_view_matching_uri (soup_uri);
+  if (web_view)
+    ephy_view_source_request_begin_get_source_from_web_view (request, WEBKIT_WEB_VIEW (web_view));
+  else
+    ephy_view_source_request_begin_get_source_from_uri (request, modified_uri);
+
+  g_free (decoded_fragment);
+  g_free (modified_uri);
+  soup_uri_free (soup_uri);
+}
+
+static void
+cancel_outstanding_request (EphyViewSourceRequest *request)
+{
+  g_cancellable_cancel (request->cancellable);
+}
+
+static void
+ephy_view_source_handler_dispose (GObject *object)
+{
+  EphyViewSourceHandler *handler = EPHY_VIEW_SOURCE_HANDLER (object);
+
+  if (handler->outstanding_requests) {
+    g_list_foreach (handler->outstanding_requests, (GFunc)cancel_outstanding_request, NULL);
+    g_list_free (handler->outstanding_requests);
+    handler->outstanding_requests = NULL;
+  }
+
+  G_OBJECT_CLASS (ephy_view_source_handler_parent_class)->dispose (object);
+}
+
+static void
+ephy_view_source_handler_init (EphyViewSourceHandler *handler)
+{
+}
+
+static void
+ephy_view_source_handler_class_init (EphyViewSourceHandlerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = ephy_view_source_handler_dispose;
+}
+
+EphyViewSourceHandler *
+ephy_view_source_handler_new (void)
+{
+  return EPHY_VIEW_SOURCE_HANDLER (g_object_new (EPHY_TYPE_VIEW_SOURCE_HANDLER, NULL));
+}
+
+void
+ephy_view_source_handler_handle_request (EphyViewSourceHandler  *handler,
+                                         WebKitURISchemeRequest *scheme_request)
+{
+  EphyViewSourceRequest *view_source_request;
+
+  view_source_request = ephy_view_source_request_new (handler, scheme_request);
+  ephy_view_source_request_start (view_source_request);
+}
diff --git a/embed/ephy-view-source-handler.h b/embed/ephy-view-source-handler.h
new file mode 100644
index 000000000..9c268a391
--- /dev/null
+++ b/embed/ephy-view-source-handler.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2016 Igalia S.L.
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <webkit2/webkit2.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_VIEW_SOURCE_HANDLER (ephy_view_source_handler_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphyViewSourceHandler, ephy_view_source_handler, EPHY, VIEW_SOURCE_HANDLER, GObject)
+
+#define EPHY_VIEW_SOURCE_SCHEME "ephy-source"
+
+EphyViewSourceHandler *ephy_view_source_handler_new            (void);
+
+void                   ephy_view_source_handler_handle_request (EphyViewSourceHandler  *handler,
+                                                                WebKitURISchemeRequest *request);
+G_END_DECLS
diff --git a/embed/ephy-web-view.c b/embed/ephy-web-view.c
index 63695934b..e3e1342ed 100644
--- a/embed/ephy-web-view.c
+++ b/embed/ephy-web-view.c
@@ -43,6 +43,7 @@
 #include "ephy-snapshot-service.h"
 #include "ephy-string.h"
 #include "ephy-uri-helpers.h"
+#include "ephy-view-source-handler.h"
 #include "ephy-web-app-utils.h"
 #include "ephy-web-extension-proxy.h"
 #include "ephy-zoom.h"
@@ -1743,6 +1744,7 @@ update_security_status_for_committed_load (EphyWebView *view,
   g_clear_pointer (&view->tls_error_failing_uri, g_free);
 
   if (!soup_uri ||
+      strcmp (soup_uri_get_scheme (soup_uri), EPHY_VIEW_SOURCE_SCHEME) == 0 ||
       webkit_security_manager_uri_scheme_is_local (security_manager, soup_uri->scheme) ||
       webkit_security_manager_uri_scheme_is_empty_document (security_manager, soup_uri->scheme)) {
     security_level = EPHY_SECURITY_LEVEL_LOCAL_PAGE;
diff --git a/embed/meson.build b/embed/meson.build
index 26ce064a4..7dbfcefab 100644
--- a/embed/meson.build
+++ b/embed/meson.build
@@ -26,6 +26,7 @@ libephyembed_sources = [
   'ephy-filters-manager.c',
   'ephy-find-toolbar.c',
   'ephy-option-menu.c',
+  'ephy-view-source-handler.c',
   'ephy-web-view.c',
   'ephy-web-extension-proxy.c',
   enums
diff --git a/po/POTFILES.in b/po/POTFILES.in
index cc4d9e71c..1ee501cff 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -11,6 +11,7 @@ embed/ephy-embed-utils.c
 embed/ephy-embed-utils.h
 embed/ephy-encodings.c
 embed/ephy-find-toolbar.c
+embed/ephy-view-source-handler.c
 embed/ephy-web-view.c
 embed/web-extension/resources/js/overview.js
 lib/ephy-file-helpers.c
diff --git a/src/window-commands.c b/src/window-commands.c
index 9452f44c0..0c53f9b03 100644
--- a/src/window-commands.c
+++ b/src/window-commands.c
@@ -53,6 +53,7 @@
 #include "ephy-shell.h"
 #include "ephy-string.h"
 #include "ephy-vcs-version.h"
+#include "ephy-view-source-handler.h"
 #include "ephy-web-app-utils.h"
 #include "ephy-zoom.h"
 
@@ -1798,6 +1799,23 @@ static void
 view_source_embedded (const char *uri, EphyEmbed *embed)
 {
   EphyEmbed *new_embed;
+  SoupURI *soup_uri;
+  char *source_uri;
+
+  /* Abort if we're already in view source mode */
+  if (strstr (uri, EPHY_VIEW_SOURCE_SCHEME) == uri)
+    return;
+
+  soup_uri = soup_uri_new (uri);
+  if (!soup_uri) {
+    g_critical ("Failed to construct SoupURI for %s", uri);
+    return;
+  }
+
+  /* Convert e.g. https://gnome.org to ephy-source://gnome.org#https */
+  soup_uri_set_fragment (soup_uri, soup_uri->scheme);
+  soup_uri_set_scheme (soup_uri, EPHY_VIEW_SOURCE_SCHEME);
+  source_uri = soup_uri_to_string (soup_uri, FALSE);
 
   new_embed = ephy_shell_new_tab
                 (ephy_shell_get_default (),
@@ -1805,13 +1823,11 @@ view_source_embedded (const char *uri, EphyEmbed *embed)
                 embed,
                 EPHY_NEW_TAB_JUMP | EPHY_NEW_TAB_APPEND_AFTER);
 
-  /* FIXME: Implement embedded view source mode using a custom URI handler and a
-   * javascript library for the syntax highlighting.
-   * https://bugzilla.gnome.org/show_bug.cgi?id=731558
-   */
-  webkit_web_view_load_uri
-    (EPHY_GET_WEBKIT_WEB_VIEW_FROM_EMBED (new_embed), uri);
+  webkit_web_view_load_uri (EPHY_GET_WEBKIT_WEB_VIEW_FROM_EMBED (new_embed), source_uri);
   gtk_widget_grab_focus (GTK_WIDGET (new_embed));
+
+  g_free (source_uri);
+  soup_uri_free (soup_uri);
 }
 
 static void
@@ -2065,15 +2081,11 @@ window_cmd_page_source (GSimpleAction *action,
 
   address = ephy_web_view_get_address (ephy_embed_get_web_view (embed));
 
-#if 0
- FIXME: Disabled due to bug #738475
-
   if (g_settings_get_boolean (EPHY_SETTINGS_MAIN,
                               EPHY_PREFS_INTERNAL_VIEW_SOURCE)) {
     view_source_embedded (address, embed);
     return;
   }
-#endif
 
   user_time = gtk_get_current_event_time ();
 


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