[gnome-builder] libide-webkit: add libide-webkit library
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] libide-webkit: add libide-webkit library
- Date: Tue, 12 Jul 2022 06:39:10 +0000 (UTC)
commit f2aacc1b8689bf7fe96cf809fc74d66b8e126288
Author: Christian Hergert <chergert redhat com>
Date: Mon Jul 11 17:54:34 2022 -0700
libide-webkit: add libide-webkit library
This provides knowledge that webkit is available and a page which can be
used to display web content in a new frame.
src/libide/webkit/ide-html-generator.c | 297 +++++++++
src/libide/webkit/ide-html-generator.h | 67 ++
src/libide/webkit/ide-text-buffer-html-generator.c | 205 ++++++
src/libide/webkit/ide-text-buffer-html-generator.h | 31 +
src/libide/webkit/ide-url-bar.c | 453 +++++++++++++
src/libide/webkit/ide-url-bar.h | 37 ++
src/libide/webkit/ide-url-bar.ui | 94 +++
src/libide/webkit/ide-webkit-page.c | 708 +++++++++++++++++++++
src/libide/webkit/ide-webkit-page.h | 62 ++
src/libide/webkit/ide-webkit-page.ui | 103 +++
src/libide/webkit/ide-webkit-plugin.c | 11 +-
src/libide/webkit/ide-webkit-util.c | 280 ++++++++
src/libide/webkit/ide-webkit-util.h | 39 ++
src/libide/webkit/libide-webkit.gresource.xml | 2 +
src/libide/webkit/libide-webkit.h | 27 +
src/libide/webkit/meson.build | 41 +-
16 files changed, 2454 insertions(+), 3 deletions(-)
---
diff --git a/src/libide/webkit/ide-html-generator.c b/src/libide/webkit/ide-html-generator.c
new file mode 100644
index 000000000..7e9485346
--- /dev/null
+++ b/src/libide/webkit/ide-html-generator.c
@@ -0,0 +1,297 @@
+/* ide-html-generator.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 of the License, 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-html-generator"
+
+#include "config.h"
+
+#include <libide-threading.h>
+
+#include "ide-html-generator.h"
+#include "ide-text-buffer-html-generator.h"
+
+typedef struct
+{
+ char *base_uri;
+} IdeHtmlGeneratorPrivate;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (IdeHtmlGenerator, ide_html_generator, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_BASE_URI,
+ N_PROPS
+};
+
+enum {
+ INVALIDATE,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+static GParamSpec *properties[N_PROPS];
+
+static void
+ide_html_generator_real_generate_async (IdeHtmlGenerator *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(IdeTask) task = NULL;
+
+ task = ide_task_new (self, cancellable, callback, user_data);
+ ide_task_set_source_tag (task, ide_html_generator_real_generate_async);
+ ide_task_return_unsupported_error (task);
+}
+
+static GBytes *
+ide_html_generator_real_generate_finish (IdeHtmlGenerator *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ return ide_task_propagate_pointer (IDE_TASK (result), error);
+}
+
+static void
+ide_html_generator_dispose (GObject *object)
+{
+ IdeHtmlGenerator *self = (IdeHtmlGenerator *)object;
+ IdeHtmlGeneratorPrivate *priv = ide_html_generator_get_instance_private (self);
+
+ g_clear_pointer (&priv->base_uri, g_free);
+
+ G_OBJECT_CLASS (ide_html_generator_parent_class)->dispose (object);
+}
+
+static void
+ide_html_generator_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeHtmlGenerator *self = IDE_HTML_GENERATOR (object);
+
+ switch (prop_id)
+ {
+ case PROP_BASE_URI:
+ g_value_set_string (value, ide_html_generator_get_base_uri (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_html_generator_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeHtmlGenerator *self = IDE_HTML_GENERATOR (object);
+
+ switch (prop_id)
+ {
+ case PROP_BASE_URI:
+ ide_html_generator_set_base_uri (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_html_generator_class_init (IdeHtmlGeneratorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = ide_html_generator_dispose;
+ object_class->get_property = ide_html_generator_get_property;
+ object_class->set_property = ide_html_generator_set_property;
+
+ klass->generate_async = ide_html_generator_real_generate_async;
+ klass->generate_finish = ide_html_generator_real_generate_finish;
+
+ properties [PROP_BASE_URI] =
+ g_param_spec_string ("base-uri", NULL, NULL, NULL,
+ (G_PARAM_READWRITE |
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ /**
+ * IdeHtmlGenerator::invalidate:
+ *
+ * The "invalidate" signal is emitted when contents have changed.
+ *
+ * This signal will be emitted by subclasses when the contents have changed
+ * and HTML will need to be regenerated.
+ */
+ signals [INVALIDATE] =
+ g_signal_new ("invalidate",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IdeHtmlGeneratorClass, invalidate),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+ide_html_generator_init (IdeHtmlGenerator *self)
+{
+}
+
+/**
+ * ide_html_generator_generate_async:
+ * @self: a #IdeHtmlGenerator
+ * @cancellable: a #GCancellable
+ * @callback: a function to call after completion
+ * @user_data: closure data for @callback
+ *
+ * Asynchronously generate HTML.
+ *
+ * This virtual function should be implemented by subclasses to generate
+ * HTML based on some form of input (which is left to the subclass).
+ *
+ * Upon completion, @callback is called and expected to call
+ * ide_html_generator_generate_finish() to retrieve the result.
+ */
+void
+ide_html_generator_generate_async (IdeHtmlGenerator *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (IDE_IS_HTML_GENERATOR (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ IDE_HTML_GENERATOR_GET_CLASS (self)->generate_async (self, cancellable, callback, user_data);
+}
+
+/**
+ * ide_html_generator_generate_finish:
+ * @self: a #IdeHtmlGenerator
+ * @result: a #GAsyncResult
+ * @error: a location for a #GError
+ *
+ * Completes a request to generate HTML.
+ *
+ * This function is used to complete a request to generate HTML from some
+ * form of input, asynchronously. The content of the HTML is dependent on
+ * the subclass implementation of #IdeHtmlGenerator.
+ *
+ * It is required that the resulting bytes have a NULL terminator at
+ * the end which is not part of the bytes length.
+ *
+ * Returns: (transfer full): a #GBytes if successful; otherwise %NULL
+ * and @error is set.
+ */
+GBytes *
+ide_html_generator_generate_finish (IdeHtmlGenerator *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ GBytes *ret;
+
+ g_return_val_if_fail (IDE_IS_HTML_GENERATOR (self), NULL);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+
+ ret = IDE_HTML_GENERATOR_GET_CLASS (self)->generate_finish (self, result, error);
+
+#ifdef G_ENABLE_DEBUG
+ if (ret != NULL)
+ {
+ const guint8 *data;
+ gsize len;
+
+ data = g_bytes_get_data (ret, &len);
+ g_assert (data[len] == 0);
+ }
+#endif
+
+ return ret;
+}
+
+/**
+ * ide_html_generator_invalidate:
+ * @self: a #IdeHtmlGenerator
+ *
+ * Notifies that the last generated HTML is now invalid.
+ *
+ * This is used by subclasses to denote that the HTML contents
+ * have changed and will need to be regenerated.
+ */
+void
+ide_html_generator_invalidate (IdeHtmlGenerator *self)
+{
+ g_return_if_fail (IDE_IS_HTML_GENERATOR (self));
+
+ g_signal_emit (self, signals [INVALIDATE], 0);
+}
+
+/**
+ * ide_html_generator_new_for_buffer:
+ * @buffer: a #GtkTextBuffer
+ *
+ * Create a 1:1 HTML generator for a buffer.
+ *
+ * Creates a #IdeHtmlGenerator that passes the content directly from
+ * what is found in a #GtkTextBuffer.
+ *
+ * Returns: (transfer full): an #IdeHtmlGenerator
+ */
+IdeHtmlGenerator *
+ide_html_generator_new_for_buffer (GtkTextBuffer *buffer)
+{
+ g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+
+ return g_object_new (IDE_TYPE_TEXT_BUFFER_HTML_GENERATOR,
+ "buffer", buffer,
+ NULL);
+}
+
+const char *
+ide_html_generator_get_base_uri (IdeHtmlGenerator *self)
+{
+ IdeHtmlGeneratorPrivate *priv = ide_html_generator_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_HTML_GENERATOR (self), NULL);
+
+ return priv->base_uri;
+}
+
+void
+ide_html_generator_set_base_uri (IdeHtmlGenerator *self,
+ const char *base_uri)
+{
+ IdeHtmlGeneratorPrivate *priv = ide_html_generator_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_HTML_GENERATOR (self));
+
+ if (g_strcmp0 (priv->base_uri, base_uri) != 0)
+ {
+ g_free (priv->base_uri);
+ priv->base_uri = g_strdup (base_uri);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BASE_URI]);
+ }
+}
diff --git a/src/libide/webkit/ide-html-generator.h b/src/libide/webkit/ide-html-generator.h
new file mode 100644
index 000000000..aef2ec92d
--- /dev/null
+++ b/src/libide/webkit/ide-html-generator.h
@@ -0,0 +1,67 @@
+/* ide-html-generator.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 of the License, 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-core.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_HTML_GENERATOR (ide_html_generator_get_type())
+
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_DERIVABLE_TYPE (IdeHtmlGenerator, ide_html_generator, IDE, HTML_GENERATOR, GObject)
+
+struct _IdeHtmlGeneratorClass
+{
+ GObjectClass parent_class;
+
+ void (*invalidate) (IdeHtmlGenerator *self);
+ void (*generate_async) (IdeHtmlGenerator *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ GBytes *(*generate_finish) (IdeHtmlGenerator *self,
+ GAsyncResult *result,
+ GError **error);
+};
+
+IDE_AVAILABLE_IN_ALL
+const char *ide_html_generator_get_base_uri (IdeHtmlGenerator *self);
+IDE_AVAILABLE_IN_ALL
+void ide_html_generator_set_base_uri (IdeHtmlGenerator *self,
+ const char *base_uri);
+IDE_AVAILABLE_IN_ALL
+void ide_html_generator_invalidate (IdeHtmlGenerator *self);
+IDE_AVAILABLE_IN_ALL
+void ide_html_generator_generate_async (IdeHtmlGenerator *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+IDE_AVAILABLE_IN_ALL
+GBytes *ide_html_generator_generate_finish (IdeHtmlGenerator *self,
+ GAsyncResult *result,
+ GError **error);
+IDE_AVAILABLE_IN_ALL
+IdeHtmlGenerator *ide_html_generator_new_for_buffer (GtkTextBuffer *buffer);
+
+G_END_DECLS
diff --git a/src/libide/webkit/ide-text-buffer-html-generator.c
b/src/libide/webkit/ide-text-buffer-html-generator.c
new file mode 100644
index 000000000..5764c2148
--- /dev/null
+++ b/src/libide/webkit/ide-text-buffer-html-generator.c
@@ -0,0 +1,205 @@
+/* ide-text-buffer-html-generator.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 of the License, 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-text-buffer-html-generator"
+
+#include "config.h"
+
+#include <libide-code.h>
+#include <libide-threading.h>
+
+#include "ide-text-buffer-html-generator.h"
+
+struct _IdeTextBufferHtmlGenerator
+{
+ IdeHtmlGenerator parent_instance;
+ GSignalGroup *buffer_signals;
+};
+
+enum {
+ PROP_0,
+ PROP_BUFFER,
+ N_PROPS
+};
+
+G_DEFINE_FINAL_TYPE (IdeTextBufferHtmlGenerator, ide_text_buffer_html_generator, IDE_TYPE_HTML_GENERATOR)
+
+static GParamSpec *properties [N_PROPS];
+
+static inline GBytes *
+get_buffer_bytes (GtkTextBuffer *buffer)
+{
+ GtkTextIter begin, end;
+ char *slice;
+
+ gtk_text_buffer_get_bounds (buffer, &begin, &end);
+ slice = gtk_text_iter_get_slice (&begin, &end);
+
+ return g_bytes_new_take (slice, strlen (slice));
+}
+
+static void
+ide_text_buffer_html_generator_generate_async (IdeHtmlGenerator *generator,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ IdeTextBufferHtmlGenerator *self = (IdeTextBufferHtmlGenerator *)generator;
+ g_autoptr(GtkTextBuffer) buffer = NULL;
+ g_autoptr(IdeTask) task = NULL;
+
+ g_assert (IDE_IS_TEXT_BUFFER_HTML_GENERATOR (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = ide_task_new (self, cancellable, callback, user_data);
+ ide_task_set_source_tag (task, ide_text_buffer_html_generator_generate_async);
+
+ buffer = g_signal_group_dup_target (self->buffer_signals);
+
+ if (IDE_IS_BUFFER (buffer))
+ ide_task_return_pointer (task,
+ ide_buffer_dup_content (IDE_BUFFER (buffer)),
+ g_bytes_unref);
+ else if (GTK_IS_TEXT_BUFFER (buffer))
+ ide_task_return_pointer (task, get_buffer_bytes (buffer), g_bytes_unref);
+ else
+ ide_task_return_pointer (task,
+ g_bytes_new_take (g_strdup (""), 0),
+ g_bytes_unref);
+}
+
+static GBytes *
+ide_text_buffer_html_generator_generate_finish (IdeHtmlGenerator *generator,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (IDE_IS_TEXT_BUFFER_HTML_GENERATOR (generator));
+ g_assert (IDE_IS_TASK (result));
+
+ return ide_task_propagate_pointer (IDE_TASK (result), error);
+}
+
+static gboolean
+file_to_base_uri (GBinding *binding,
+ const GValue *from,
+ GValue *to,
+ gpointer user_data)
+{
+ g_value_set_string (to, g_file_get_uri (g_value_get_object (from)));
+ return TRUE;
+}
+
+static void
+ide_text_buffer_html_generator_set_buffer (IdeTextBufferHtmlGenerator *self,
+ GtkTextBuffer *buffer)
+{
+ g_assert (IDE_IS_TEXT_BUFFER_HTML_GENERATOR (self));
+ g_assert (!buffer || GTK_IS_TEXT_BUFFER (buffer));
+
+ g_signal_group_set_target (self->buffer_signals, buffer);
+
+ if (IDE_IS_BUFFER (buffer))
+ g_object_bind_property_full (buffer, "file",
+ self, "base-uri",
+ G_BINDING_SYNC_CREATE,
+ file_to_base_uri,
+ NULL, NULL, NULL);
+}
+
+static void
+ide_text_buffer_html_generator_dispose (GObject *object)
+{
+ IdeTextBufferHtmlGenerator *self = (IdeTextBufferHtmlGenerator *)object;
+
+ g_clear_object (&self->buffer_signals);
+
+ G_OBJECT_CLASS (ide_text_buffer_html_generator_parent_class)->dispose (object);
+}
+
+static void
+ide_text_buffer_html_generator_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeTextBufferHtmlGenerator *self = IDE_TEXT_BUFFER_HTML_GENERATOR (object);
+
+ switch (prop_id)
+ {
+ case PROP_BUFFER:
+ g_value_take_object (value, g_signal_group_dup_target (self->buffer_signals));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_text_buffer_html_generator_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeTextBufferHtmlGenerator *self = IDE_TEXT_BUFFER_HTML_GENERATOR (object);
+
+ switch (prop_id)
+ {
+ case PROP_BUFFER:
+ ide_text_buffer_html_generator_set_buffer (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_text_buffer_html_generator_class_init (IdeTextBufferHtmlGeneratorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ IdeHtmlGeneratorClass *generator_class = IDE_HTML_GENERATOR_CLASS (klass);
+
+ object_class->dispose = ide_text_buffer_html_generator_dispose;
+ object_class->get_property = ide_text_buffer_html_generator_get_property;
+ object_class->set_property = ide_text_buffer_html_generator_set_property;
+
+ generator_class->generate_async = ide_text_buffer_html_generator_generate_async;
+ generator_class->generate_finish = ide_text_buffer_html_generator_generate_finish;
+
+ properties [PROP_BUFFER] =
+ g_param_spec_object ("buffer", NULL, NULL,
+ GTK_TYPE_TEXT_BUFFER,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_text_buffer_html_generator_init (IdeTextBufferHtmlGenerator *self)
+{
+ self->buffer_signals = g_signal_group_new (GTK_TYPE_TEXT_BUFFER);
+
+ g_signal_group_connect_object (self->buffer_signals,
+ "changed",
+ G_CALLBACK (ide_html_generator_invalidate),
+ self,
+ G_CONNECT_SWAPPED);
+}
diff --git a/src/libide/webkit/ide-text-buffer-html-generator.h
b/src/libide/webkit/ide-text-buffer-html-generator.h
new file mode 100644
index 000000000..843910b33
--- /dev/null
+++ b/src/libide/webkit/ide-text-buffer-html-generator.h
@@ -0,0 +1,31 @@
+/* ide-text-buffer-html-generator.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 of the License, 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "ide-html-generator.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TEXT_BUFFER_HTML_GENERATOR (ide_text_buffer_html_generator_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeTextBufferHtmlGenerator, ide_text_buffer_html_generator, IDE,
TEXT_BUFFER_HTML_GENERATOR, IdeHtmlGenerator)
+
+G_END_DECLS
diff --git a/src/libide/webkit/ide-url-bar.c b/src/libide/webkit/ide-url-bar.c
new file mode 100644
index 000000000..0bc3d9194
--- /dev/null
+++ b/src/libide/webkit/ide-url-bar.c
@@ -0,0 +1,453 @@
+/* ide-url-bar.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 of the License, 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-url-bar"
+
+#include "config.h"
+
+#include <libide-io.h>
+#include <libide-gtk.h>
+
+#include "ide-url-bar.h"
+#include "ide-webkit-util.h"
+
+struct _IdeUrlBar
+{
+ GtkWidget parent_instance;
+
+ /* Owned references */
+ WebKitWebView *web_view;
+ GBindingGroup *web_view_bindings;
+ GSignalGroup *web_view_signals;
+
+ /* Weak References */
+ IdeAnimation *animation;
+
+ /* Template references */
+ GtkOverlay *overlay;
+ GtkStack *stack;
+ GtkLabel *url_display;
+ GtkText *url_editable;
+ GtkProgressBar *load_progress;
+ GtkImage *security_image;
+};
+
+enum {
+ PROP_0,
+ PROP_WEB_VIEW,
+ N_PROPS
+};
+
+G_DEFINE_FINAL_TYPE (IdeUrlBar, ide_url_bar, GTK_TYPE_WIDGET)
+
+static GParamSpec *properties [N_PROPS];
+
+static const char *
+get_security_icon_name (IdeWebkitSecurityLevel security_level)
+{
+ switch (security_level)
+ {
+ case IDE_WEBKIT_SECURITY_LEVEL_LOCAL_PAGE:
+ case IDE_WEBKIT_SECURITY_LEVEL_TO_BE_DETERMINED:
+ return NULL;
+
+ case IDE_WEBKIT_SECURITY_LEVEL_NONE:
+ case IDE_WEBKIT_SECURITY_LEVEL_UNACCEPTABLE_CERTIFICATE:
+ return "lock-small-open-symbolic";
+
+ case IDE_WEBKIT_SECURITY_LEVEL_STRONG_SECURITY:
+ return "lock-small-symbolic";
+
+ default:
+ return NULL;
+ }
+}
+
+static void
+on_web_view_load_changed_cb (IdeUrlBar *self,
+ WebKitLoadEvent load_event,
+ WebKitWebView *web_view)
+{
+ g_assert (IDE_IS_URL_BAR (self));
+ g_assert (WEBKIT_IS_WEB_VIEW (web_view));
+
+ switch (load_event)
+ {
+ case WEBKIT_LOAD_COMMITTED:
+ case WEBKIT_LOAD_FINISHED: {
+ IdeWebkitSecurityLevel security_level;
+
+ security_level = ide_webkit_util_get_security_level (web_view);
+ g_object_set (self->security_image,
+ "icon-name", get_security_icon_name (security_level),
+ NULL);
+ break;
+ }
+
+ case WEBKIT_LOAD_REDIRECTED:
+ case WEBKIT_LOAD_STARTED:
+ g_object_set (self->security_image,
+ "icon-name", "content-loading-symbolic",
+ NULL);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+on_editable_focus_enter_cb (IdeUrlBar *self,
+ GtkEventControllerFocus *focus)
+{
+ g_assert (IDE_IS_URL_BAR (self));
+ g_assert (GTK_IS_EVENT_CONTROLLER_FOCUS (focus));
+
+}
+
+static void
+on_editable_focus_leave_cb (IdeUrlBar *self,
+ GtkEventControllerFocus *focus)
+{
+ g_assert (IDE_IS_URL_BAR (self));
+ g_assert (GTK_IS_EVENT_CONTROLLER_FOCUS (focus));
+
+}
+
+static void
+on_editable_activate_cb (IdeUrlBar *self,
+ GtkEditable *editable)
+{
+ g_autofree char *expanded = NULL;
+ g_autofree char *normalized = NULL;
+ const char *uri;
+
+ g_assert (IDE_IS_URL_BAR (self));
+ g_assert (GTK_IS_EDITABLE (editable));
+
+ if (self->web_view == NULL)
+ return;
+
+ uri = gtk_editable_get_text (editable);
+ if (uri == NULL || uri[0] == 0)
+ return;
+
+ /* Expand ~/ access to home directory first */
+ if (g_str_has_prefix (uri, "~/"))
+ uri = expanded = ide_path_expand (uri);
+
+ normalized = ide_webkit_util_normalize_address (uri);
+
+ webkit_web_view_load_uri (self->web_view, normalized);
+ gtk_stack_set_visible_child_name (self->stack, "display");
+ gtk_widget_grab_focus (GTK_WIDGET (self->web_view));
+}
+
+static void
+on_click_gesture_pressed_cb (IdeUrlBar *self,
+ int n_presses,
+ double x,
+ double y,
+ GtkGestureClick *click)
+{
+ const char *name;
+
+ g_assert (IDE_IS_URL_BAR (self));
+ g_assert (GTK_IS_GESTURE_CLICK (click));
+
+ if (self->web_view == NULL)
+ return;
+
+ name = gtk_stack_get_visible_child_name (self->stack);
+
+ /* On first click, just change to the text field immediately so that
+ * we can propagate the event to that widget instead of the label.
+ */
+ if (n_presses == 1)
+ {
+ if (g_strcmp0 (name, "edit") != 0)
+ {
+ const char *uri = webkit_web_view_get_uri (self->web_view);
+
+ gtk_editable_set_text (GTK_EDITABLE (self->url_editable), uri ? uri : "");
+ gtk_stack_set_visible_child_name (self->stack, "edit");
+ gtk_widget_grab_focus (GTK_WIDGET (self->url_editable));
+ gtk_editable_select_region (GTK_EDITABLE (self->url_editable), 0, -1);
+ gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED);
+ return;
+ }
+ }
+
+ gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_DENIED);
+}
+
+static void
+on_web_view_notify_is_loading_cb (IdeUrlBar *self,
+ GParamSpec *pspec,
+ WebKitWebView *web_view)
+{
+ g_assert (IDE_IS_URL_BAR (self));
+ g_assert (WEBKIT_IS_WEB_VIEW (web_view));
+
+ if (webkit_web_view_is_loading (web_view))
+ {
+ gtk_progress_bar_set_fraction (self->load_progress, 0);
+ gtk_widget_show (GTK_WIDGET (self->load_progress));
+ }
+ else
+ {
+ ide_gtk_widget_hide_with_fade (GTK_WIDGET (self->load_progress));
+ }
+}
+
+static void
+on_web_view_notify_estimated_load_progress_cb (IdeUrlBar *self,
+ GParamSpec *pspec,
+ WebKitWebView *web_view)
+{
+ IdeAnimation *anim;
+ double progress;
+
+ g_assert (IDE_IS_URL_BAR (self));
+ g_assert (WEBKIT_IS_WEB_VIEW (web_view));
+
+ progress = webkit_web_view_get_estimated_load_progress (web_view);
+
+ /* First cancel any previous animation */
+ if ((anim = self->animation))
+ {
+ g_clear_weak_pointer (&self->animation);
+ ide_animation_stop (anim);
+ }
+
+ /* Short-circuit if we're not actively loading or we are jumping
+ * backwards in progress instead of forwards.
+ */
+ if (!webkit_web_view_is_loading (web_view) ||
+ progress < gtk_progress_bar_get_fraction (self->load_progress))
+ {
+ gtk_progress_bar_set_fraction (self->load_progress, progress);
+ return;
+ }
+
+ anim = ide_object_animate (self->load_progress,
+ IDE_ANIMATION_LINEAR,
+ 200,
+ NULL,
+ "fraction", progress,
+ NULL);
+ g_set_weak_pointer (&self->animation, anim);
+}
+
+static gboolean
+ide_url_bar_grab_focus (GtkWidget *widget)
+{
+ IdeUrlBar *self = (IdeUrlBar *)widget;
+
+ g_assert (IDE_IS_URL_BAR (self));
+
+ if (self->web_view == NULL)
+ return FALSE;
+
+ gtk_stack_set_visible_child_name (self->stack, "edit");
+ gtk_widget_grab_focus (GTK_WIDGET (self->url_editable));
+ gtk_editable_select_region (GTK_EDITABLE (self->url_editable), 0, -1);
+
+ return TRUE;
+}
+
+static gboolean
+focus_view_callback (GtkWidget *widget,
+ GVariant *params,
+ gpointer user_data)
+{
+ IdeUrlBar *self = (IdeUrlBar *)widget;
+
+ g_assert (IDE_IS_URL_BAR (self));
+
+ if (self->web_view != NULL)
+ return gtk_widget_grab_focus (GTK_WIDGET (self->web_view));
+
+ return FALSE;
+}
+
+static void
+ide_url_bar_dispose (GObject *object)
+{
+ IdeUrlBar *self = (IdeUrlBar *)object;
+
+ g_clear_object (&self->web_view_bindings);
+ g_clear_object (&self->web_view_signals);
+ g_clear_object (&self->web_view);
+
+ g_clear_pointer ((GtkWidget **)&self->overlay, gtk_widget_unparent);
+
+ G_OBJECT_CLASS (ide_url_bar_parent_class)->dispose (object);
+}
+
+static void
+ide_url_bar_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeUrlBar *self = IDE_URL_BAR (object);
+
+ switch (prop_id)
+ {
+ case PROP_WEB_VIEW:
+ g_value_set_object (value, ide_url_bar_get_web_view (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_url_bar_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeUrlBar *self = IDE_URL_BAR (object);
+
+ switch (prop_id)
+ {
+ case PROP_WEB_VIEW:
+ ide_url_bar_set_web_view (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_url_bar_class_init (IdeUrlBarClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = ide_url_bar_dispose;
+ object_class->get_property = ide_url_bar_get_property;
+ object_class->set_property = ide_url_bar_set_property;
+
+ widget_class->grab_focus = ide_url_bar_grab_focus;
+
+ properties [PROP_WEB_VIEW] =
+ g_param_spec_object ("web-view", NULL, NULL,
+ WEBKIT_TYPE_WEB_VIEW,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_css_name (widget_class, "entry");
+ gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+ gtk_widget_class_set_template_from_resource (widget_class, "/plugins/webkit/ide-url-bar.ui");
+ gtk_widget_class_bind_template_child (widget_class, IdeUrlBar, url_display);
+ gtk_widget_class_bind_template_child (widget_class, IdeUrlBar, url_editable);
+ gtk_widget_class_bind_template_child (widget_class, IdeUrlBar, load_progress);
+ gtk_widget_class_bind_template_child (widget_class, IdeUrlBar, overlay);
+ gtk_widget_class_bind_template_child (widget_class, IdeUrlBar, security_image);
+ gtk_widget_class_bind_template_child (widget_class, IdeUrlBar, stack);
+ gtk_widget_class_bind_template_callback (widget_class, on_click_gesture_pressed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_editable_focus_enter_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_editable_focus_leave_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_editable_activate_cb);
+
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_Escape, 0, focus_view_callback, NULL);
+}
+
+static void
+ide_url_bar_init (IdeUrlBar *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->web_view_signals = g_signal_group_new (WEBKIT_TYPE_WEB_VIEW);
+ g_signal_group_connect_object (self->web_view_signals,
+ "notify::estimated-load-progress",
+ G_CALLBACK (on_web_view_notify_estimated_load_progress_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_group_connect_object (self->web_view_signals,
+ "notify::is-loading",
+ G_CALLBACK (on_web_view_notify_is_loading_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_group_connect_object (self->web_view_signals,
+ "load-changed",
+ G_CALLBACK (on_web_view_load_changed_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ self->web_view_bindings = g_binding_group_new ();
+ g_binding_group_bind (self->web_view_bindings, "uri",
+ self->url_display, "label",
+ G_BINDING_SYNC_CREATE);
+
+ gtk_widget_set_cursor_from_name (GTK_WIDGET (self->url_display), "text");
+}
+
+WebKitWebView *
+ide_url_bar_get_web_view (IdeUrlBar *self)
+{
+ g_return_val_if_fail (IDE_IS_URL_BAR (self), NULL);
+
+ return self->web_view;
+}
+
+void
+ide_url_bar_set_web_view (IdeUrlBar *self,
+ WebKitWebView *web_view)
+{
+ g_return_if_fail (IDE_IS_URL_BAR (self));
+ g_return_if_fail (!web_view || WEBKIT_IS_WEB_VIEW (web_view));
+
+ if (g_set_object (&self->web_view, web_view))
+ {
+ g_binding_group_set_source (self->web_view_bindings, web_view);
+ g_signal_group_set_target (self->web_view_signals, web_view);
+
+ gtk_widget_hide (GTK_WIDGET (self->load_progress));
+ gtk_widget_set_can_focus (GTK_WIDGET (self), web_view != NULL);
+ g_object_set (self->security_image,
+ "icon-name", NULL,
+ NULL);
+
+ if (self->web_view != NULL)
+ {
+ const char *uri = webkit_web_view_get_uri (self->web_view);
+
+ gtk_editable_set_text (GTK_EDITABLE (self->url_editable), uri ? uri : "");
+
+ if (gtk_widget_has_focus (GTK_WIDGET (self->url_editable)))
+ gtk_editable_select_region (GTK_EDITABLE (self->url_editable), 0, -1);
+
+ on_web_view_notify_estimated_load_progress_cb (self, NULL, self->web_view);
+
+ /* TODO: Update security image if we ever share a url bar for multiple
+ * web views.
+ */
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WEB_VIEW]);
+ }
+}
diff --git a/src/libide/webkit/ide-url-bar.h b/src/libide/webkit/ide-url-bar.h
new file mode 100644
index 000000000..dd0d68a21
--- /dev/null
+++ b/src/libide/webkit/ide-url-bar.h
@@ -0,0 +1,37 @@
+/* ide-url-bar.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 of the License, 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_URL_BAR (ide_url_bar_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeUrlBar, ide_url_bar, IDE, URL_BAR, GtkWidget)
+
+IdeUrlBar *ide_url_bar_new (void);
+WebKitWebView *ide_url_bar_get_web_view (IdeUrlBar *self);
+void ide_url_bar_set_web_view (IdeUrlBar *self,
+ WebKitWebView *web_view);
+
+G_END_DECLS
diff --git a/src/libide/webkit/ide-url-bar.ui b/src/libide/webkit/ide-url-bar.ui
new file mode 100644
index 000000000..ee53cfba0
--- /dev/null
+++ b/src/libide/webkit/ide-url-bar.ui
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="IdeUrlBar" parent="GtkWidget">
+ <child>
+ <object class="GtkGestureClick">
+ <property name="propagation-phase">capture</property>
+ <signal name="pressed" handler="on_click_gesture_pressed_cb" swapped="true" object="IdeUrlBar"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkOverlay" id="overlay">
+ <child type="overlay">
+ <object class="GtkProgressBar" id="load_progress">
+ <property name="hexpand">true</property>
+ <property name="valign">end</property>
+ <property name="margin-start">6</property>
+ <property name="margin-end">6</property>
+ <property name="visible">false</property>
+ <style>
+ <class name="osd"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="hexpand">true</property>
+ <property name="transition-type">none</property>
+ <property name="hhomogeneous">true</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">display</property>
+ <property name="child">
+ <object class="GtkBox">
+ <child>
+ <object class="GtkBox" id="display_controls">
+ <property name="margin-start">6</property>
+ <property name="margin-end">6</property>
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkImage" id="security_image">
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="url_display">
+ <property name="hexpand">true</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">edit</property>
+ <property name="child">
+ <object class="GtkBox">
+ <child>
+ <object class="GtkBox" id="edit_controls">
+ </object>
+ </child>
+ <child>
+ <object class="GtkText" id="url_editable">
+ <property name="hexpand">true</property>
+ <property name="enable-emoji-completion">false</property>
+ <property name="input-purpose">url</property>
+ <signal name="activate" handler="on_editable_activate_cb" swapped="true"
object="IdeUrlBar"/>
+ <child>
+ <object class="GtkEventControllerFocus">
+ <signal name="enter" handler="on_editable_focus_enter_cb" swapped="true"
object="IdeUrlBar"/>
+ <signal name="leave" handler="on_editable_focus_leave_cb" swapped="true"
object="IdeUrlBar"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkSizeGroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="edit_controls"/>
+ <widget name="display_controls"/>
+ </widgets>
+ </object>
+</interface>
diff --git a/src/libide/webkit/ide-webkit-page.c b/src/libide/webkit/ide-webkit-page.c
new file mode 100644
index 000000000..f8af06ffe
--- /dev/null
+++ b/src/libide/webkit/ide-webkit-page.c
@@ -0,0 +1,708 @@
+/* ide-webkit-page.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 of the License, 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-webkit-page"
+
+#include "config.h"
+
+#include <webkit2/webkit2.h>
+
+#include "ide-webkit-page.h"
+#include "ide-url-bar.h"
+
+typedef struct
+{
+ GtkStack *reload_stack;
+ GtkCenterBox *toolbar;
+ IdeUrlBar *url_bar;
+ WebKitSettings *web_settings;
+ WebKitWebView *web_view;
+
+ GSimpleActionGroup *actions;
+
+ IdeHtmlGenerator *generator;
+
+ guint dirty : 1;
+ guint generating : 1;
+ guint disposed : 1;
+} IdeWebkitPagePrivate;
+
+enum {
+ PROP_0,
+ PROP_SHOW_TOOLBAR,
+ N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeWebkitPage, ide_webkit_page, IDE_TYPE_PAGE)
+
+static GParamSpec *properties [N_PROPS];
+
+static gboolean
+transform_title_with_fallback (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ IdeWebkitPage *self = user_data;
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+ const char *title;
+
+ g_assert (G_IS_BINDING (binding));
+ g_assert (G_VALUE_HOLDS_STRING (from_value));
+ g_assert (G_VALUE_HOLDS_STRING (to_value));
+ g_assert (IDE_IS_WEBKIT_PAGE (self));
+
+ title = g_value_get_string (from_value);
+ if (ide_str_empty0 (title))
+ title = webkit_web_view_get_uri (priv->web_view);
+ g_value_set_string (to_value, title);
+ return TRUE;
+}
+
+static gboolean
+transform_cairo_surface_to_gicon (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ IdeWebkitPage *self = user_data;
+ cairo_surface_t *surface;
+ GdkPixbuf *pixbuf;
+ int favicon_width;
+ int favicon_height;
+ int width;
+ int height;
+
+ g_assert (G_IS_BINDING (binding));
+ g_assert (G_VALUE_HOLDS_POINTER (from_value));
+ g_assert (G_VALUE_HOLDS_OBJECT (to_value));
+ g_assert (IDE_IS_WEBKIT_PAGE (self));
+
+ /* No ownership transfer */
+ surface = g_value_get_pointer (from_value);
+
+ if (surface == NULL)
+ {
+ g_value_take_object (to_value, g_themed_icon_new ("web-browser-symbolic"));
+ return TRUE;
+ }
+
+ width = 16 * gtk_widget_get_scale_factor (GTK_WIDGET (self));
+ height = 16 * gtk_widget_get_scale_factor (GTK_WIDGET (self));
+ favicon_width = cairo_image_surface_get_width (surface);
+ favicon_height = cairo_image_surface_get_height (surface);
+ pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, favicon_width, favicon_height);
+
+ if ((favicon_width != width || favicon_height != height))
+ {
+ GdkPixbuf *scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+ pixbuf = scaled_pixbuf;
+ }
+
+ g_assert (!pixbuf || G_IS_ICON (pixbuf));
+
+ g_value_take_object (to_value, pixbuf);
+
+ return TRUE;
+}
+
+static void
+on_toolbar_notify_visible_cb (IdeWebkitPage *self,
+ GParamSpec *pspec,
+ GtkWidget *toolbar)
+{
+ g_assert (IDE_IS_WEBKIT_PAGE (self));
+ g_assert (GTK_IS_WIDGET (toolbar));
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_TOOLBAR]);
+}
+
+static gboolean
+ide_webkit_page_grab_focus (GtkWidget *widget)
+{
+ IdeWebkitPage *self = (IdeWebkitPage *)widget;
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+ const char *uri;
+
+ g_assert (IDE_IS_WEBKIT_PAGE (self));
+
+ uri = webkit_web_view_get_uri (priv->web_view);
+
+ if (ide_str_empty0 (uri))
+ return gtk_widget_grab_focus (GTK_WIDGET (priv->url_bar));
+ else
+ return gtk_widget_grab_focus (GTK_WIDGET (priv->web_view));
+}
+
+static gboolean
+on_web_view_decide_policy_cb (IdeWebkitPage *self,
+ WebKitPolicyDecision *decision,
+ WebKitPolicyDecisionType decision_type,
+ WebKitWebView *web_view)
+{
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+
+ g_assert (IDE_IS_WEBKIT_PAGE (self));
+ g_assert (WEBKIT_IS_POLICY_DECISION (decision));
+ g_assert (WEBKIT_IS_WEB_VIEW (web_view));
+
+ if (priv->generator == NULL)
+ return FALSE;
+
+ if (decision_type == WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION)
+ {
+ WebKitNavigationAction *action = webkit_navigation_policy_decision_get_navigation_action
(WEBKIT_NAVIGATION_POLICY_DECISION (decision));
+ WebKitURIRequest *request = webkit_navigation_action_get_request (action);
+ const char *uri = webkit_uri_request_get_uri (request);
+ const char *base_uri = ide_html_generator_get_base_uri (priv->generator);
+
+ if (!ide_str_equal0 (uri, base_uri))
+ {
+ GtkRoot *root = gtk_widget_get_root (GTK_WIDGET (self));
+ ide_gtk_show_uri_on_window (GTK_WINDOW (root), uri, g_get_monotonic_time (), NULL);
+ webkit_policy_decision_ignore (decision);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+go_forward_action (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeWebkitPage *self = user_data;
+
+ IDE_ENTRY;
+
+ g_assert (G_IS_SIMPLE_ACTION (action));
+ g_assert (IDE_IS_WEBKIT_PAGE (self));
+
+ ide_webkit_page_go_forward (self);
+
+ IDE_EXIT;
+}
+
+static void
+go_back_action (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeWebkitPage *self = user_data;
+
+ IDE_ENTRY;
+
+ g_assert (G_IS_SIMPLE_ACTION (action));
+ g_assert (IDE_IS_WEBKIT_PAGE (self));
+
+ ide_webkit_page_go_back (self);
+
+ IDE_EXIT;
+}
+
+static void
+reload_action (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeWebkitPage *self = user_data;
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+
+ IDE_ENTRY;
+
+ g_assert (G_IS_SIMPLE_ACTION (action));
+ g_assert (IDE_IS_WEBKIT_PAGE (self));
+
+ webkit_web_view_reload (priv->web_view);
+
+ IDE_EXIT;
+}
+
+static void
+stop_action (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeWebkitPage *self = user_data;
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+
+ IDE_ENTRY;
+
+ g_assert (G_IS_SIMPLE_ACTION (action));
+ g_assert (IDE_IS_WEBKIT_PAGE (self));
+
+ webkit_web_view_stop_loading (priv->web_view);
+
+ IDE_EXIT;
+}
+
+static const GActionEntry actions[] = {
+ { "go-forward", go_forward_action },
+ { "go-back", go_back_action },
+ { "reload", reload_action },
+ { "stop", stop_action },
+};
+
+static void
+set_action_enabled (IdeWebkitPage *self,
+ const char *action_name,
+ gboolean enabled)
+{
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+ GAction *action;
+
+ g_assert (IDE_IS_WEBKIT_PAGE (self));
+ g_assert (action_name != NULL);
+
+ if (!(action = g_action_map_lookup_action (G_ACTION_MAP (priv->actions), action_name)))
+ {
+ g_critical ("Failed to locate action %s", action_name);
+ return;
+ }
+
+ if (!G_IS_SIMPLE_ACTION (action))
+ {
+ g_critical ("Implausible, %s is not a GSimpleAction",
+ G_OBJECT_TYPE_NAME (action));
+ return;
+ }
+
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
+}
+
+static void
+on_back_forward_list_changed_cb (IdeWebkitPage *self,
+ WebKitBackForwardListItem *item_added,
+ const GList *items_removed,
+ WebKitBackForwardList *list)
+{
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_WEBKIT_PAGE (self));
+ g_assert (WEBKIT_IS_BACK_FORWARD_LIST (list));
+
+ set_action_enabled (self, "go-forward",
+ webkit_web_view_can_go_forward (priv->web_view));
+ set_action_enabled (self, "go-back",
+ webkit_web_view_can_go_back (priv->web_view));
+
+ IDE_EXIT;
+}
+
+static void
+ide_webkit_page_update_reload (IdeWebkitPage *self)
+{
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+ const char *uri;
+ gboolean loading;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_WEBKIT_PAGE (self));
+
+ loading = webkit_web_view_is_loading (priv->web_view);
+ uri = webkit_web_view_get_uri (priv->web_view);
+
+ set_action_enabled (self, "reload", !loading && !ide_str_empty0 (uri));
+ set_action_enabled (self, "stop", loading);
+
+ if (loading)
+ gtk_stack_set_visible_child_name (priv->reload_stack, "stop");
+ else
+ gtk_stack_set_visible_child_name (priv->reload_stack, "reload");
+
+ IDE_EXIT;
+}
+
+static void
+add_property_action (gpointer object,
+ const char *property_name,
+ GActionMap *action_map)
+{
+ g_autoptr(GPropertyAction) action = NULL;
+
+ g_assert (G_IS_OBJECT (object));
+ g_assert (property_name != NULL);
+ g_assert (G_IS_ACTION_MAP (action_map));
+
+ action = g_property_action_new (property_name, object, property_name);
+
+ if (action != NULL)
+ g_action_map_add_action (action_map, G_ACTION (action));
+}
+
+static void
+ide_webkit_page_constructed (GObject *object)
+{
+ IdeWebkitPage *self = (IdeWebkitPage *)object;
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+ GtkStyleContext *context;
+ GdkRGBA color;
+
+ G_OBJECT_CLASS (ide_webkit_page_parent_class)->constructed (object);
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (priv->web_view));
+ if (gtk_style_context_lookup_color (context, "theme_base_color", &color))
+ webkit_web_view_set_background_color (WEBKIT_WEB_VIEW (priv->web_view), &color);
+}
+
+static void
+ide_webkit_page_dispose (GObject *object)
+{
+ IdeWebkitPage *self = (IdeWebkitPage *)object;
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+
+ priv->disposed = TRUE;
+
+ g_clear_object (&priv->generator);
+ g_clear_object (&priv->actions);
+
+ G_OBJECT_CLASS (ide_webkit_page_parent_class)->dispose (object);
+}
+
+static void
+ide_webkit_page_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeWebkitPage *self = IDE_WEBKIT_PAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_SHOW_TOOLBAR:
+ g_value_set_boolean (value, ide_webkit_page_get_show_toolbar (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_webkit_page_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeWebkitPage *self = IDE_WEBKIT_PAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_SHOW_TOOLBAR:
+ ide_webkit_page_set_show_toolbar (self, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_webkit_page_class_init (IdeWebkitPageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructed = ide_webkit_page_constructed;
+ object_class->dispose = ide_webkit_page_dispose;
+ object_class->get_property = ide_webkit_page_get_property;
+ object_class->set_property = ide_webkit_page_set_property;
+
+ widget_class->grab_focus = ide_webkit_page_grab_focus;
+
+ properties [PROP_SHOW_TOOLBAR] =
+ g_param_spec_boolean ("show-toolbar",
+ "Show Toolbar",
+ "Show Toolbar",
+ TRUE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/plugins/webkit/ide-webkit-page.ui");
+
+ gtk_widget_class_bind_template_child_private (widget_class, IdeWebkitPage, reload_stack);
+ gtk_widget_class_bind_template_child_private (widget_class, IdeWebkitPage, toolbar);
+ gtk_widget_class_bind_template_child_private (widget_class, IdeWebkitPage, url_bar);
+ gtk_widget_class_bind_template_child_private (widget_class, IdeWebkitPage, web_settings);
+ gtk_widget_class_bind_template_child_private (widget_class, IdeWebkitPage, web_view);
+ gtk_widget_class_bind_template_callback (widget_class, on_toolbar_notify_visible_cb);
+ gtk_widget_class_bind_template_callback (widget_class, ide_webkit_page_update_reload);
+ gtk_widget_class_bind_template_callback (widget_class, on_web_view_decide_policy_cb);
+
+ g_type_ensure (WEBKIT_TYPE_SETTINGS);
+ g_type_ensure (WEBKIT_TYPE_WEB_VIEW);
+ g_type_ensure (IDE_TYPE_URL_BAR);
+}
+
+static void
+ide_webkit_page_init (IdeWebkitPage *self)
+{
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+ WebKitBackForwardList *list;
+
+ panel_widget_set_can_maximize (PANEL_WIDGET (self), TRUE);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_object_bind_property_full (priv->web_view, "title", self, "title", 0,
+ transform_title_with_fallback,
+ NULL, self, NULL);
+ g_object_bind_property_full (priv->web_view, "favicon", self, "icon", 0,
+ transform_cairo_surface_to_gicon,
+ NULL, self, NULL);
+
+ priv->actions = g_simple_action_group_new ();
+ g_action_map_add_action_entries (G_ACTION_MAP (priv->actions),
+ actions,
+ G_N_ELEMENTS (actions),
+ self);
+ gtk_widget_insert_action_group (GTK_WIDGET (self),
+ "web",
+ G_ACTION_GROUP (priv->actions));
+
+ add_property_action (priv->web_settings,
+ "enable-javascript",
+ G_ACTION_MAP (priv->actions));
+
+ list = webkit_web_view_get_back_forward_list (priv->web_view);
+ g_signal_connect_object (list,
+ "changed",
+ G_CALLBACK (on_back_forward_list_changed_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ set_action_enabled (self, "go-forward", FALSE);
+ set_action_enabled (self, "go-back", FALSE);
+ set_action_enabled (self, "reload", FALSE);
+ set_action_enabled (self, "stop", FALSE);
+}
+
+IdeWebkitPage *
+ide_webkit_page_new (void)
+{
+ return g_object_new (IDE_TYPE_WEBKIT_PAGE, NULL);
+}
+
+void
+ide_webkit_page_load_uri (IdeWebkitPage *self,
+ const char *uri)
+{
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_WEBKIT_PAGE (self));
+ g_return_if_fail (uri != NULL);
+
+ webkit_web_view_load_uri (priv->web_view, uri);
+}
+
+gboolean
+ide_webkit_page_get_show_toolbar (IdeWebkitPage *self)
+{
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_WEBKIT_PAGE (self), FALSE);
+
+ return gtk_widget_get_visible (GTK_WIDGET (priv->toolbar));
+}
+
+void
+ide_webkit_page_set_show_toolbar (IdeWebkitPage *self,
+ gboolean show_toolbar)
+{
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_WEBKIT_PAGE (self));
+
+ gtk_widget_set_visible (GTK_WIDGET (priv->toolbar), show_toolbar);
+}
+
+gboolean
+ide_webkit_page_focus_address (IdeWebkitPage *self)
+{
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_WEBKIT_PAGE (self), FALSE);
+
+ return gtk_widget_grab_focus (GTK_WIDGET (priv->url_bar));
+}
+
+void
+ide_webkit_page_go_back (IdeWebkitPage *self)
+{
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+ WebKitBackForwardList *list;
+ WebKitBackForwardListItem *item;
+
+ IDE_ENTRY;
+
+ g_return_if_fail (IDE_IS_WEBKIT_PAGE (self));
+
+ list = webkit_web_view_get_back_forward_list (priv->web_view);
+ item = webkit_back_forward_list_get_back_item (list);
+
+ g_return_if_fail (item != NULL);
+
+ webkit_web_view_go_to_back_forward_list_item (priv->web_view, item);
+
+ IDE_EXIT;
+}
+
+void
+ide_webkit_page_go_forward (IdeWebkitPage *self)
+{
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+ WebKitBackForwardList *list;
+ WebKitBackForwardListItem *item;
+
+ IDE_ENTRY;
+
+ g_return_if_fail (IDE_IS_WEBKIT_PAGE (self));
+
+ list = webkit_web_view_get_back_forward_list (priv->web_view);
+ item = webkit_back_forward_list_get_forward_item (list);
+
+ g_return_if_fail (item != NULL);
+
+ webkit_web_view_go_to_back_forward_list_item (priv->web_view, item);
+
+ IDE_EXIT;
+}
+
+void
+ide_webkit_page_reload (IdeWebkitPage *self)
+{
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_WEBKIT_PAGE (self));
+
+ if (webkit_web_view_is_loading (priv->web_view))
+ webkit_web_view_stop_loading (priv->web_view);
+
+ webkit_web_view_reload (priv->web_view);
+}
+
+void
+ide_webkit_page_reload_ignoring_cache (IdeWebkitPage *self)
+{
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_WEBKIT_PAGE (self));
+
+ if (webkit_web_view_is_loading (priv->web_view))
+ webkit_web_view_stop_loading (priv->web_view);
+
+ webkit_web_view_reload_bypass_cache (priv->web_view);
+}
+
+static void
+ide_webkit_page_generate_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeHtmlGenerator *generator = (IdeHtmlGenerator *)object;
+ g_autoptr(IdeWebkitPage) self = user_data;
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GBytes) bytes = NULL;
+
+ g_assert (IDE_IS_HTML_GENERATOR (generator));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (IDE_IS_WEBKIT_PAGE (self));
+
+ priv->generating = FALSE;
+
+ if (!(bytes = ide_html_generator_generate_finish (generator, result, &error)))
+ {
+ /* Don't try to spin again in this case by checking dirty */
+ g_warning ("Failed to generate HTML: %s", error->message);
+ return;
+ }
+
+ if (priv->disposed)
+ return;
+
+ webkit_web_view_load_html (priv->web_view,
+ (const char *)g_bytes_get_data (bytes, NULL),
+ ide_html_generator_get_base_uri (generator));
+
+ /* See if we need to run again, and check for re-entrantcy */
+ if (priv->dirty && !priv->generating)
+ {
+ priv->dirty = FALSE;
+ priv->generating = TRUE;
+ ide_html_generator_generate_async (generator,
+ NULL,
+ ide_webkit_page_generate_cb,
+ g_steal_pointer (&self));
+ }
+}
+
+static void
+ide_webkit_page_generator_invalidate_cb (IdeWebkitPage *self,
+ IdeHtmlGenerator *generator)
+{
+ IdeWebkitPagePrivate *priv = ide_webkit_page_get_instance_private (self);
+
+ g_assert (IDE_IS_MAIN_THREAD ());
+ g_assert (IDE_IS_WEBKIT_PAGE (self));
+ g_assert (IDE_IS_HTML_GENERATOR (generator));
+
+ priv->dirty = TRUE;
+
+ if (priv->generating)
+ return;
+
+ priv->generating = TRUE;
+ priv->dirty = FALSE;
+
+ ide_html_generator_generate_async (generator,
+ NULL,
+ ide_webkit_page_generate_cb,
+ g_object_ref (self));
+}
+
+IdeWebkitPage *
+ide_webkit_page_new_for_generator (IdeHtmlGenerator *generator)
+{
+ IdeWebkitPage *self;
+ IdeWebkitPagePrivate *priv;
+
+ g_return_val_if_fail (IDE_IS_HTML_GENERATOR (generator), NULL);
+
+ self = ide_webkit_page_new ();
+ priv = ide_webkit_page_get_instance_private (self);
+
+ priv->generator = g_object_ref (generator);
+ g_signal_connect_object (priv->generator,
+ "invalidate",
+ G_CALLBACK (ide_webkit_page_generator_invalidate_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ ide_webkit_page_generator_invalidate_cb (self, generator);
+
+ return self;
+}
diff --git a/src/libide/webkit/ide-webkit-page.h b/src/libide/webkit/ide-webkit-page.h
new file mode 100644
index 000000000..091511fbe
--- /dev/null
+++ b/src/libide/webkit/ide-webkit-page.h
@@ -0,0 +1,62 @@
+/* ide-webkit-page.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 of the License, 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-gui.h>
+
+#include "ide-html-generator.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_WEBKIT_PAGE (ide_webkit_page_get_type())
+
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_DERIVABLE_TYPE (IdeWebkitPage, ide_webkit_page, IDE, WEBKIT_PAGE, IdePage)
+
+struct _IdeWebkitPageClass
+{
+ IdePageClass parent_class;
+};
+
+IDE_AVAILABLE_IN_ALL
+IdeWebkitPage *ide_webkit_page_new (void);
+IDE_AVAILABLE_IN_ALL
+IdeWebkitPage *ide_webkit_page_new_for_generator (IdeHtmlGenerator *generator);
+IDE_AVAILABLE_IN_ALL
+void ide_webkit_page_load_uri (IdeWebkitPage *self,
+ const char *uri);
+IDE_AVAILABLE_IN_ALL
+gboolean ide_webkit_page_focus_address (IdeWebkitPage *self);
+IDE_AVAILABLE_IN_ALL
+gboolean ide_webkit_page_get_show_toolbar (IdeWebkitPage *self);
+IDE_AVAILABLE_IN_ALL
+void ide_webkit_page_set_show_toolbar (IdeWebkitPage *self,
+ gboolean show_toolbar);
+IDE_AVAILABLE_IN_ALL
+void ide_webkit_page_go_back (IdeWebkitPage *self);
+IDE_AVAILABLE_IN_ALL
+void ide_webkit_page_go_forward (IdeWebkitPage *self);
+IDE_AVAILABLE_IN_ALL
+void ide_webkit_page_reload (IdeWebkitPage *self);
+IDE_AVAILABLE_IN_ALL
+void ide_webkit_page_reload_ignoring_cache (IdeWebkitPage *self);
+
+G_END_DECLS
diff --git a/src/libide/webkit/ide-webkit-page.ui b/src/libide/webkit/ide-webkit-page.ui
new file mode 100644
index 000000000..c809725d2
--- /dev/null
+++ b/src/libide/webkit/ide-webkit-page.ui
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="IdeWebkitPage" parent="IdePage">
+ <property name="icon-name">web-browser-symbolic</property>
+ <property name="title" translatable="yes">Blank page</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="WebKitWebView" id="web_view">
+ <property name="settings">web_settings</property>
+ <property name="hexpand">true</property>
+ <property name="vexpand">true</property>
+ <signal name="notify::is-loading" handler="ide_webkit_page_update_reload" swapped="true"
object="IdeWebkitPage"/>
+ <signal name="notify::uri" handler="ide_webkit_page_update_reload" swapped="true"
object="IdeWebkitPage"/>
+ <signal name="decide-policy" handler="on_web_view_decide_policy_cb" swapped="true"
object="IdeWebkitPage"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparator"/>
+ </child>
+ <child>
+ <object class="GtkCenterBox" id="toolbar">
+ <signal name="notify::visible" handler="on_toolbar_notify_visible_cb" swapped="true"
object="IdeWebkitPage"/>
+ <property name="orientation">horizontal</property>
+ <style>
+ <class name="toolbar"/>
+ </style>
+ <child type="start">
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">3</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkButton">
+ <property name="icon-name">go-previous-symbolic</property>
+ <property name="action-name">web.go-back</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="icon-name">go-next-symbolic</property>
+ <property name="action-name">web.go-forward</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="reload_stack">
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">reload</property>
+ <property name="child">
+ <object class="GtkButton">
+ <property name="icon-name">view-refresh-symbolic</property>
+ <property name="action-name">web.reload</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">stop</property>
+ <property name="child">
+ <object class="GtkButton">
+ <property name="icon-name">stop-sign-symbolic</property>
+ <property name="action-name">web.stop</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="center">
+ <object class="IdeUrlBar" id="url_bar">
+ <property name="hexpand">true</property>
+ <property name="valign">center</property>
+ <property name="web_view">web_view</property>
+ </object>
+ </child>
+ <child type="end">
+ <object class="GtkMenuButton">
+ <property name="icon-name">open-menu-symbolic</property>
+ <property name="menu-model">primary_menu</property>
+ <property name="direction">up</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="WebKitSettings" id="web_settings"/>
+ <menu id="primary_menu">
+ <section>
+ <attribute name="label" translatable="yes">Settings</attribute>
+ <item>
+ <attribute name="label" translatable="yes">Allow JavaScript</attribute>
+ <attribute name="action">web.enable-javascript</attribute>
+ </item>
+ </section>
+ </menu>
+</interface>
diff --git a/src/libide/webkit/ide-webkit-plugin.c b/src/libide/webkit/ide-webkit-plugin.c
index bbc84b7e0..6f7009c1d 100644
--- a/src/libide/webkit/ide-webkit-plugin.c
+++ b/src/libide/webkit/ide-webkit-plugin.c
@@ -26,16 +26,25 @@
#include <webkit2/webkit2.h>
#include <girepository.h>
+#include <libide-core.h>
+
+#include "ide-webkit-page.h"
+
_IDE_EXTERN void _ide_webkit_register_types (PeasObjectModule *module);
void
_ide_webkit_register_types (PeasObjectModule *module)
{
WebKitWebContext *context;
+ g_autoptr(GError) error = NULL;
g_type_ensure (WEBKIT_TYPE_WEB_VIEW);
- g_irepository_require (NULL, "WebKit2", "4.0", 0, NULL);
+ g_type_ensure (IDE_TYPE_WEBKIT_PAGE);
+
+ if (!g_irepository_require (NULL, "WebKit2", "5.0", 0, &error))
+ g_warning ("%s", error->message);
context = webkit_web_context_get_default ();
webkit_web_context_set_sandbox_enabled (context, TRUE);
+ webkit_web_context_set_favicon_database_directory (context, NULL);
}
diff --git a/src/libide/webkit/ide-webkit-util.c b/src/libide/webkit/ide-webkit-util.c
new file mode 100644
index 000000000..8e8b29dbe
--- /dev/null
+++ b/src/libide/webkit/ide-webkit-util.c
@@ -0,0 +1,280 @@
+/* ide-webkit-util.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 of the License, 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/* Much of the code within this file is derived from numerous files
+ * within the Epiphany web browser. The original copyright is provided
+ * below.
+ */
+/*
+ * Copyright © 2000-2003 Marco Pesenti Gritti
+ * Copyright © 2002 Marco Pesenti Gritti
+ * Copyright © 2003, 2004, 2005 Christian Persch
+ * Copyright © 2004 Crispin Flowerday
+ * Copyright © 2004 Adam Hooper
+ * Copyright © 2008, 2009 Gustavo Noronha Silva
+ * Copyright © 2009, 2010, 2014 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 3 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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-webkit-util"
+
+#include "config.h"
+
+#include <libsoup/soup.h>
+
+#include "ide-webkit-util.h"
+
+#define DOMAIN_REGEX "^localhost(\\.[^[:space:]]+)?(:\\d+)?(:[0-9]+)?(/.*)?$|" \
+ "^[^\\.[:space:]]+\\.[^\\.[:space:]]+.*$|"
+
+static char *
+string_find_and_replace (const char *haystack,
+ const char *to_find,
+ const char *to_repl)
+{
+ GString *str;
+
+ g_assert (haystack);
+ g_assert (to_find);
+ g_assert (to_repl);
+
+ str = g_string_new (haystack);
+ g_string_replace (str, to_find, to_repl, 0);
+ return g_string_free (str, FALSE);
+}
+
+static char *
+string_get_host_name (const char *url)
+{
+ g_autoptr(GUri) uri = NULL;
+
+ if (url == NULL ||
+ g_str_has_prefix (url, "file://") ||
+ g_str_has_prefix (url, "about:"))
+ return NULL;
+
+ uri = g_uri_parse (url, G_URI_FLAGS_NONE, NULL);
+ /* If uri is NULL it's very possible that we just got
+ * something without a scheme, let's try to prepend
+ * 'http://' */
+ if (uri == NULL) {
+ char *effective_url = g_strconcat ("http://", url, NULL);
+ uri = g_uri_parse (effective_url, G_URI_FLAGS_NONE, NULL);
+ g_free (effective_url);
+ }
+
+ if (uri == NULL)
+ return NULL;
+
+ return g_strdup (g_uri_get_host (uri));
+}
+
+static gboolean
+address_has_web_scheme (const char *address)
+{
+ gboolean has_web_scheme;
+ int colonpos;
+
+ if (address == NULL)
+ return FALSE;
+
+ colonpos = (int)((strstr (address, ":")) - address);
+
+ if (colonpos < 0)
+ return FALSE;
+
+ has_web_scheme = !(g_ascii_strncasecmp (address, "http", colonpos) &&
+ g_ascii_strncasecmp (address, "https", colonpos) &&
+ g_ascii_strncasecmp (address, "file", colonpos) &&
+ g_ascii_strncasecmp (address, "javascript", colonpos) &&
+ g_ascii_strncasecmp (address, "data", colonpos) &&
+ g_ascii_strncasecmp (address, "blob", colonpos) &&
+ g_ascii_strncasecmp (address, "about", colonpos) &&
+ g_ascii_strncasecmp (address, "gopher", colonpos) &&
+ g_ascii_strncasecmp (address, "inspector", colonpos) &&
+ g_ascii_strncasecmp (address, "webkit", colonpos));
+
+ return has_web_scheme;
+}
+
+static gboolean
+address_is_existing_absolute_filename (const char *address)
+{
+ g_autofree char *real_address = NULL;
+
+ if (strchr (address, '#') == NULL) {
+ real_address = g_strdup (address);
+ } else {
+ gint pos;
+
+ pos = g_strstr_len (address, -1, "#") - address;
+ real_address = g_strndup (address, pos);
+ }
+
+ return g_path_is_absolute (real_address) &&
+ g_file_test (real_address, G_FILE_TEST_EXISTS);
+}
+
+static gboolean
+is_host_with_port (const char *address)
+{
+ g_auto (GStrv) split = NULL;
+ gint64 port = 0;
+
+ if (strchr (address, ' '))
+ return FALSE;
+
+ split = g_strsplit (address, ":", -1);
+ if (g_strv_length (split) == 2)
+ port = g_ascii_strtoll (split[1], NULL, 10);
+
+ return port != 0;
+}
+
+static char *
+ensure_host_name_is_lowercase (const char *address)
+{
+ g_autofree gchar *host = string_get_host_name (address);
+ g_autofree gchar *lowercase_host = NULL;
+
+ if (host == NULL)
+ return g_strdup (address);
+
+ lowercase_host = g_utf8_strdown (host, -1);
+
+ if (strcmp (host, lowercase_host) != 0)
+ return string_find_and_replace (address, host, lowercase_host);
+ else
+ return g_strdup (address);
+}
+
+
+/* Does various normalization rules to make sure @input_address ends up
+ * with a URI scheme (e.g. absolute filenames or "localhost"), changes
+ * the URI scheme to something more appropriate when needed and lowercases
+ * the hostname.
+ */
+char *
+ide_webkit_util_normalize_address (const char *input_address)
+{
+ char *effective_address = NULL;
+ g_autofree gchar *address = NULL;
+
+ g_return_val_if_fail (input_address != NULL, NULL);
+
+ address = ensure_host_name_is_lowercase (input_address);
+
+ if (address_is_existing_absolute_filename (address))
+ return g_strconcat ("file://", address, NULL);
+
+ if (strcmp (address, "about:gpu") == 0)
+ return g_strdup ("webkit://gpu");
+
+ if (!address_has_web_scheme (address)) {
+ const char *scheme;
+
+ scheme = g_uri_peek_scheme (address);
+
+ /* Auto-prepend http:// to anything that is not
+ * one according to GLib, because it probably will be
+ * something like "google.com". Special case localhost(:port)
+ * and IP(:port), because GUri, correctly, thinks it is a
+ * URI with scheme being localhost/IP and, optionally, path
+ * being the port. Ideally we should check if we have a
+ * handler for the scheme, and since we'll fail for localhost
+ * and IP, we'd fallback to loading it as a domain. */
+ if (!scheme ||
+ !g_strcmp0 (scheme, "localhost") ||
+ g_hostname_is_ip_address (scheme) ||
+ is_host_with_port (address))
+ effective_address = g_strconcat ("http://", address, NULL);
+ }
+
+ return effective_address ? effective_address : g_strdup (address);
+}
+
+static char *
+hostname_to_tld (const char *hostname)
+{
+ g_auto(GStrv) parts = NULL;
+ guint length;
+
+ parts = g_strsplit (hostname, ".", 0);
+ length = g_strv_length (parts);
+
+ if (length >= 1)
+ return g_strdup (parts[length - 1]);
+
+ return g_strdup ("");
+}
+
+IdeWebkitSecurityLevel
+ide_webkit_util_get_security_level (WebKitWebView *web_view)
+{
+ IdeWebkitSecurityLevel security_level;
+ GTlsCertificateFlags tls_errors = 0;
+ WebKitSecurityManager *security_manager;
+ WebKitWebContext *web_context;
+ GTlsCertificate *certificate = NULL;
+ g_autoptr(GUri) guri = NULL;
+ g_autofree char *tld = NULL;
+ const char *uri;
+
+ g_return_val_if_fail (WEBKIT_IS_WEB_VIEW (web_view), 0);
+
+ uri = webkit_web_view_get_uri (web_view);
+ web_context = webkit_web_view_get_context (web_view);
+ security_manager = webkit_web_context_get_security_manager (web_context);
+ guri = g_uri_parse (uri, G_URI_FLAGS_NONE, NULL);
+
+ if (guri && g_uri_get_host (guri))
+ tld = hostname_to_tld (g_uri_get_host (guri));
+
+ if (!guri ||
+ g_strcmp0 (tld, "127.0.0.1") == 0 ||
+ g_strcmp0 (tld, "::1") == 0 ||
+ g_strcmp0 (tld, "localhost") == 0 || /* We trust localhost to be local since glib!616. */
+ webkit_security_manager_uri_scheme_is_local (security_manager, g_uri_get_scheme (guri)) ||
+ webkit_security_manager_uri_scheme_is_empty_document (security_manager, g_uri_get_scheme (guri)))
+ security_level = IDE_WEBKIT_SECURITY_LEVEL_LOCAL_PAGE;
+ else if (webkit_web_view_get_tls_info (web_view, &certificate, &tls_errors))
+ security_level = tls_errors == 0 ?
+ IDE_WEBKIT_SECURITY_LEVEL_STRONG_SECURITY :
IDE_WEBKIT_SECURITY_LEVEL_UNACCEPTABLE_CERTIFICATE;
+ else if (webkit_web_view_is_loading (web_view))
+ security_level = IDE_WEBKIT_SECURITY_LEVEL_TO_BE_DETERMINED;
+ else
+ security_level = IDE_WEBKIT_SECURITY_LEVEL_NONE;
+
+ return security_level;
+}
diff --git a/src/libide/webkit/ide-webkit-util.h b/src/libide/webkit/ide-webkit-util.h
new file mode 100644
index 000000000..859113f00
--- /dev/null
+++ b/src/libide/webkit/ide-webkit-util.h
@@ -0,0 +1,39 @@
+/* ide-webkit-util.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 of the License, 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <webkit2/webkit2.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ IDE_WEBKIT_SECURITY_LEVEL_NONE,
+ IDE_WEBKIT_SECURITY_LEVEL_LOCAL_PAGE,
+ IDE_WEBKIT_SECURITY_LEVEL_STRONG_SECURITY,
+ IDE_WEBKIT_SECURITY_LEVEL_UNACCEPTABLE_CERTIFICATE,
+ IDE_WEBKIT_SECURITY_LEVEL_TO_BE_DETERMINED,
+} IdeWebkitSecurityLevel;
+
+char *ide_webkit_util_normalize_address (const char *input_address);
+IdeWebkitSecurityLevel ide_webkit_util_get_security_level (WebKitWebView *web_view);
+
+G_END_DECLS
diff --git a/src/libide/webkit/libide-webkit.gresource.xml b/src/libide/webkit/libide-webkit.gresource.xml
index f1621dbaa..b665760ee 100644
--- a/src/libide/webkit/libide-webkit.gresource.xml
+++ b/src/libide/webkit/libide-webkit.gresource.xml
@@ -2,5 +2,7 @@
<gresources>
<gresource prefix="/plugins/webkit">
<file>webkit.plugin</file>
+ <file preprocess="xml-stripblanks">ide-webkit-page.ui</file>
+ <file preprocess="xml-stripblanks">ide-url-bar.ui</file>
</gresource>
</gresources>
diff --git a/src/libide/webkit/libide-webkit.h b/src/libide/webkit/libide-webkit.h
new file mode 100644
index 000000000..9bde8d723
--- /dev/null
+++ b/src/libide/webkit/libide-webkit.h
@@ -0,0 +1,27 @@
+/* libide-webkit.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 of the License, 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-gui.h>
+
+#define IDE_WEBKIT_INSIDE
+# include "ide-webkit-page.h"
+#undef IDE_WEBKIT_INSIDE
diff --git a/src/libide/webkit/meson.build b/src/libide/webkit/meson.build
index fce477b36..43011c64a 100644
--- a/src/libide/webkit/meson.build
+++ b/src/libide/webkit/meson.build
@@ -1,10 +1,35 @@
+libwebkit_dep = dependency('webkit2gtk-5.0', required: false)
+
+if libwebkit_dep.found()
+
+libide_webkit_header_dir = join_paths(libide_header_dir, 'webkit')
+libide_webkit_header_subdir = join_paths(libide_header_subdir, 'webkit')
+libide_include_directories += include_directories('.')
#
# Sources
#
-libide_webkit_sources = [
+libide_webkit_private_sources = [
'ide-webkit-plugin.c',
+ 'ide-webkit-util.c',
+ 'ide-text-buffer-html-generator.c',
+ 'ide-url-bar.c',
+]
+
+#
+# Public API Headers
+#
+
+libide_webkit_public_headers = [
+ 'libide-webkit.h',
+ 'ide-html-generator.h',
+ 'ide-webkit-page.h',
+]
+
+libide_webkit_public_sources = [
+ 'ide-html-generator.c',
+ 'ide-webkit-page.c',
]
#
@@ -17,7 +42,8 @@ libide_webkit_resources = gnome.compile_resources(
c_name: 'ide_webkit',
)
libide_webkit_generated_headers = [libide_webkit_resources[1]]
-libide_webkit_sources += libide_webkit_resources
+
+libide_webkit_sources = libide_webkit_resources + libide_webkit_public_sources +
libide_webkit_private_sources
#
# Dependencies
@@ -26,6 +52,7 @@ libide_webkit_sources += libide_webkit_resources
libide_webkit_deps = [
libwebkit_dep,
libpeas_dep,
+ libide_gui_dep,
]
#
@@ -43,3 +70,13 @@ libide_webkit_dep = declare_dependency(
include_directories: include_directories('.'),
sources: libide_webkit_generated_headers,
)
+
+gnome_builder_private_sources += files(libide_webkit_private_sources)
+gnome_builder_public_sources += files(libide_webkit_public_sources)
+gnome_builder_public_headers += files(libide_webkit_public_headers)
+gnome_builder_include_subdirs += libide_webkit_header_subdir
+gnome_builder_gir_extra_args += ['--c-include=libide-webkit.h', '-DIDE_WEBKIT_COMPILATION']
+
+install_headers(libide_webkit_public_headers, subdir: libide_webkit_header_subdir)
+
+endif
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]