unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
       [not found] <87zgqslafe.fsf.ref@yahoo.com>
@ 2021-10-29  4:30 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-10-29 13:18   ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-04 23:11   ` Lars Ingebrigtsen
  0 siblings, 2 replies; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-10-29  4:30 UTC (permalink / raw)
  To: 51473

[-- Attachment #1: Type: text/plain, Size: 684 bytes --]

The attached changes fix flickering xwidgets by moving xwidget display
into a separate X window, and also enable the scrolling optimization for
xwidgets.

Someone with access to a Mac will have to do the changes necessary for
xwidget scrolling to work correctly on macOS, as they don't work on
GNUstep in the first place.

However, event passthrough doesn't work yet, and will require some
thought.  But that would be a fantastic opportunity to clean up the
current xwidget event handling mess (and perhaps even define a mechanism
for Lisp code to send events into xwidgets, which would make integrating
them into the existing Emacs event model much easier.)  Any thoughts?

Thanks.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Use-an-X-window-to-display-xwidgets-on-X11.patch --]
[-- Type: text/x-patch, Size: 17345 bytes --]

From 09f3697b2904dac7616697ba252d93e4748f7418 Mon Sep 17 00:00:00 2001
From: Po Lu <luangruo@yahoo.com>
Date: Thu, 28 Oct 2021 21:46:01 +0800
Subject: [PATCH 1/2] Use an X window to display xwidgets on X11

Since Emacs draws using X11 primitives, Emacs drawing operations can
erase the GtkDrawingArea used to draw the offscreen widget, which
leads to unpleasant flickering effects, all of which can be prevented
by creating a native X window and drawing to that instead.

* src/xterm.c (x_window_to_frame): Return xwidget window if
appropriate.
(handle_one_xevent): Handle xwidget expose events.

* src/xwidget.c (x_window_to_xvw_map): New variable.
(xwidget_view_from_window): New function.

(xwidget_show_view, xwidget_hide_view)
(offscreen_damage_event): Move to drawing xwidgets with X windows.

(xv_do_draw)
(xwidget_expose): New function.

(xwidget_osr_draw_cb, xwidget_osr_event_forward)
(xwidget_osr_event_set_embedder): Removed.

(xwidget_init_view): Replace use of GtkDrawingArea with that of an X
window.

(x_draw_xwidget_glyph_string): Prevent xwidget views from constantly
generating expose events, and use an X window instead of a
GtkDrawingArea.

(syms_of_xwidget): Initialize x_window_to_xwv_map.

src/xwidget.h (struct xwidget_view): Replace GTK widgets with
X-related fields.

(xwidget_view_from_window, xwidget_expose): New functions.
---
 src/xterm.c   |  24 ++++-
 src/xwidget.c | 277 ++++++++++++++++++++++++++------------------------
 src/xwidget.h |  12 ++-
 3 files changed, 175 insertions(+), 138 deletions(-)

diff --git a/src/xterm.c b/src/xterm.c
index aa1a1a5eed..54bfb65bd0 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -4563,8 +4563,9 @@ x_focus_changed (int type, int state, struct x_display_info *dpyinfo, struct fra
     }
 }
 
-/* Return the Emacs frame-object corresponding to an X window.
-   It could be the frame's main window or an icon window.  */
+/* Return the Emacs frame-object corresponding to an X window.  It
+   could be the frame's main window, an icon window, or an xwidget
+   window.  */
 
 static struct frame *
 x_window_to_frame (struct x_display_info *dpyinfo, int wdesc)
@@ -4575,6 +4576,13 @@ x_window_to_frame (struct x_display_info *dpyinfo, int wdesc)
   if (wdesc == None)
     return NULL;
 
+#ifdef HAVE_XWIDGETS
+  struct xwidget_view *xvw = xwidget_view_from_window (wdesc);
+
+  if (xvw && xvw->frame)
+    return xvw->frame;
+#endif
+
   FOR_EACH_FRAME (tail, frame)
     {
       f = XFRAME (frame);
@@ -8211,6 +8219,18 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 
     case Expose:
       f = x_window_to_frame (dpyinfo, event->xexpose.window);
+#ifdef HAVE_XWIDGETS
+      {
+	struct xwidget_view *xv =
+	  xwidget_view_from_window (event->xexpose.window);
+
+	if (xv)
+	  {
+	    xwidget_expose (xv);
+	    goto OTHER;
+	  }
+      }
+#endif
       if (f)
         {
           if (!FRAME_VISIBLE_P (f))
diff --git a/src/xwidget.c b/src/xwidget.c
index e4b42e6e0c..62b30a07ab 100644
--- a/src/xwidget.c
+++ b/src/xwidget.c
@@ -35,10 +35,16 @@ Copyright (C) 2011-2021 Free Software Foundation, Inc.
 #ifdef USE_GTK
 #include <webkit2/webkit2.h>
 #include <JavaScriptCore/JavaScript.h>
+#include <cairo.h>
+#include <X11/Xlib.h>
 #elif defined NS_IMPL_COCOA
 #include "nsxwidget.h"
 #endif
 
+#ifdef USE_GTK
+static Lisp_Object x_window_to_xwv_map;
+#endif
+
 static struct xwidget *
 allocate_xwidget (void)
 {
@@ -222,15 +228,28 @@ xwidget_hidden (struct xwidget_view *xv)
 }
 
 #ifdef USE_GTK
+
+struct xwidget_view *
+xwidget_view_from_window (Window wdesc)
+{
+  Lisp_Object key = make_fixnum (wdesc);
+  Lisp_Object xwv = Fgethash (key, x_window_to_xwv_map, Qnil);
+
+  if (NILP (xwv))
+    return NULL;
+
+  return XXWIDGET_VIEW (xwv);
+}
+
 static void
 xwidget_show_view (struct xwidget_view *xv)
 {
   xv->hidden = false;
-  gtk_widget_show (xv->widgetwindow);
-  gtk_fixed_move (GTK_FIXED (xv->emacswindow),
-                  xv->widgetwindow,
-                  xv->x + xv->clip_left,
-                  xv->y + xv->clip_top);
+  XMoveWindow (xv->dpy, xv->wdesc,
+	       xv->x + xv->clip_left,
+	       xv->y + xv->clip_top);
+  XMapWindow (xv->dpy, xv->wdesc);
+  XFlush (xv->dpy);
 }
 
 /* Hide an xwidget view.  */
@@ -238,28 +257,48 @@ xwidget_show_view (struct xwidget_view *xv)
 xwidget_hide_view (struct xwidget_view *xv)
 {
   xv->hidden = true;
-  gtk_fixed_move (GTK_FIXED (xv->emacswindow), xv->widgetwindow,
-                  10000, 10000);
+  XUnmapWindow (xv->dpy, xv->wdesc);
+  XFlush (xv->dpy);
+}
+
+static void
+xv_do_draw (struct xwidget_view *xw, struct xwidget *w)
+{
+  block_input ();
+
+  cairo_save (xw->cr_context);
+  cairo_translate (xw->cr_context, -xw->clip_left,
+		   -xw->clip_top);
+  gtk_widget_draw (w->widget_osr, xw->cr_context);
+  cairo_restore (xw->cr_context);
+
+  unblock_input ();
 }
 
 /* When the off-screen webkit master view changes this signal is called.
    It copies the bitmap from the off-screen instance.  */
 static gboolean
 offscreen_damage_event (GtkWidget *widget, GdkEvent *event,
-                        gpointer xv_widget)
+                        gpointer xwidget_view)
 {
-  /* Queue a redraw of onscreen widget.
-     There is a guard against receiving an invalid widget,
-     which should only happen if we failed to remove the
-     specific signal handler for the damage event.  */
-  if (GTK_IS_WIDGET (xv_widget))
-    gtk_widget_queue_draw (GTK_WIDGET (xv_widget));
-  else
-    message ("Warning, offscreen_damage_event received invalid xv pointer:%p\n",
-             xv_widget);
+  struct xwidget_view *xw = xwidget_view;
+  struct xwidget *w = XXWIDGET (xw->model);
+
+  if (xw->wdesc == None)
+    return FALSE;
+
+  xv_do_draw (xw, w);
 
   return FALSE;
 }
+
+void
+xwidget_expose (struct xwidget_view *xv)
+{
+  struct xwidget *xw = XXWIDGET (xv->model);
+
+  xv_do_draw (xv, xw);
+}
 #endif /* USE_GTK */
 
 void
@@ -498,51 +537,6 @@ webkit_decide_policy_cb (WebKitWebView *webView,
     return FALSE;
   }
 }
-
-
-/* For gtk3 offscreen rendered widgets.  */
-static gboolean
-xwidget_osr_draw_cb (GtkWidget *widget, cairo_t *cr, gpointer data)
-{
-  struct xwidget *xw = g_object_get_data (G_OBJECT (widget), XG_XWIDGET);
-  struct xwidget_view *xv = g_object_get_data (G_OBJECT (widget),
-                                               XG_XWIDGET_VIEW);
-
-  cairo_rectangle (cr, 0, 0, xv->clip_right, xv->clip_bottom);
-  cairo_clip (cr);
-
-  gtk_widget_draw (xw->widget_osr, cr);
-  return FALSE;
-}
-
-static gboolean
-xwidget_osr_event_forward (GtkWidget *widget, GdkEvent *event,
-			   gpointer user_data)
-{
-  /* Copy events that arrive at the outer widget to the offscreen widget.  */
-  struct xwidget *xw = g_object_get_data (G_OBJECT (widget), XG_XWIDGET);
-  GdkEvent *eventcopy = gdk_event_copy (event);
-  eventcopy->any.window = gtk_widget_get_window (xw->widget_osr);
-
-  /* TODO: This might leak events.  They should be deallocated later,
-     perhaps in xwgir_event_cb.  */
-  gtk_main_do_event (eventcopy);
-
-  /* Don't propagate this event further.  */
-  return TRUE;
-}
-
-static gboolean
-xwidget_osr_event_set_embedder (GtkWidget *widget, GdkEvent *event,
-				gpointer data)
-{
-  struct xwidget_view *xv = data;
-  struct xwidget *xww = XXWIDGET (xv->model);
-  gdk_offscreen_window_set_embedder (gtk_widget_get_window
-				     (xww->widgetwindow_osr),
-                                     gtk_widget_get_window (xv->widget));
-  return FALSE;
-}
 #endif /* USE_GTK */
 
 
@@ -568,63 +562,21 @@ xwidget_init_view (struct xwidget *xww,
   XSETXWIDGET (xv->model, xww);
 
 #ifdef USE_GTK
-  if (EQ (xww->type, Qwebkit))
-    {
-      xv->widget = gtk_drawing_area_new ();
-      /* Expose event handling.  */
-      gtk_widget_set_app_paintable (xv->widget, TRUE);
-      gtk_widget_add_events (xv->widget, GDK_ALL_EVENTS_MASK);
-
-      /* Draw the view on damage-event.  */
-      g_signal_connect (G_OBJECT (xww->widgetwindow_osr), "damage-event",
-                        G_CALLBACK (offscreen_damage_event), xv->widget);
-
-      if (EQ (xww->type, Qwebkit))
-        {
-          g_signal_connect (G_OBJECT (xv->widget), "button-press-event",
-                            G_CALLBACK (xwidget_osr_event_forward), NULL);
-          g_signal_connect (G_OBJECT (xv->widget), "button-release-event",
-                            G_CALLBACK (xwidget_osr_event_forward), NULL);
-          g_signal_connect (G_OBJECT (xv->widget), "motion-notify-event",
-                            G_CALLBACK (xwidget_osr_event_forward), NULL);
-        }
-      else
-        {
-          /* xwgir debug, orthogonal to forwarding.  */
-          g_signal_connect (G_OBJECT (xv->widget), "enter-notify-event",
-                            G_CALLBACK (xwidget_osr_event_set_embedder), xv);
-        }
-      g_signal_connect (G_OBJECT (xv->widget), "draw",
-                        G_CALLBACK (xwidget_osr_draw_cb), NULL);
-    }
+  xv->dpy = FRAME_X_DISPLAY (s->f);
 
-  /* Widget realization.
-
-     Make container widget first, and put the actual widget inside the
-     container later.  Drawing should crop container window if necessary
-     to handle case where xwidget is partially obscured by other Emacs
-     windows.  Other containers than gtk_fixed where explored, but
-     gtk_fixed had the most predictable behavior so far.  */
-
-  xv->emacswindow = FRAME_GTK_WIDGET (s->f);
-  xv->widgetwindow = gtk_fixed_new ();
-  gtk_widget_set_has_window (xv->widgetwindow, TRUE);
-  gtk_container_add (GTK_CONTAINER (xv->widgetwindow), xv->widget);
-
-  /* Store some xwidget data in the gtk widgets.  */
-  g_object_set_data (G_OBJECT (xv->widget), XG_FRAME_DATA, s->f);
-  g_object_set_data (G_OBJECT (xv->widget), XG_XWIDGET, xww);
-  g_object_set_data (G_OBJECT (xv->widget), XG_XWIDGET_VIEW, xv);
-  g_object_set_data (G_OBJECT (xv->widgetwindow), XG_XWIDGET, xww);
-  g_object_set_data (G_OBJECT (xv->widgetwindow), XG_XWIDGET_VIEW, xv);
-
-  gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xww->width,
-                               xww->height);
-  gtk_widget_set_size_request (xv->widgetwindow, xww->width, xww->height);
-  gtk_fixed_put (GTK_FIXED (FRAME_GTK_WIDGET (s->f)), xv->widgetwindow, x, y);
   xv->x = x;
   xv->y = y;
-  gtk_widget_show_all (xv->widgetwindow);
+
+  xv->clip_left = 0;
+  xv->clip_right = xww->width;
+  xv->clip_top = 0;
+  xv->clip_bottom = xww->height;
+
+  xv->wdesc = None;
+  xv->frame = s->f;
+
+  g_signal_connect (G_OBJECT (xww->widgetwindow_osr), "damage-event",
+		    G_CALLBACK (offscreen_damage_event), xv);
 #elif defined NS_IMPL_COCOA
   nsxwidget_init_view (xv, xww, s, x, y);
   nsxwidget_resize_view(xv, xww->width, xww->height);
@@ -681,6 +633,8 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
   window_box (s->w, TEXT_AREA, &text_area_x, &text_area_y,
               &text_area_width, &text_area_height);
 
+  /* On X11, this keeps generating expose events.  */
+#ifndef USE_GTK
   /* Resize xwidget webkit if its container window size is changed in
      some ways, for example, a buffer became hidden in small split
      window, then it can appear front in merged whole window.  */
@@ -693,6 +647,7 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
                        make_int (text_area_width),
                        make_int (text_area_height));
     }
+#endif
 
   clip_left = max (0, text_area_x - x);
   clip_right = max (clip_left,
@@ -711,15 +666,45 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
      later.  */
   bool moved = (xv->x + xv->clip_left != x + clip_left
 		|| xv->y + xv->clip_top != y + clip_top);
+
+#ifdef USE_GTK
+  bool wdesc_was_none = xv->wdesc == None;
+#endif
   xv->x = x;
   xv->y = y;
 
+#ifdef USE_GTK
+  block_input ();
+  if (xv->wdesc == None)
+    {
+      Lisp_Object xvw;
+      XSETXWIDGET_VIEW (xvw, xv);
+      XSetWindowAttributes a;
+      a.event_mask = ExposureMask;
+
+      xv->wdesc = XCreateWindow (xv->dpy, FRAME_X_WINDOW (s->f),
+				 x + clip_left, y + clip_top,
+				 clip_right - clip_left,
+				 clip_bottom - clip_top, 0,
+				 CopyFromParent, CopyFromParent,
+				 CopyFromParent, CWEventMask, &a);
+      xv->cr_surface = cairo_xlib_surface_create (xv->dpy,
+						  xv->wdesc,
+						  FRAME_DISPLAY_INFO (s->f)->visual,
+						  clip_right - clip_left,
+						  clip_bottom - clip_top);
+      xv->cr_context = cairo_create (xv->cr_surface);
+      Fputhash (make_fixnum (xv->wdesc), xvw, x_window_to_xwv_map);
+
+      moved = false;
+    }
+#endif
+
   /* Has it moved?  */
   if (moved)
     {
 #ifdef USE_GTK
-      gtk_fixed_move (GTK_FIXED (FRAME_GTK_WIDGET (s->f)),
-                      xv->widgetwindow, x + clip_left, y + clip_top);
+      XMoveWindow (xv->dpy, xv->wdesc, x + clip_left, y + clip_top);
 #elif defined NS_IMPL_COCOA
       nsxwidget_move_view (xv, x + clip_left, y + clip_top);
 #endif
@@ -735,10 +720,14 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
       || xv->clip_top != clip_top || xv->clip_left != clip_left)
     {
 #ifdef USE_GTK
-      gtk_widget_set_size_request (xv->widgetwindow, clip_right - clip_left,
-                                   clip_bottom - clip_top);
-      gtk_fixed_move (GTK_FIXED (xv->widgetwindow), xv->widget, -clip_left,
-                      -clip_top);
+      if (!wdesc_was_none)
+	{
+	  XResizeWindow (xv->dpy, xv->wdesc, clip_right - clip_left,
+			 clip_bottom - clip_top);
+	  XFlush (xv->dpy);
+	  cairo_xlib_surface_set_size (xv->cr_surface, clip_right - clip_left,
+				       clip_bottom - clip_top);
+	}
 #elif defined NS_IMPL_COCOA
       nsxwidget_resize_view (xv, clip_right - clip_left,
                              clip_bottom - clip_top);
@@ -758,12 +747,15 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
   if (!xwidget_hidden (xv))
     {
 #ifdef USE_GTK
-      gtk_widget_queue_draw (xv->widgetwindow);
-      gtk_widget_queue_draw (xv->widget);
+      xv_do_draw (xv, xww);
 #elif defined NS_IMPL_COCOA
       nsxwidget_set_needsdisplay (xv);
 #endif
     }
+
+#ifdef USE_GTK
+  unblock_input ();
+#endif
 }
 
 static bool
@@ -976,8 +968,13 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
           if (XXWIDGET (xv->model) == xw)
             {
 #ifdef USE_GTK
-              gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xw->width,
-                                           xw->height);
+	      if (xv->wdesc != None)
+		{
+		  XResizeWindow (xv->dpy, xv->wdesc, xw->width, xw->height);
+		  XFlush (xv->dpy);
+		  cairo_xlib_surface_set_size (xv->cr_surface,
+					       xw->width, xw->height);
+		}
 #elif defined NS_IMPL_COCOA
               nsxwidget_resize_view(xv, xw->width, xw->height);
 #endif
@@ -1084,13 +1081,21 @@ DEFUN ("delete-xwidget-view",
   CHECK_XWIDGET_VIEW (xwidget_view);
   struct xwidget_view *xv = XXWIDGET_VIEW (xwidget_view);
 #ifdef USE_GTK
-  gtk_widget_destroy (xv->widgetwindow);
-  /* xv->model still has signals pointing to the view.  There can be
-     several views.  Find the matching signals and delete them all.  */
-  g_signal_handlers_disconnect_matched  (XXWIDGET (xv->model)->widgetwindow_osr,
-                                         G_SIGNAL_MATCH_DATA,
-                                         0, 0, 0, 0,
-                                         xv->widget);
+  if (xv->wdesc != None)
+    {
+      block_input ();
+      XDestroyWindow (xv->dpy, xv->wdesc);
+      /* xv->model still has signals pointing to the view.  There can be
+	 several views.  Find the matching signals and delete them all.  */
+      g_signal_handlers_disconnect_matched  (XXWIDGET (xv->model)->widgetwindow_osr,
+					     G_SIGNAL_MATCH_DATA,
+					     0, 0, 0, 0, xv);
+
+      cairo_destroy (xv->cr_context);
+      cairo_surface_destroy (xv->cr_surface);
+      Fremhash (make_fixnum (xv->wdesc), x_window_to_xwv_map);
+      unblock_input ();
+    }
 #elif defined NS_IMPL_COCOA
   nsxwidget_delete_view (xv);
 #endif
@@ -1236,6 +1241,12 @@ syms_of_xwidget (void)
   Vxwidget_view_list = Qnil;
 
   Fprovide (intern ("xwidget-internal"), Qnil);
+
+#ifdef USE_GTK
+  x_window_to_xwv_map = CALLN (Fmake_hash_table, QCtest, Qeq);
+
+  staticpro (&x_window_to_xwv_map);
+#endif
 }
 
 
diff --git a/src/xwidget.h b/src/xwidget.h
index 591f23489d..fc68b52cdb 100644
--- a/src/xwidget.h
+++ b/src/xwidget.h
@@ -32,6 +32,7 @@ #define XWIDGET_H_INCLUDED
 
 #if defined (USE_GTK)
 #include <gtk/gtk.h>
+#include <X11/Xlib.h>
 #elif defined (NS_IMPL_COCOA) && defined (__OBJC__)
 #import <AppKit/NSView.h>
 #import "nsxwidget.h"
@@ -98,9 +99,12 @@ #define XWIDGET_H_INCLUDED
   bool hidden;
 
 #if defined (USE_GTK)
-  GtkWidget *widget;
-  GtkWidget *widgetwindow;
-  GtkWidget *emacswindow;
+  Display *dpy;
+  Window wdesc;
+  struct frame *frame;
+
+  cairo_surface_t *cr_surface;
+  cairo_t *cr_context;
 #elif defined (NS_IMPL_COCOA)
 # ifdef __OBJC__
   XvWindow *xvWindow;
@@ -162,6 +166,8 @@ #define XG_XWIDGET_VIEW "emacs_xwidget_view"
 void store_xwidget_js_callback_event (struct xwidget *xw,
                                       Lisp_Object proc,
                                       Lisp_Object argument);
+struct xwidget_view *xwidget_view_from_window (Window wdesc);
+void xwidget_expose (struct xwidget_view *xv);
 #else
 INLINE_HEADER_BEGIN
 INLINE void syms_of_xwidget (void) {}
-- 
2.31.1


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-Enable-scrolling-optimization-for-xwidgets.patch --]
[-- Type: text/x-patch, Size: 8699 bytes --]

From fda1685eb81d0a8b7fe19966572913b504606d9d Mon Sep 17 00:00:00 2001
From: Po Lu <luangruo@yahoo.com>
Date: Fri, 29 Oct 2021 11:33:06 +0800
Subject: [PATCH 2/2] Enable scrolling optimization for xwidgets

* src/dispextern.h (struct glyph): Store xwidget ID instead of a
reference.
* src/dispnew.c (scrolling_window): Enable scrolling optimization on
xwidget builds.
* src/xdisp.c (fill_xwidget_glyph_string, produce_xwidget_glyph):
Obtain xwidget from ID.
* src/xterm.c (x_scroll_run): Scroll xwidget windows.
* src/xwidget.c (id_to_xwidget_map, xwidget_counter): New xwidget
variables.
(Fmake_xwidget): Assign each xwidget a unique ID, and keep track of
that ID.
(xwidget_from_id): New function.
(syms_of_xwidget): Initialize id_to_xwidget_map.
(xwidget_end_redisplay): Lookup xwidgets via ID.
* src/xwidget.h (struct xwidget): Add ID field.
(xwidget_from_id): New function.
---
 src/dispextern.h |  4 +--
 src/dispnew.c    | 10 ------
 src/xdisp.c      |  4 +--
 src/xterm.c      | 82 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/xwidget.c    | 24 +++++++++++++-
 src/xwidget.h    |  3 ++
 6 files changed, 112 insertions(+), 15 deletions(-)

diff --git a/src/dispextern.h b/src/dispextern.h
index 08dac5d455..218675b021 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -536,8 +536,8 @@ #define FACE_ID_BITS	20
     int img_id;
 
 #ifdef HAVE_XWIDGETS
-    /* Xwidget reference (type == XWIDGET_GLYPH).  */
-    struct xwidget *xwidget;
+    /* Xwidget ID.  */
+    uint32_t xwidget;
 #endif
 
     /* Sub-structure for type == STRETCH_GLYPH.  */
diff --git a/src/dispnew.c b/src/dispnew.c
index c3f6d0bfef..2e0b4801da 100644
--- a/src/dispnew.c
+++ b/src/dispnew.c
@@ -4447,16 +4447,6 @@ scrolling_window (struct window *w, int tab_line_p)
 	break;
     }
 
-#ifdef HAVE_XWIDGETS
-  /* Currently this seems needed to detect xwidget movement reliably.
-     This is most probably because an xwidget glyph is represented in
-     struct glyph's 'union u' by a pointer to a struct, which takes 8
-     bytes in 64-bit builds, and thus the comparison of u.val values
-     done by GLYPH_EQUAL_P doesn't work reliably, since it assumes the
-     size of the union is 4 bytes.  FIXME.  */
-    return 0;
-#endif
-
   /* Can't scroll the display of w32 GUI frames when position of point
      is indicated by the system caret, because scrolling the display
      will then "copy" the pixels used by the caret.  */
diff --git a/src/xdisp.c b/src/xdisp.c
index aa01db210b..198bfc06a4 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -28425,7 +28425,7 @@ fill_xwidget_glyph_string (struct glyph_string *s)
     }
   s->width = s->first_glyph->pixel_width;
   s->ybase += s->first_glyph->voffset;
-  s->xwidget = s->first_glyph->u.xwidget;
+  s->xwidget = xwidget_from_id (s->first_glyph->u.xwidget);
 }
 #endif
 /* Fill glyph string S from a sequence of stretch glyphs.
@@ -29830,7 +29830,7 @@ produce_xwidget_glyph (struct it *it)
           glyph->padding_p = 0;
 	  glyph->glyph_not_available_p = 0;
 	  glyph->face_id = it->face_id;
-          glyph->u.xwidget = it->xwidget;
+          glyph->u.xwidget = it->xwidget->xwidget_id;
 	  glyph->font_type = FONT_TYPE_UNKNOWN;
 	  if (it->bidi_p)
 	    {
diff --git a/src/xterm.c b/src/xterm.c
index 54bfb65bd0..b12c15cb7a 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -4390,6 +4390,88 @@ x_scroll_run (struct window *w, struct run *run)
   /* Cursor off.  Will be switched on again in gui_update_window_end.  */
   gui_clear_cursor (w);
 
+#ifdef HAVE_XWIDGETS
+  /* "Copy" xwidget windows in the area that will be scrolled.  */
+  Display *dpy = FRAME_X_DISPLAY (f);
+  Window window = FRAME_X_WINDOW (f);
+
+  Window root, parent, *children;
+  unsigned int nchildren;
+
+  if (XQueryTree (dpy, window, &root, &parent, &children, &nchildren))
+    {
+      /* Now find xwidget views situated between from_y and to_y, and
+	 attached to w.  */
+      for (unsigned int i = 0; i < nchildren; ++i)
+	{
+	  Window child = children[i];
+	  struct xwidget_view *view = xwidget_view_from_window (child);
+
+	  if (view)
+	    {
+	      int window_y = view->y + view->clip_top;
+	      int window_height = view->clip_bottom - view->clip_top;
+	      int min_y = min (from_y, to_y);
+	      int max_y = max (from_y, to_y);
+
+	      Emacs_Rectangle r1, r2, result;
+	      r1.x = w->pixel_left;
+	      r1.y = min_y;
+	      r1.width = w->pixel_width;
+	      r1.height = max_y - min_y;
+	      r2 = r1;
+	      r2.y = window_y;
+	      r2.height = window_height;
+
+	      /* The window is offscreen, just unmap it.  */
+	      if (window_height == 0)
+		{
+		  view->hidden = true;
+		  XUnmapWindow (dpy, child);
+		  continue;
+		}
+
+	      bool intersects_p =
+		gui_intersect_rectangles (&r1, &r2, &result);
+
+	      if (XWINDOW (view->w) == w && intersects_p)
+		{
+		  int y = view->y + (to_y - from_y);
+		  int text_area_x, text_area_y, text_area_width, text_area_height;
+		  int clip_top, clip_bottom;
+
+		  window_box (w, TEXT_AREA, &text_area_x, &text_area_y,
+			      &text_area_width, &text_area_height);
+
+		  clip_top = max (0, text_area_y - y);
+		  clip_bottom = max (clip_top,
+				     min (XXWIDGET (view->model)->height,
+					  text_area_y + text_area_height - y));
+
+		  view->y = y;
+		  view->clip_top = clip_top;
+		  view->clip_bottom = clip_bottom;
+
+		  /* This means the view has moved offscreen.  Unmap
+		     it and hide it here.  */
+		  if ((view->clip_top - view->clip_bottom) <= 0)
+		    {
+		      view->hidden = true;
+		      XUnmapWindow (dpy, child);
+		    }
+		  else
+		    XMoveResizeWindow (dpy, child, view->x + view->clip_left,
+				       view->y + view->clip_top,
+				       view->clip_right - view->clip_left,
+				       view->clip_top - view->clip_bottom);
+		  XFlush (dpy);
+		}
+            }
+	}
+      XFree (children);
+    }
+#endif
+
 #ifdef USE_CAIRO
   if (FRAME_CR_CONTEXT (f))
     {
diff --git a/src/xwidget.c b/src/xwidget.c
index 62b30a07ab..68188eba08 100644
--- a/src/xwidget.c
+++ b/src/xwidget.c
@@ -41,6 +41,9 @@ Copyright (C) 2011-2021 Free Software Foundation, Inc.
 #include "nsxwidget.h"
 #endif
 
+static Lisp_Object id_to_xwidget_map;
+static uint32_t xwidget_counter = 0;
+
 #ifdef USE_GTK
 static Lisp_Object x_window_to_xwv_map;
 #endif
@@ -114,6 +117,9 @@ DEFUN ("make-xwidget",
   XSETXWIDGET (val, xw);
   Vxwidget_list = Fcons (val, Vxwidget_list);
   xw->plist = Qnil;
+  xw->xwidget_id = ++xwidget_counter;
+
+  Fputhash (make_fixnum (xw->xwidget_id), val, id_to_xwidget_map);
 
 #ifdef USE_GTK
   xw->widgetwindow_osr = NULL;
@@ -227,6 +233,18 @@ xwidget_hidden (struct xwidget_view *xv)
   return xv->hidden;
 }
 
+struct xwidget *
+xwidget_from_id (uint32_t id)
+{
+  Lisp_Object key = make_fixnum (id);
+  Lisp_Object xwidget = Fgethash (key, id_to_xwidget_map, Qnil);
+
+  if (NILP (xwidget))
+    emacs_abort ();
+
+  return XXWIDGET (xwidget);
+}
+
 #ifdef USE_GTK
 
 struct xwidget_view *
@@ -1242,6 +1260,9 @@ syms_of_xwidget (void)
 
   Fprovide (intern ("xwidget-internal"), Qnil);
 
+  id_to_xwidget_map = CALLN (Fmake_hash_table, QCtest, Qeq);
+  staticpro (&id_to_xwidget_map);
+
 #ifdef USE_GTK
   x_window_to_xwv_map = CALLN (Fmake_hash_table, QCtest, Qeq);
 
@@ -1385,7 +1406,7 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
 		  /* The only call to xwidget_end_redisplay is in dispnew.
 		     xwidget_end_redisplay (w->current_matrix);  */
 		  struct xwidget_view *xv
-		    = xwidget_view_lookup (glyph->u.xwidget, w);
+		    = xwidget_view_lookup (xwidget_from_id (glyph->u.xwidget), w);
 #ifdef USE_GTK
 		  /* FIXME: Is it safe to assume xwidget_view_lookup
 		     always succeeds here?  If so, this comment can be removed.
@@ -1448,6 +1469,7 @@ kill_buffer_xwidgets (Lisp_Object buffer)
       {
         CHECK_XWIDGET (xwidget);
         struct xwidget *xw = XXWIDGET (xwidget);
+	Fremhash (make_fixnum (xw->xwidget_id), id_to_xwidget_map);
 #ifdef USE_GTK
         if (xw->widget_osr && xw->widgetwindow_osr)
           {
diff --git a/src/xwidget.h b/src/xwidget.h
index fc68b52cdb..28098c0b09 100644
--- a/src/xwidget.h
+++ b/src/xwidget.h
@@ -60,6 +60,7 @@ #define XWIDGET_H_INCLUDED
 
   int height;
   int width;
+  uint32_t xwidget_id;
 
 #if defined (USE_GTK)
   /* For offscreen widgets, unused if not osr.  */
@@ -168,6 +169,8 @@ #define XG_XWIDGET_VIEW "emacs_xwidget_view"
                                       Lisp_Object argument);
 struct xwidget_view *xwidget_view_from_window (Window wdesc);
 void xwidget_expose (struct xwidget_view *xv);
+
+extern struct xwidget *xwidget_from_id (uint32_t id);
 #else
 INLINE_HEADER_BEGIN
 INLINE void syms_of_xwidget (void) {}
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-10-29  4:30 ` bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-10-29 13:18   ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-04 23:11   ` Lars Ingebrigtsen
  1 sibling, 0 replies; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-10-29 13:18 UTC (permalink / raw)
  To: 51473

[-- Attachment #1: Type: text/plain, Size: 849 bytes --]

Po Lu <luangruo@yahoo.com> writes:

> The attached changes fix flickering xwidgets by moving xwidget display
> into a separate X window, and also enable the scrolling optimization for
> xwidgets.
>
> Someone with access to a Mac will have to do the changes necessary for
> xwidget scrolling to work correctly on macOS, as they don't work on
> GNUstep in the first place.
>
> However, event passthrough doesn't work yet, and will require some
> thought.  But that would be a fantastic opportunity to clean up the
> current xwidget event handling mess (and perhaps even define a mechanism
> for Lisp code to send events into xwidgets, which would make integrating
> them into the existing Emacs event model much easier.)  Any thoughts?
>
> Thanks.

And I missed something, you will also need the following patch for
everything to function correctly:


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0003-Destroy-xwidgets-when-destroying-frames.patch --]
[-- Type: text/x-patch, Size: 4692 bytes --]

From b490315aad58c704589fbb753de3238ce0d08819 Mon Sep 17 00:00:00 2001
From: Po Lu <luangruo@yahoo.com>
Date: Fri, 29 Oct 2021 19:26:49 +0800
Subject: [PATCH 3/3] Destroy xwidgets when destroying frames

* src/xterm.c (x_free_frame_resources): Make sure to kill xwidget views.
* src/xwidget.c (Fmake_xwidget): Attach damage event signal.
(offscreen_damage_event): Operate on xwidgets and not individual
views.
(xwidget_init_view): Don't attach damage event signal here.
(Fdelete_xwidget_view): Destroy window correctly and stop removing
damage event signal.
(kill_frame_xwidget_views): New function.
* src/xwidget.c (kill_frame_xwidget_views): New function.
---
 src/xterm.c   |  4 ++++
 src/xwidget.c | 52 +++++++++++++++++++++++++++++++++++----------------
 src/xwidget.h |  1 +
 3 files changed, 41 insertions(+), 16 deletions(-)

diff --git a/src/xterm.c b/src/xterm.c
index b12c15cb7a..3e8cfb8b29 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -12210,6 +12210,10 @@ x_free_frame_resources (struct frame *f)
 	xfree (f->shell_position);
 #else  /* !USE_X_TOOLKIT */
 
+#ifdef HAVE_XWIDGETS
+      kill_frame_xwidget_views (f);
+#endif
+
 #ifdef USE_GTK
       xg_free_frame_widgets (f);
 #endif /* USE_GTK */
diff --git a/src/xwidget.c b/src/xwidget.c
index 68188eba08..fe6640171c 100644
--- a/src/xwidget.c
+++ b/src/xwidget.c
@@ -46,6 +46,7 @@ Copyright (C) 2011-2021 Free Software Foundation, Inc.
 
 #ifdef USE_GTK
 static Lisp_Object x_window_to_xwv_map;
+static gboolean offscreen_damage_event (GtkWidget *, GdkEvent *, gpointer);
 #endif
 
 static struct xwidget *
@@ -193,6 +194,9 @@ DEFUN ("make-xwidget",
                             xw);
         }
 
+      g_signal_connect (G_OBJECT (xw->widgetwindow_osr), "damage-event",
+			G_CALLBACK (offscreen_damage_event), xw);
+
       unblock_input ();
     }
 #elif defined NS_IMPL_COCOA
@@ -297,15 +301,20 @@ xv_do_draw (struct xwidget_view *xw, struct xwidget *w)
    It copies the bitmap from the off-screen instance.  */
 static gboolean
 offscreen_damage_event (GtkWidget *widget, GdkEvent *event,
-                        gpointer xwidget_view)
+                        gpointer xwidget)
 {
-  struct xwidget_view *xw = xwidget_view;
-  struct xwidget *w = XXWIDGET (xw->model);
+  block_input ();
 
-  if (xw->wdesc == None)
-    return FALSE;
+  for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail);
+       tail = XCDR (tail))
+    {
+      struct xwidget_view *view = XXWIDGET_VIEW (XCAR (tail));
+
+      if (view->wdesc && XXWIDGET (view->model) == xwidget)
+	xv_do_draw (view, XXWIDGET (view->model));
+    }
 
-  xv_do_draw (xw, w);
+  unblock_input ();
 
   return FALSE;
 }
@@ -592,9 +601,6 @@ xwidget_init_view (struct xwidget *xww,
 
   xv->wdesc = None;
   xv->frame = s->f;
-
-  g_signal_connect (G_OBJECT (xww->widgetwindow_osr), "damage-event",
-		    G_CALLBACK (offscreen_damage_event), xv);
 #elif defined NS_IMPL_COCOA
   nsxwidget_init_view (xv, xww, s, x, y);
   nsxwidget_resize_view(xv, xww->width, xww->height);
@@ -1102,15 +1108,9 @@ DEFUN ("delete-xwidget-view",
   if (xv->wdesc != None)
     {
       block_input ();
-      XDestroyWindow (xv->dpy, xv->wdesc);
-      /* xv->model still has signals pointing to the view.  There can be
-	 several views.  Find the matching signals and delete them all.  */
-      g_signal_handlers_disconnect_matched  (XXWIDGET (xv->model)->widgetwindow_osr,
-					     G_SIGNAL_MATCH_DATA,
-					     0, 0, 0, 0, xv);
-
       cairo_destroy (xv->cr_context);
       cairo_surface_destroy (xv->cr_surface);
+      XDestroyWindow (xv->dpy, xv->wdesc);
       Fremhash (make_fixnum (xv->wdesc), x_window_to_xwv_map);
       unblock_input ();
     }
@@ -1456,6 +1456,26 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
     }
 }
 
+#ifdef USE_GTK
+void
+kill_frame_xwidget_views (struct frame *f)
+{
+  Lisp_Object rem = Qnil;
+
+  for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail);
+       tail = XCDR (tail))
+    {
+      if (XXWIDGET_VIEW (XCAR (tail))->frame == f)
+	rem = Fcons (XCAR (tail), rem);
+    }
+
+  for (; CONSP (rem); rem = XCDR (rem))
+    {
+      Fdelete_xwidget_view (XCAR (rem));
+    }
+}
+#endif
+
 /* Kill all xwidget in BUFFER.  */
 void
 kill_buffer_xwidgets (Lisp_Object buffer)
diff --git a/src/xwidget.h b/src/xwidget.h
index 28098c0b09..f51921dbef 100644
--- a/src/xwidget.h
+++ b/src/xwidget.h
@@ -171,6 +171,7 @@ #define XG_XWIDGET_VIEW "emacs_xwidget_view"
 void xwidget_expose (struct xwidget_view *xv);
 
 extern struct xwidget *xwidget_from_id (uint32_t id);
+extern void kill_frame_xwidget_views (struct frame *f);
 #else
 INLINE_HEADER_BEGIN
 INLINE void syms_of_xwidget (void) {}
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-10-29  4:30 ` bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-10-29 13:18   ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-04 23:11   ` Lars Ingebrigtsen
  2021-11-05  7:20     ` Eli Zaretskii
  2021-11-05  7:34     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 2 replies; 39+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-04 23:11 UTC (permalink / raw)
  To: Po Lu; +Cc: 51473

Po Lu <luangruo@yahoo.com> writes:

> The attached changes fix flickering xwidgets by moving xwidget display
> into a separate X window, and also enable the scrolling optimization for
> xwidgets.

Sounds good...

> Someone with access to a Mac will have to do the changes necessary for
> xwidget scrolling to work correctly on macOS, as they don't work on
> GNUstep in the first place.

Does this mean that these patches will break the Macos build?

> However, event passthrough doesn't work yet, and will require some
> thought.  But that would be a fantastic opportunity to clean up the
> current xwidget event handling mess (and perhaps even define a mechanism
> for Lisp code to send events into xwidgets, which would make integrating
> them into the existing Emacs event model much easier.)  Any thoughts?

I'm not really that familiar with how xwidget works, so er does this
mean that the events work less with your patch than before?

I'm unable to test this at all, because:

larsi@elva:~/src/emacs/trunk$ emake;  ./src/emacs -geometry -0+0  
Overriding existing handler for signal 10. Set JSC_SIGNAL_FOR_GC if you want WebKit to use a different signal

** (emacs:2173922): ERROR **: 00:09:01.997: GApplication is required for xdg-desktop-portal access in the WebKit sandbox.
Fatal error 5: Trace/breakpoint trap


This is on Debian/bookwork.  (And without your patch, just building with
xwidget and `M-x xwidget-webkit-browse-url RET fsf.org RET', and Emacs
crashes.)  

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-04 23:11   ` Lars Ingebrigtsen
@ 2021-11-05  7:20     ` Eli Zaretskii
  2021-11-05  7:29       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-05 13:35       ` Lars Ingebrigtsen
  2021-11-05  7:34     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 2 replies; 39+ messages in thread
From: Eli Zaretskii @ 2021-11-05  7:20 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: luangruo, 51473

> From: Lars Ingebrigtsen <larsi@gnus.org>
> Date: Fri, 05 Nov 2021 00:11:58 +0100
> Cc: 51473@debbugs.gnu.org
> 
> (And without your patch, just building with
> xwidget and `M-x xwidget-webkit-browse-url RET fsf.org RET', and Emacs
> crashes.)  

Sounds like a serious problem.  Any idea why it crashes?  What does
the backtrace say?





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-05  7:20     ` Eli Zaretskii
@ 2021-11-05  7:29       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-05  7:33         ` Lars Ingebrigtsen
  2021-11-05 13:35       ` Lars Ingebrigtsen
  1 sibling, 1 reply; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-05  7:29 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Lars Ingebrigtsen, 51473

Eli Zaretskii <eliz@gnu.org> writes:

> Sounds like a serious problem.  Any idea why it crashes?  What does
> the backtrace say?

Lars, are you using Fedora 34 by any chance?  The WebKitGTK 2 package
there has some kind of bug that also affects the GNOME Yelp viewer, not
just Emacs.

I realize that I've forgotten to update this issue with a few
improvements I've made though.  I'll send them soon, it would be nice if
someone took a look at them afer that.

Thanks.





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-05  7:29       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-05  7:33         ` Lars Ingebrigtsen
  2021-11-05  7:47           ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 39+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-05  7:33 UTC (permalink / raw)
  To: Po Lu; +Cc: 51473

Po Lu <luangruo@yahoo.com> writes:

> Lars, are you using Fedora 34 by any chance?  The WebKitGTK 2 package
> there has some kind of bug that also affects the GNOME Yelp viewer, not
> just Emacs.

No, I'm using Debian/bookworm.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-04 23:11   ` Lars Ingebrigtsen
  2021-11-05  7:20     ` Eli Zaretskii
@ 2021-11-05  7:34     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-05 13:10       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-05 13:28       ` Lars Ingebrigtsen
  1 sibling, 2 replies; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-05  7:34 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: 51473

Lars Ingebrigtsen <larsi@gnus.org> writes:

> Sounds good...
>
>> Someone with access to a Mac will have to do the changes necessary for
>> xwidget scrolling to work correctly on macOS, as they don't work on
>> GNUstep in the first place.
>
> Does this mean that these patches will break the Macos build?

I tried my best to keep it working, but the scroll optimization will not
function there.

> I'm not really that familiar with how xwidget works, so er does this
> mean that the events work less with your patch than before?

Yes, but I fixed that, and defined a new facility for sending events to
xwidgets from Lisp code in a few other changes.  Should I squash them or
send the new commits as individual patches?  Thanks.

> I'm unable to test this at all, because:

> ** (emacs:2173922): ERROR **: 00:09:01.997: GApplication is required for xdg-desktop-portal access in the WebKit sandbox.
> Fatal error 5: Trace/breakpoint trap

> This is on Debian/bookwork.  (And without your patch, just building with
> xwidget and `M-x xwidget-webkit-browse-url RET fsf.org RET', and Emacs
> crashes.)  

This is probably the same WebKitGtk bug that also happens on Fedora.
Can you tell me the version of your WebKitGtk package and GLib?

If you can't upgrade/downgrade, continually retrying could help.





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-05  7:33         ` Lars Ingebrigtsen
@ 2021-11-05  7:47           ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 0 replies; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-05  7:47 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: Eli Zaretskii, 51473

Lars Ingebrigtsen <larsi@gnus.org> writes:

> No, I'm using Debian/bookworm.

Thanks, I tried to explain the situation better in my reply to your
message.





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-05  7:34     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-05 13:10       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-05 13:32         ` Lars Ingebrigtsen
  2021-11-05 13:28       ` Lars Ingebrigtsen
  1 sibling, 1 reply; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-05 13:10 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: 51473

[-- Attachment #1: Type: text/plain, Size: 304 bytes --]

Po Lu <luangruo@yahoo.com> writes:

> Yes, but I fixed that, and defined a new facility for sending events to
> xwidgets from Lisp code in a few other changes.  Should I squash them or
> send the new commits as individual patches?  Thanks.

Also, if you want to try out the enhancements, here they are:


[-- Attachment #2: patches.tar.gz --]
[-- Type: application/gzip, Size: 25286 bytes --]

[-- Attachment #3: Type: text/plain, Size: 85 bytes --]


I have tried to explain the user-facing changes in NEWS and in the
manual.

Thanks.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-05  7:34     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-05 13:10       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-05 13:28       ` Lars Ingebrigtsen
  2021-11-05 13:40         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 1 reply; 39+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-05 13:28 UTC (permalink / raw)
  To: Po Lu; +Cc: 51473

Po Lu <luangruo@yahoo.com> writes:

> This is probably the same WebKitGtk bug that also happens on Fedora.
> Can you tell me the version of your WebKitGtk package and GLib?

Let's see...  it's these packages?

webkit2gtk-driver/testing 2.34.1-1 amd64
  WebKitGTK WebDriver support

libglib2.0-dev/testing,now 2.70.0-3 amd64 [installed,automatic]
  Development files for the GLib library

> If you can't upgrade/downgrade, continually retrying could help.

I've done an "apt update/upgrade" (and rebuilt Emacs), but it's still
failing in the same way.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-05 13:10       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-05 13:32         ` Lars Ingebrigtsen
  0 siblings, 0 replies; 39+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-05 13:32 UTC (permalink / raw)
  To: Po Lu; +Cc: 51473

Po Lu <luangruo@yahoo.com> writes:

> I have tried to explain the user-facing changes in NEWS and in the
> manual.

Thanks; I've only skimmed the patches, but the general gist makes sense
to me.  But I can't really test them, so somebody else will have to do that.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-05  7:20     ` Eli Zaretskii
  2021-11-05  7:29       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-05 13:35       ` Lars Ingebrigtsen
  1 sibling, 0 replies; 39+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-05 13:35 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, 51473

Eli Zaretskii <eliz@gnu.org> writes:

> Sounds like a serious problem.  Any idea why it crashes?  What does
> the backtrace say?

The start of it is:

Overriding existing handler for signal 10. Set JSC_SIGNAL_FOR_GC if you want WebKit to use a different signal

** (emacs:2296186): ERROR **: 14:33:31.815: GApplication is required for xdg-desktop-portal access in the WebKit sandbox.
Fatal error 5: Trace/breakpoint trap
Backtrace:

So I guess the Webkit code is doing something here to exit, so it didn't
seem worth trying to debug further on the Emacs side (for me).

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-05 13:28       ` Lars Ingebrigtsen
@ 2021-11-05 13:40         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-05 13:48           ` Lars Ingebrigtsen
  0 siblings, 1 reply; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-05 13:40 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: 51473

Lars Ingebrigtsen <larsi@gnus.org> writes:

> Let's see...  it's these packages?
>
> webkit2gtk-driver/testing 2.34.1-1 amd64
>   WebKitGTK WebDriver support

That's not the right package.  You should be looking at the version of
`webkit2gtk', on Debian.

> libglib2.0-dev/testing,now 2.70.0-3 amd64 [installed,automatic]
>   Development files for the GLib library

Interesting, thanks.  Unfortunately, that puts an end to my theory that
this bug only happens with <2.69.3.

FWIW, the only systems I've seen to exhibit this bug have webkit2gtk3
2.32 with GLib 2.68.





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-05 13:40         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-05 13:48           ` Lars Ingebrigtsen
  2021-11-06  0:00             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-06  0:22             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 2 replies; 39+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-05 13:48 UTC (permalink / raw)
  To: Po Lu; +Cc: 51473

Po Lu <luangruo@yahoo.com> writes:

> That's not the right package.  You should be looking at the version of
> `webkit2gtk', on Debian.

Uhm...  there's no package called exactly webkit2gtk here, but there's
these, perhaps?

libwebkit2gtk-4.0-dev/testing,now 2.34.1-1 amd64 [installed]
  Web content engine library for GTK - development files

webkit2gtk-driver/testing 2.34.1-1 amd64
  WebKitGTK WebDriver support

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-05 13:48           ` Lars Ingebrigtsen
@ 2021-11-06  0:00             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-06  0:22             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 0 replies; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-06  0:00 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: 51473

Lars Ingebrigtsen <larsi@gnus.org> writes:

> Uhm...  there's no package called exactly webkit2gtk here, but there's
> these, perhaps?

> libwebkit2gtk-4.0-dev/testing,now 2.34.1-1 amd64 [installed]
>   Web content engine library for GTK - development files

That's the version of the library for GTK 4.x, not GTK 3, but the
versions should be the same.

Thanks for the help, I'll probably have a workaround soon.





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-05 13:48           ` Lars Ingebrigtsen
  2021-11-06  0:00             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-06  0:22             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-06  0:25               ` Lars Ingebrigtsen
  1 sibling, 1 reply; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-06  0:22 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: 51473

Lars Ingebrigtsen <larsi@gnus.org> writes:

> webkit2gtk-driver/testing 2.34.1-1 amd64
>   WebKitGTK WebDriver support

A temporary workaround is to disable sandboxing of the WebKit
sub-processes.  Would this be acceptable?  Also, would it be OK to adopt
such a change in the release branch?

Thanks.





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-06  0:22             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-06  0:25               ` Lars Ingebrigtsen
  2021-11-06  2:06                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-06  5:39                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 2 replies; 39+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-06  0:25 UTC (permalink / raw)
  To: Po Lu; +Cc: 51473

Po Lu <luangruo@yahoo.com> writes:

> A temporary workaround is to disable sandboxing of the WebKit
> sub-processes.  Would this be acceptable?

Hm...  I'd rather not, if possible.  But if that's the only non-invasive
fix possible...

> Also, would it be OK to adopt such a change in the release branch?

If Emacs 28 is segfaulting (and I assume it is, but I've only tried
Emacs 29) on a lot of newer systems, then I guess we should.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-06  0:25               ` Lars Ingebrigtsen
@ 2021-11-06  2:06                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-06  5:39                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 0 replies; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-06  2:06 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: 51473

Lars Ingebrigtsen <larsi@gnus.org> writes:

> Po Lu <luangruo@yahoo.com> writes:
>
>> A temporary workaround is to disable sandboxing of the WebKit
>> sub-processes.  Would this be acceptable?
>
> Hm...  I'd rather not, if possible.  But if that's the only non-invasive
> fix possible...

I will try to find some other non-invasive fix.

>> Also, would it be OK to adopt such a change in the release branch?

> If Emacs 28 is segfaulting (and I assume it is, but I've only tried
> Emacs 29) on a lot of newer systems, then I guess we should.

It's not a segfault, but rather some race condition occurring in
WebKitGTK+, which causes it to invoke one of the GLib logging functions
incorrectly, which then raises SIGTRAP.

This crash also happens in Emacs 27, and also GNOME's Yelp viewer.





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-06  0:25               ` Lars Ingebrigtsen
  2021-11-06  2:06                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-06  5:39                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-06  8:58                   ` Eli Zaretskii
  1 sibling, 1 reply; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-06  5:39 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: 51473

Lars Ingebrigtsen <larsi@gnus.org> writes:

> Hm...  I'd rather not, if possible.  But if that's the only non-invasive
> fix possible...

Another solution is to set the environment variables `SNAP', `SNAP_NAME'
and `SNAP_REVISION', so WebKitGTK thinks it is running in snap and
doesn't go down the faulty Bubblewrap code path.

It works, but it could potentially affect other programs running inside
Emacs as well, and that variable can't be temporarily set because it's
impossible to determine when WebKitGTK will actually launch a
subprocess.

So I think it should be documented in etc/PROBLEMS instead.  Something
along the lines of:

** Emacs crashes with SIGTRAP when trying to start an WebKit xwidget.

The version of WebKitGTK installed on your system is buggy, and errors
out trying to start a subprocess through bubblewrap.  You can prevent
the buggy code from being executed by setting the environment variables
`SNAP', `SNAP_NAME' and `SNAP_REVISION', which tricks WebKit into
thinking Emacs is running in snap and makes it use GLib to launch
subprocesses instead.

WDYT?





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-06  5:39                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-06  8:58                   ` Eli Zaretskii
  2021-11-06 10:41                     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 39+ messages in thread
From: Eli Zaretskii @ 2021-11-06  8:58 UTC (permalink / raw)
  To: Po Lu; +Cc: larsi, 51473

> Cc: 51473@debbugs.gnu.org
> Date: Sat, 06 Nov 2021 13:39:34 +0800
> From:  Po Lu via "Bug reports for GNU Emacs,
>  the Swiss army knife of text editors" <bug-gnu-emacs@gnu.org>
> 
> So I think it should be documented in etc/PROBLEMS instead.  Something
> along the lines of:
> 
> ** Emacs crashes with SIGTRAP when trying to start an WebKit xwidget.
> 
> The version of WebKitGTK installed on your system is buggy, and errors
> out trying to start a subprocess through bubblewrap.  You can prevent
> the buggy code from being executed by setting the environment variables
> `SNAP', `SNAP_NAME' and `SNAP_REVISION', which tricks WebKit into
> thinking Emacs is running in snap and makes it use GLib to launch
> subprocesses instead.
> 
> WDYT?

Thanks, I added an entry in PROBLEMS along those lines.





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-06  8:58                   ` Eli Zaretskii
@ 2021-11-06 10:41                     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-06 17:42                       ` Lars Ingebrigtsen
  0 siblings, 1 reply; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-06 10:41 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, 51473

[-- Attachment #1: Type: text/plain, Size: 540 bytes --]

Eli Zaretskii <eliz@gnu.org> writes:

> Thanks, I added an entry in PROBLEMS along those lines.

Thanks!  Lars, could you test the latest changes below with the
workaround now described in PROBLEMS?

Also, could someone with a Mac verify that this still builds on macOS,
and preferably implement the missing features on that platform as well?

I hope all goes well, because it would be really nice to see this
installed in the master branch soon.  Many people have been waiting for
xwidgets to be actually useful for a long time.

Thanks.


[-- Attachment #2: patches.tar.gz --]
[-- Type: application/gzip, Size: 35713 bytes --]

^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-06 10:41                     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-06 17:42                       ` Lars Ingebrigtsen
  2021-11-07  0:41                         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 39+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-06 17:42 UTC (permalink / raw)
  To: Po Lu; +Cc: 51473

Po Lu <luangruo@yahoo.com> writes:

> Thanks!  Lars, could you test the latest changes below with the
> workaround now described in PROBLEMS?
>
> Also, could someone with a Mac verify that this still builds on macOS,
> and preferably implement the missing features on that platform as well?

I can test both, but it'd be easier to test if it was just one huge
patch.  (When applying for real, we can apply the patch series.)  So can
you post this series as one patch?

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-06 17:42                       ` Lars Ingebrigtsen
@ 2021-11-07  0:41                         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-07  0:55                           ` Lars Ingebrigtsen
                                             ` (2 more replies)
  0 siblings, 3 replies; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-07  0:41 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: Eli Zaretskii, 51473

[-- Attachment #1: Type: text/plain, Size: 249 bytes --]

Lars Ingebrigtsen <larsi@gnus.org> writes:

> I can test both, but it'd be easier to test if it was just one huge
> patch.  (When applying for real, we can apply the patch series.)  So can
> you post this series as one patch?

Thanks, here you go.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: test.diff --]
[-- Type: text/x-patch, Size: 91851 bytes --]

diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi
index 7f91e1c188..1207ab5e9a 100644
--- a/doc/emacs/misc.texi
+++ b/doc/emacs/misc.texi
@@ -2953,6 +2953,34 @@ Embedded WebKit Widgets
 reloading it.  Type @w{@kbd{C-h b}} in that buffer to see the key
 bindings.
 
+@findex xwidget-webkit-edit-mode
+@cindex xwidget-webkit-edit-mode
+  By default, typing a self-inserting character inside an xwidget
+webkit buffer will do nothing, or trigger some special action.  To
+make those characters and other common editing keys insert themselves
+when pressed, you can enable @code{xwidget-webkit-edit-mode}, which
+redefines them to be passed through to the WebKit xwidget.
+
+You can also enable @code{xwidget-webkit-edit-mode} by typing @kbd{e}
+inside the xwidget webkit buffer.
+
+@findex xwidget-webkit-isearch-mode
+@cindex xwidget-webkit-isearch-mode
+@cindex searching in webkit buffers
+  @code{xwidget-webkit-isearch-mode} is a minor mode that behaves
+similarly to incremental search (@pxref{Incremental Search}), but
+operates on the contents of a WebKit widget instead of the current
+buffer.  It is bound to @kbd{C-s} and @kbd{C-r} inside xwidget-webkit
+buffers.  When it is enabled through @kbd{C-r}, the initial search
+will be performed in reverse direction.
+
+Typing any self-inserting character will cause the character to be
+inserted into the current search query.  Typing @kbd{C-s} will cause
+the WebKit widget to display the next search result, while typing
+@kbd{C-r} will cause it to display the last.
+
+To leave incremental search, you can type @kbd{C-g}.
+
 @node Browse-URL
 @subsection  Following URLs
 @cindex World Wide Web
diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi
index b38a83b4fe..832b570b6a 100644
--- a/doc/lispref/commands.texi
+++ b/doc/lispref/commands.texi
@@ -1176,6 +1176,7 @@ Input Events
 * Repeat Events::               Double and triple click (or drag, or down).
 * Motion Events::               Just moving the mouse, not pushing a button.
 * Focus Events::                Moving the mouse between frames.
+* Xwidget Events::              Events generated by xwidgets.
 * Misc Events::                 Other events the system can generate.
 * Event Examples::              Examples of the lists for mouse events.
 * Classifying Events::          Finding the modifier keys in an event symbol.
@@ -1871,6 +1872,76 @@ Focus Events
 so that the focus event comes either before or after the multi-event key
 sequence, and not within it.
 
+@node Xwidget Events
+@subsection Xwidget events
+
+Xwidgets (@pxref{Xwidgets}) can send events to update Lisp programs on
+their status.  These events are dubbed @code{xwidget-events}, and
+contain various data describing the nature of the change.
+
+@table @code
+@cindex @code{xwidget-event} event
+@item (xwidget-event @var{kind} @var{xwidget} @var{arg})
+This event is sent whenever some kind of update occurs in
+@var{xwidget}.  There are several types of updates, which are
+identified by @var{kind}.
+
+@cindex @code{load-changed} xwidget events
+An xwidget event with @var{kind} set to @code{load-changed} indicates
+that the @var{xwidget} has reached a particular point of the
+page-loading process.  When these events are sent, @var{arg} will
+contain a string that futher describes the status of the widget.
+
+@cindex @samp{"load-finished"} in xwidgets
+When @var{arg} is @samp{"load-finished"}, it means the xwidget has
+finished processing whatever page-loading operation that it was
+previously performing.
+
+@cindex @samp{"load-started"} in xwidgets
+Otherwise, if it is @samp{"load-started"}, then the widget has begun a
+page-loading operation.
+
+@cindex @samp{"load-redirected"} in xwidgets
+If @var{arg} is @samp{"load-redirected"}, it means the widget has
+encountered and followed a redirect during the page-loading operation.
+
+@cindex @samp{"load-committed"} in xwidgets
+If @var{arg} is @samp{"load-committed"}, then the widget has committed
+to a given URL during the page-loading operation.  This means that the
+URL is the final URL that will be rendered by @var{xwidget} during the
+current page-loading operation.
+
+@cindex @code{download-callback} xwidget events
+An event with @var{kind} set to @code{download-callback} indicates
+that a download of some kind has been completed.
+
+In these events, there can be arguments after @var{arg}, which itself
+indicates the URL that the download file was retrieved from: the first
+argument after @var{arg} indicates the MIME type of the download, as a
+string, while the second such argument contains the full file path to
+the downloaded file.
+
+@cindex @code{download-started} xwidget events
+An event with @var{kind} set to @code{download-started} indicates that
+a download has been started.  In these events, @var{arg} contains the
+URL of the file that is currently being downloaded.
+
+@cindex @code{javascript-callback} xwidget events
+An event with @var{kind} set to @code{javascript-callback} contains
+JavaScript callback data.  These events are used internally by
+@code{xwidget-webkit-execute-script}.
+
+@cindex @code{xwidget-display-event} event
+@item (xwidget-display-event @var{xwidget})
+This event is sent whenever an xwidget requests that another xwidget
+be displayed.  @var{xwidget} is the xwidget that should be displayed.
+
+@var{xwidget}'s buffer will be set to a temporary buffer.  When
+displaying the widget, care should be taken to replace the buffer with
+the buffer in which the xwidget will be displayed, using
+@code{set-xwidget-buffer}  (@pxref{Xwidgets}).
+@end table
+
 @node Misc Events
 @subsection Miscellaneous System Events
 
diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi
index 22528a1b0f..37f07c4f28 100644
--- a/doc/lispref/display.texi
+++ b/doc/lispref/display.texi
@@ -6784,7 +6784,10 @@ Xwidgets
 in a @code{display} text or overlay property (@pxref{Display
 Property}).
 
-@defun make-xwidget type title width height arguments &optional buffer
+  Embedded widgets can send events notifying Lisp code about changes
+occurring within them.  (@pxref{Xwidget Events}).
+
+@defun make-xwidget type title width height arguments &optional buffer related
 This creates and returns an xwidget object.  If
 @var{buffer} is omitted or @code{nil}, it defaults to the current
 buffer.  If @var{buffer} names a buffer that doesn't exist, it will be
@@ -6797,7 +6800,9 @@ Xwidgets
 @end table
 
 The @var{width} and @var{height} arguments specify the widget size in
-pixels, and @var{title}, a string, specifies its title.
+pixels, and @var{title}, a string, specifies its title.  @var{related}
+is used internally by the WebKit widget, and is not of interest to the
+programmer.
 @end defun
 
 @defun xwidgetp object
@@ -6818,6 +6823,10 @@ Xwidgets
 This function returns the buffer of @var{xwidget}.
 @end defun
 
+@defun set-xwidget-buffer xwidget buffer
+This function sets the buffer of @var{xwidget} to @var{buffer}.
+@end defun
+
 @defun get-buffer-xwidgets buffer
 This function returns a list of xwidget objects associated with the
 @var{buffer}, which can be specified as a buffer object or a name of
@@ -6878,6 +6887,61 @@ Xwidgets
 query-on-exit flag, either @code{t} or @code{nil}.
 @end defun
 
+@defun xwidget-perform-lispy-event xwidget event frame
+Send an input event @var{event} to @var{xwidget}.  The precise action
+performed is platform-specific.  See @ref{Input Events}.
+
+You can optionally pass the frame the event was generated from via
+@var{frame}.  On X11, modifier keys in key events will not be
+considered if @var{frame} is @code{nil}, and the selected frame is not
+an X-Windows frame.
+
+On GTK, only keyboard and function key events are implemented.  Mouse,
+motion, and click events are dispatched to the xwidget without going
+through Lisp code, and as such shouldn't require this function to be
+sent.
+@end defun
+
+@defun xwidget-webkit-search query xwidget &optional case-insensitive backwards wrap-around
+Start an incremental search on the WebKit widget @var{xwidget} with
+the string @var{query} as a query.  @var{case-insensitive} denotes
+whether or not the search is case-insensitive, @var{backwards}
+determines if the search is performed backwards towards the start of
+the document, and @var{wrap-around} determines whether or not the
+search terminates at the end of the document.
+
+If the function is called while a search query is already present,
+then the query specified here will replace the existing query.
+
+To stop a search query, use @code{xwidget-webkit-finish-search}.
+@end defun
+
+@defun xwidget-webkit-next-result xwidget
+Display the next search result in @var{xwidget}.  This function will
+error unless a search query has already been started in @var{xwidget}
+through @code{xwidget-webkit-search}.
+
+If @code{wrap-around} was non-nil when @code{xwidget-webkit-search}
+was called, then the search will restart from the beginning of the
+document if the end is reached.
+@end defun
+
+@defun xwidget-webkit-previous-result xwidget
+Display the previous search result in @var{xwidget}.  This function
+will error unless a search query has already been started in
+@var{xwidget} through @code{xwidget-webkit-search}.
+
+If @code{wrap-around} was non-nil when @code{xwidget-webkit-search}
+was called, then the search will restart from the end of the
+document if the beginning is reached.
+@end defun
+
+@defun xwidget-webkit-finish-search xwidget
+Finish a search operation started with @code{xwidget-webkit-search} in
+@var{xwidget}.  If there is no query currently ongoing, then this
+function will error.
+@end defun
+
 @node Buttons
 @section Buttons
 @cindex buttons in buffers
diff --git a/etc/NEWS b/etc/NEWS
index a50229916f..0e5caa4825 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -495,6 +495,31 @@ the buffer will take you to that directory.
 This is a convenience function to extract the field data from
 'exif-parse-file' and 'exif-parse-buffer'.
 
+** Xwidgets
+
++++
+*** New minor mode `xwidget-webkit-edit-mode'.
+When this mode is enabled, self-inserting characters and other common
+web browser shotcut keys are redefined to send themselves to the
+WebKit widget.
+
++++
+*** New minor mode `xwidget-webkit-isearch-mode'.
+This mode acts similarly to incremental search, and allows to search
+the contents of a WebKit widget.  In xwidget-webkit mode, it is bound
+to `C-s' and `C-r'.
+
+---
+*** On X11, the WebKit inspector is now available inside xwidgets.
+To access the inspector, right click on the widget and select "Inspect
+Element".
+
+---
+*** "Open in New Window" in a WebKit widget's context menu now works.
+The newly created buffer will be displayed via display-buffer, which
+can be customized through the usual mechanism of display-buffer-alist
+and friends.
+
 \f
 * New Modes and Packages in Emacs 29.1
 
@@ -719,6 +744,33 @@ an exact match, then the lowercased '[menu-bar foo\ bar]' and finally
 '[menu-bar foo-bar]'.  This further improves backwards-compatibility
 when converting menus to use 'easy-menu-define'.
 
++++
+** New function `xwidget-perform-lispy-event'.
+This function allows you to send events to xwidgets.  Usually, some
+equivalent of the event will be sent, but there is no guarantee of
+what the widget will actually receive.
+
+On GTK+, only key and function key events are implemented.
+
++++
+** New functions for performing searches on WebKit xwidgets.
+Some new functions, such as `xwidget-webkit-search', have been added
+for performing searches on WebKit xwidgets.
+
++++
+** `load-changed' xwidget events are now more detailed.
+In particular, they can now have different arguments based on the
+state of the WebKit widget.  `load-finished' is sent when a load has
+completed, `load-started' when a load first starts, `load-redirected'
+after a redirect, and `load-committed' when the WebKit widget first
+commits to the load.
+
++++
+** New event type `xwidget-display-event'.
+These events are sent whenever an xwidget requests that Emacs display
+another.  The only argument to this event is the xwidget that should
+be displayed.
+
 \f
 * Changes in Emacs 29.1 on Non-Free Operating Systems
 
diff --git a/etc/images/README b/etc/images/README
index 9bbe796cc9..561cfff765 100644
--- a/etc/images/README
+++ b/etc/images/README
@@ -68,6 +68,7 @@ Emacs images and their source in the GNOME icons stock/ directory:
   bookmark_add.xpm          actions/bookmark_add
   cancel.xpm                slightly modified generic/stock_stop
   connect.xpm               net/stock_connect
+  connect-to-url.xpm        net/stock_connect-to-url
   contact.xpm               net/stock_contact
   data-save.xpm             data/stock_data-save
   delete.xpm                generic/stock_delete
diff --git a/etc/images/connect-to-url.pbm b/etc/images/connect-to-url.pbm
new file mode 100644
index 0000000000..f142349f4a
Binary files /dev/null and b/etc/images/connect-to-url.pbm differ
diff --git a/etc/images/connect-to-url.xpm b/etc/images/connect-to-url.xpm
new file mode 100644
index 0000000000..38fefeaf61
--- /dev/null
+++ b/etc/images/connect-to-url.xpm
@@ -0,0 +1,281 @@
+/* XPM */
+static char *connect_to_url[] = {
+/* columns rows colors chars-per-pixel */
+"24 24 251 2 ",
+"   c black",
+".  c #010101",
+"X  c #000103",
+"o  c #010204",
+"O  c #010305",
+"+  c #020407",
+"@  c #020609",
+"#  c #03070C",
+"$  c #04080D",
+"%  c #0F0F0D",
+"&  c #030A10",
+"*  c #050B10",
+"=  c #060C11",
+"-  c #070D13",
+";  c #070D14",
+":  c #060C15",
+">  c #070E14",
+",  c #0B1824",
+"<  c #0A1B2B",
+"1  c #0A1C2E",
+"2  c #141A20",
+"3  c #161E25",
+"4  c #181E23",
+"5  c #0D2032",
+"6  c #142534",
+"7  c #1F2830",
+"8  c #1D2933",
+"9  c #102438",
+"0  c #272622",
+"q  c #21292F",
+"w  c #272F36",
+"e  c #282F33",
+"r  c #222F3A",
+"t  c #2E3337",
+"y  c #2D373E",
+"u  c #32383C",
+"i  c #33383C",
+"p  c #343A3E",
+"a  c #43423C",
+"s  c #112941",
+"d  c #102A44",
+"f  c #132D47",
+"g  c #192F46",
+"h  c #17314B",
+"j  c #15314F",
+"k  c #163351",
+"l  c #163554",
+"z  c #173554",
+"x  c #1F3A53",
+"c  c #1D3955",
+"v  c #1A3958",
+"b  c #1C3B5B",
+"n  c #1F3C58",
+"m  c #1D3C5C",
+"M  c #1E3E5D",
+"N  c #1F3F5F",
+"B  c #303B44",
+"V  c #313C44",
+"C  c #313D47",
+"Z  c #213C56",
+"A  c #233E57",
+"S  c #1F405F",
+"D  c #374148",
+"F  c #2D4050",
+"G  c #25405B",
+"H  c #25425E",
+"J  c #214262",
+"K  c #244565",
+"L  c #264665",
+"P  c #254666",
+"I  c #2A4967",
+"U  c #284969",
+"Y  c #2A4C6C",
+"T  c #2C4F6F",
+"R  c #33526E",
+"E  c #385269",
+"W  c #2D5070",
+"Q  c #2E5172",
+"!  c #335473",
+"~  c #3F5B75",
+"^  c #3D5F7D",
+"/  c #41494F",
+"(  c #646056",
+")  c #6C685E",
+"_  c #505F6C",
+"`  c #48657C",
+"'  c #556A7A",
+"]  c #5B6C78",
+"[  c #5F6F7B",
+"{  c #5D6F7D",
+"}  c #706C62",
+"|  c #726D63",
+" . c #78756B",
+".. c #7D786E",
+"X. c #60727F",
+"o. c #807D74",
+"O. c #8A857B",
+"+. c #8B877E",
+"@. c #4E6A83",
+"#. c #4A6A86",
+"$. c #4A7090",
+"%. c #587790",
+"&. c #5F7E95",
+"*. c #587B98",
+"=. c #6F7980",
+"-. c #697F8F",
+";. c #66839B",
+":. c #6A879F",
+">. c #708391",
+",. c #728A9A",
+"<. c #748898",
+"1. c #758A99",
+"2. c #7B8F9F",
+"3. c #708DA4",
+"4. c #7990A1",
+"5. c #7292AB",
+"6. c #7691A8",
+"7. c #7693AB",
+"8. c #7B98AE",
+"9. c #7E98AD",
+"0. c #7E9DB3",
+"q. c #7F9EB4",
+"w. c #8C8981",
+"e. c #989389",
+"r. c #A6A29B",
+"t. c #8093A1",
+"y. c #8598A3",
+"u. c #8498A7",
+"i. c #809AAD",
+"p. c #8F9FAA",
+"a. c #899FAE",
+"s. c #819FB5",
+"d. c #86A2B8",
+"f. c #87A5BB",
+"g. c #88A3B8",
+"h. c #89A5BA",
+"j. c #8FABBF",
+"k. c #97A7B1",
+"l. c #90AABE",
+"z. c #91ABBF",
+"x. c #98ACB9",
+"c. c #AAA7A0",
+"v. c #B1ADA4",
+"b. c #B3B1AA",
+"n. c #B7B3AA",
+"m. c #A3B1BC",
+"M. c #A5B1BC",
+"N. c #A9B6BF",
+"B. c #BEBBB5",
+"V. c #C4C2BD",
+"C. c #94AEC1",
+"Z. c #96AEC1",
+"A. c #94AFC2",
+"S. c #95AFC2",
+"D. c #96B0C3",
+"F. c #98B0C3",
+"G. c #9FB5C3",
+"H. c #99B3C6",
+"J. c #98B3C7",
+"K. c #9AB3C6",
+"L. c #9BB4C7",
+"P. c #9FB8CA",
+"I. c #9FB8CB",
+"U. c #A2B8C9",
+"Y. c #A3B9C9",
+"T. c #A0B9CB",
+"R. c #A3BACB",
+"E. c #A0B9CC",
+"W. c #A2BACC",
+"Q. c #A4BDCE",
+"!. c #A6BECF",
+"~. c #B8BEC2",
+"^. c #B8C3CA",
+"/. c #BCC5CB",
+"(. c #BDC8CE",
+"). c #A8C0D1",
+"_. c #AAC0D0",
+"`. c #ABC1D1",
+"'. c #ACC2D3",
+"]. c #AAC5D7",
+"[. c #B4C8D6",
+"{. c #BDCBD5",
+"}. c #B4C9D8",
+"|. c #B6CAD8",
+" X c #B8CBD9",
+".X c #BBCDDB",
+"XX c #B7D0E0",
+"oX c #BDD3E2",
+"OX c #BCD5E5",
+"+X c #CECAC3",
+"@X c #C5D2C8",
+"#X c #C0D2DE",
+"$X c #C4D3DF",
+"%X c #CCD7DE",
+"&X c #D2D8DC",
+"*X c #E1DFDB",
+"=X c #E2E1DD",
+"-X c #C2D3E0",
+";X c #C2D4E1",
+":X c #C5D5E1",
+">X c #C6D6E1",
+",X c #C4D6E2",
+"<X c #C5D6E3",
+"1X c #C6D7E3",
+"2X c #C3D7E4",
+"3X c #C1D7E6",
+"4X c #C7D8E3",
+"5X c #C5D8E5",
+"6X c #C7D9E5",
+"7X c #CBD9E4",
+"8X c #CBDAE5",
+"9X c #CDDAE4",
+"0X c #CCDBE5",
+"qX c #CFDBE5",
+"wX c #CBDCE7",
+"eX c #C0D9E8",
+"rX c #C2DBEA",
+"tX c #C4DAE8",
+"yX c #D0DEE7",
+"uX c #D1DFE8",
+"iX c #D0DFE9",
+"pX c #D0E0EA",
+"aX c #D1E1EB",
+"sX c #D3E1EA",
+"dX c #D4E1E9",
+"fX c #D4E1EA",
+"gX c #D5E2EA",
+"hX c #D4E2EB",
+"jX c #D6E2EB",
+"kX c #D3E2EC",
+"lX c #D8E3EA",
+"zX c #DFE6EB",
+"xX c #D9E4EC",
+"cX c #D9E5ED",
+"vX c #DAE5ED",
+"bX c #DAE6ED",
+"nX c #DCE7EE",
+"mX c #DBE8EF",
+"MX c #DDE8EF",
+"NX c #DFE8EF",
+"BX c #EAE8E3",
+"VX c #EBEAE6",
+"CX c #ECEBE8",
+"ZX c #E9EEEA",
+"AX c #F0EFEC",
+"SX c #F2F0ED",
+"DX c #E1ECF3",
+"FX c #E4EDF3",
+"GX c #E8EFF4",
+"HX c #F0F3F1",
+"JX c None",
+/* pixels */
+"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJXu D p t i V w JXJXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXC X./.&XDXGX%X{.m._ r JXJXJXJXJXJXJX",
+"JXJXJXJXJXi /.DXnXnXFXuX7X$X$XjXM.w JXJXJXJXJXJX",
+"JXJXJXJX/ ^.qXbX1XkX5X5X-X;XsXqXjXN.B JXJXJXJXJX",
+"JXJXJXe (.bXMXDXaXtXtX3XoXbXjXsXyX7Xx.q JXJXJXJX",
+"JXJX7 k.jXbXbX5X3XeXrXOXXX1XsXyXwX$X|.4.3 JXJXJX",
+"JXJXX.:XuXjX'.]._.y.    G.sXW.|..X$X[.H.' JXJXJX",
+"JXJXu.$XqXT.H.>.    e.o.  sXwX}.R.R.`.H.1.- JXJX",
+"JX4 a.9.C.h.] a n.V.BXo.        p.!.T.l.4.- JXJX",
+"JX2 F.d.5.7.  =XAXc.BXo.  @X@XZX  !.C.F.@.> JXJX",
+"            o.=XAXc.BXo.        t.U.z.3.Y $ JXJX",
+"BXBXBXBXVXBXBXAXVXO.CXo.  P.C.!.I.J.C.;.L * JXJX",
+"o.o.o.o.o. . .B.b...*X .  $.*.T.J.A.h.Y c @ JXJX",
+"             .w.r.| +X .        1.C.3.L h   JXJX",
+"JXJX6 Q ^ 1.% w.r.| +X .  @X@XHX  h.:.M ,   JXJX",
+"JXJXO x T #.] 0 +.} v.)         -.s.H 9 O JXJXJX",
+"JXJXJX+ n ! i.X.% % e.(   Q Y %.0.&.f O   JXJXJX",
+"JXJXJXJX& A s.8.E A % % A K J R ` g @   JXJXJXJX",
+"JXJXJXJXJX@ C ~ m M J N M b v l < O   JXJXJXJXJX",
+"JXJXJXJXJXJX  : 5 d k z k d 1 &     JXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJX                JXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX"
+};
diff --git a/lisp/xwidget.el b/lisp/xwidget.el
index 8c593abea8..d427e70233 100644
--- a/lisp/xwidget.el
+++ b/lisp/xwidget.el
@@ -37,6 +37,7 @@
 (declare-function make-xwidget "xwidget.c"
                   (type title width height arguments &optional buffer))
 (declare-function xwidget-buffer "xwidget.c" (xwidget))
+(declare-function set-xwidget-buffer "xwidget.c" (xwidget buffer))
 (declare-function xwidget-size-request "xwidget.c" (xwidget))
 (declare-function xwidget-resize "xwidget.c" (xwidget new-width new-height))
 (declare-function xwidget-webkit-execute-script "xwidget.c"
@@ -88,6 +89,9 @@ xwidget-at
 (require 'seq)
 (require 'url-handlers)
 
+(defvar-local xwidget-webkit--title ""
+  "The title of the WebKit widget, used for the header line.")
+
 ;;;###autoload
 (defun xwidget-webkit-browse-url (url &optional new-session)
   "Ask xwidget-webkit to browse URL.
@@ -124,6 +128,14 @@ xwidget-webkit-clone-and-split-right
     (with-selected-window (split-window-right)
       (xwidget-webkit-new-session url))))
 
+(declare-function xwidget-perform-lispy-event "xwidget.c")
+
+(defun xwidget-webkit-pass-command-event ()
+  "Pass `last-command-event' to the current buffer's WebKit widget."
+  (interactive)
+  (xwidget-perform-lispy-event (xwidget-webkit-current-session)
+                               last-command-event))
+
 ;;todo.
 ;; - check that the webkit support is compiled in
 (defvar xwidget-webkit-mode-map
@@ -138,6 +150,9 @@ xwidget-webkit-mode-map
     (define-key map "w" 'xwidget-webkit-current-url)
     (define-key map "+" 'xwidget-webkit-zoom-in)
     (define-key map "-" 'xwidget-webkit-zoom-out)
+    (define-key map "e" 'xwidget-webkit-edit-mode)
+    (define-key map "\C-r" 'xwidget-webkit-isearch-mode)
+    (define-key map "\C-s" 'xwidget-webkit-isearch-mode)
 
     ;;similar to image mode bindings
     (define-key map (kbd "SPC")                 'xwidget-webkit-scroll-up)
@@ -164,6 +179,63 @@ xwidget-webkit-mode-map
     map)
   "Keymap for `xwidget-webkit-mode'.")
 
+(easy-menu-define nil xwidget-webkit-mode-map "Xwidget WebKit menu."
+  (list "Xwidget WebKit"
+        ["Browse URL" xwidget-webkit-browse-url
+         :active t
+         :help "Prompt for a URL, then instruct WebKit to browse it"]
+        ["Back" xwidget-webkit-back t]
+        ["Forward" xwidget-webkit-forward t]
+        ["Reload" xwidget-webkit-reload t]
+        ["Insert String" xwidget-webkit-insert-string
+         :active t
+         :help "Insert a string into the currently active field"]
+        ["Zoom In" xwidget-webkit-zoom-in t]
+        ["Zoom Out" xwidget-webkit-zoom-out t]
+        ["Edit Mode" xwidget-webkit-edit-mode
+         :active t
+         :style toggle
+         :selected xwidget-webkit-edit-mode
+         :help "Send self inserting characters to the WebKit widget"]
+        ["Save Selection" xwidget-webkit-copy-selection-as-kill
+         :active t
+         :help "Save the browser's selection in the kill ring"]
+        ["Incremental Search" xwidget-webkit-isearch-mode
+         :active (not xwidget-webkit-isearch-mode)
+         :help "Perform incremental search inside the WebKit widget"]))
+
+(defvar xwidget-webkit-tool-bar-map
+  (let ((map (make-sparse-keymap)))
+    (prog1 map
+      (tool-bar-local-item-from-menu 'xwidget-webkit-back
+                                     "left-arrow"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-forward
+                                     "right-arrow"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-reload
+                                     "refresh"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-zoom-in
+                                     "zoom-in"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-zoom-out
+                                     "zoom-out"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-browse-url
+                                     "connect-to-url"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-isearch-mode
+                                     "search"
+                                     map
+                                     xwidget-webkit-mode-map))))
+
 (defun xwidget-webkit-zoom-in ()
   "Increase webkit view zoom factor."
   (interactive nil xwidget-webkit-mode)
@@ -276,6 +348,8 @@ xwidget-webkit-callback
     (with-current-buffer (xwidget-buffer xwidget)
       (cond ((eq xwidget-event-type 'load-changed)
              (let ((title (xwidget-webkit-title xwidget)))
+               (setq xwidget-webkit--title title)
+               (force-mode-line-update)
                (xwidget-log "webkit finished loading: %s" title)
                ;; Do not adjust webkit size to window here, the selected window
                ;; can be the mini-buffer window unwantedly.
@@ -309,8 +383,10 @@ bookmark-make-record-function
 (define-derived-mode xwidget-webkit-mode special-mode "xwidget-webkit"
   "Xwidget webkit view mode."
   (setq buffer-read-only t)
+  (setq-local tool-bar-map xwidget-webkit-tool-bar-map)
   (setq-local bookmark-make-record-function
               #'xwidget-webkit-bookmark-make-record)
+  (setq-local header-line-format 'xwidget-webkit--title)
   ;; Keep track of [vh]scroll when switching buffers
   (image-mode-setup-winprops))
 
@@ -626,6 +702,29 @@ xwidget-webkit-new-session
     (xwidget-webkit-mode)
     (xwidget-webkit-goto-uri (xwidget-webkit-last-session) url)))
 
+(defun xwidget-webkit-import-widget (xwidget)
+  "Create a new webkit session buffer from XWIDGET, an existing xwidget.
+Return the buffer."
+  (let* ((bufname (generate-new-buffer-name "*xwidget-webkit*"))
+         (callback #'xwidget-webkit-callback)
+         (buffer (get-buffer-create bufname)))
+    (with-current-buffer buffer
+      (save-excursion
+        (erase-buffer)
+        (insert ".")
+        (put-text-property (point-min) (point-max)
+                           'display (list 'xwidget :xwidget xwidget)))
+      (xwidget-put xwidget 'callback callback)
+      (set-xwidget-buffer xwidget buffer)
+      (xwidget-webkit-mode))
+    buffer))
+
+(defun xwidget-webkit-display-event (event)
+  "Import the xwidget inside EVENT and display it."
+  (interactive "e")
+  (display-buffer (xwidget-webkit-import-widget (nth 1 event))))
+
+(global-set-key [xwidget-display-event] 'xwidget-webkit-display-event)
 
 (defun xwidget-webkit-goto-url (url)
   "Goto URL with xwidget webkit."
@@ -684,6 +783,158 @@ xwidget-put
   (set-xwidget-plist xwidget
                      (plist-put (xwidget-plist xwidget) propname value)))
 
+(defvar xwidget-webkit-edit-mode-map (make-keymap))
+
+(define-key xwidget-webkit-edit-mode-map [backspace] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [tab] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [return] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-return] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-return] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-return] 'xwidget-webkit-pass-command-event)
+
+(define-minor-mode xwidget-webkit-edit-mode
+  "Minor mode for editing the content of WebKit buffers.
+
+This defines most self-inserting characters and some common
+keyboard shortcuts to `xwidget-webkit-pass-command-event', which
+will pass the key events corresponding to these characters to the
+WebKit widget."
+  :keymap xwidget-webkit-edit-mode-map)
+
+(substitute-key-definition 'self-insert-command
+                           'xwidget-webkit-pass-command-event
+                           xwidget-webkit-edit-mode-map
+                           global-map)
+
+(declare-function xwidget-webkit-search "xwidget.c")
+(declare-function xwidget-webkit-next-result "xwidget.c")
+(declare-function xwidget-webkit-previous-result "xwidget.c")
+(declare-function xwidget-webkit-finish-search "xwidget.c")
+
+(defvar-local xwidget-webkit-isearch--string ""
+  "The current search query.")
+(defvar-local xwidget-webkit-isearch--is-reverse nil
+  "Whether or not the current isearch should be reverse.")
+
+(defun xwidget-webkit-isearch--update ()
+  "Update the current buffer's WebKit widget's search query.
+The query will be set to the contents of `xwidget-webkit-isearch--string'."
+  (xwidget-webkit-search xwidget-webkit-isearch--string
+                         (xwidget-webkit-current-session)
+                         t xwidget-webkit-isearch--is-reverse t)
+  (message "Search contents: %s" xwidget-webkit-isearch--string))
+
+(defun xwidget-webkit-isearch-erasing-char (count)
+  "Erase the last COUNT characters of the current query."
+  (interactive (list (prefix-numeric-value current-prefix-arg)))
+  (when (> (length xwidget-webkit-isearch--string) 0)
+    (setq xwidget-webkit-isearch--string
+          (substring xwidget-webkit-isearch--string 0
+                     (- (length xwidget-webkit-isearch--string) count))))
+  (xwidget-webkit-isearch--update))
+
+(defun xwidget-webkit-isearch-printing-char (char &optional count)
+  "Add ordinary character CHAR to the search string and search.
+With argument, add COUNT copies of CHAR."
+  (interactive (list last-command-event
+                     (prefix-numeric-value current-prefix-arg)))
+  (setq xwidget-webkit-isearch--string (concat xwidget-webkit-isearch--string
+                                               (make-string (or count 1) char)))
+  (xwidget-webkit-isearch--update))
+
+(defun xwidget-webkit-isearch-forward (count)
+  "Move to the next search result COUNT times."
+  (interactive (list (prefix-numeric-value current-prefix-arg)))
+  (let ((was-reverse xwidget-webkit-isearch--is-reverse))
+    (setq xwidget-webkit-isearch--is-reverse nil)
+    (when was-reverse
+      (xwidget-webkit-isearch--update)))
+  (let ((i 0))
+    (while (< i count)
+      (xwidget-webkit-next-result (xwidget-webkit-current-session))
+      (cl-incf i))))
+
+(defun xwidget-webkit-isearch-backward (count)
+  "Move to the previous search result COUNT times."
+  (interactive (list (prefix-numeric-value current-prefix-arg)))
+  (let ((was-reverse xwidget-webkit-isearch--is-reverse))
+    (setq xwidget-webkit-isearch--is-reverse t)
+    (unless was-reverse
+      (xwidget-webkit-isearch--update)))
+  (let ((i 0))
+    (while (< i count)
+      (xwidget-webkit-next-result (xwidget-webkit-current-session))
+      (cl-incf i))))
+
+(defun xwidget-webkit-isearch-exit ()
+  "Exit incremental search of a WebKit buffer."
+  (interactive)
+  (xwidget-webkit-isearch-mode 0))
+
+(defvar xwidget-webkit-isearch-mode-map (make-keymap)
+  "The keymap used inside xwidget-webkit-isearch-mode.")
+
+(set-char-table-range (nth 1 xwidget-webkit-isearch-mode-map)
+                      (cons 0 (max-char))
+                      'xwidget-webkit-isearch-exit)
+
+(substitute-key-definition 'self-insert-command
+                           'xwidget-webkit-isearch-printing-char
+                           xwidget-webkit-isearch-mode-map
+                           global-map)
+
+(define-key xwidget-webkit-isearch-mode-map (kbd "DEL")
+  'xwidget-webkit-isearch-erasing-char)
+(define-key xwidget-webkit-isearch-mode-map [return] 'xwidget-webkit-isearch-exit)
+(define-key xwidget-webkit-isearch-mode-map "\r" 'xwidget-webkit-isearch-exit)
+(define-key xwidget-webkit-isearch-mode-map "\C-g" 'xwidget-webkit-isearch-exit)
+(define-key xwidget-webkit-isearch-mode-map "\C-r" 'xwidget-webkit-isearch-backward)
+(define-key xwidget-webkit-isearch-mode-map "\C-s" 'xwidget-webkit-isearch-forward)
+(define-key xwidget-webkit-isearch-mode-map "\t" 'xwidget-webkit-isearch-printing-char)
+
+(let ((meta-map (make-keymap)))
+  (set-char-table-range (nth 1 meta-map)
+                        (cons 0 (max-char))
+                        'xwidget-webkit-isearch-exit)
+  (define-key xwidget-webkit-isearch-mode-map (char-to-string meta-prefix-char) meta-map))
+
+(define-minor-mode xwidget-webkit-isearch-mode
+  "Minor mode for performing incremental search inside WebKit buffers.
+
+An attempt was made for this to resemble regular incremental
+search, but it suffers from several limitations, such as not
+supporting recursive edits.
+
+If this mode is enabled with `C-r', then the search will default
+to being performed in reverse direction.
+
+To navigate around the search results, type
+\\[xwidget-webkit-isearch-forward] to move forward, and
+\\[xwidget-webkit-isearch-backward] to move backward.
+
+Press \\[xwidget-webkit-isearch-exit] to exit incremental search."
+  :keymap xwidget-webkit-isearch-mode-map
+  (if xwidget-webkit-isearch-mode
+      (progn
+        (setq xwidget-webkit-isearch--string "")
+        (setq xwidget-webkit-isearch--is-reverse (eq last-command-event ?\C-r)))
+    (xwidget-webkit-finish-search (xwidget-webkit-current-session))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
diff --git a/src/dispextern.h b/src/dispextern.h
index 5b28fe7666..f17f095e0d 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -536,8 +536,8 @@ #define FACE_ID_BITS	20
     int img_id;
 
 #ifdef HAVE_XWIDGETS
-    /* Xwidget reference (type == XWIDGET_GLYPH).  */
-    struct xwidget *xwidget;
+    /* Xwidget ID.  */
+    uint32_t xwidget;
 #endif
 
     /* Sub-structure for type == STRETCH_GLYPH.  */
diff --git a/src/dispnew.c b/src/dispnew.c
index 4a73244c89..632eec2f03 100644
--- a/src/dispnew.c
+++ b/src/dispnew.c
@@ -4449,16 +4449,6 @@ scrolling_window (struct window *w, int tab_line_p)
 	break;
     }
 
-#ifdef HAVE_XWIDGETS
-  /* Currently this seems needed to detect xwidget movement reliably.
-     This is most probably because an xwidget glyph is represented in
-     struct glyph's 'union u' by a pointer to a struct, which takes 8
-     bytes in 64-bit builds, and thus the comparison of u.val values
-     done by GLYPH_EQUAL_P doesn't work reliably, since it assumes the
-     size of the union is 4 bytes.  FIXME.  */
-    return 0;
-#endif
-
   /* Can't scroll the display of w32 GUI frames when position of point
      is indicated by the system caret, because scrolling the display
      will then "copy" the pixels used by the caret.  */
diff --git a/src/keyboard.c b/src/keyboard.c
index aa6a4b9e97..c4a5671b10 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -3993,6 +3993,7 @@ kbd_buffer_get_event (KBOARD **kbp,
 #endif
 #ifdef HAVE_XWIDGETS
       case XWIDGET_EVENT:
+      case XWIDGET_DISPLAY_EVENT:
 #endif
       case SAVE_SESSION_EVENT:
       case NO_EVENT:
@@ -4897,7 +4898,7 @@ #define FUNCTION_KEY_OFFSET 0xff00
 
 /* You'll notice that this table is arranged to be conveniently
    indexed by X Windows keysym values.  */
-static const char *const lispy_function_keys[] =
+const char *const lispy_function_keys[] =
   {
     /* X Keysym value */
 
@@ -6139,6 +6140,11 @@ make_lispy_event (struct input_event *event)
       {
         return Fcons (Qxwidget_event, event->arg);
       }
+
+    case XWIDGET_DISPLAY_EVENT:
+      {
+	return list2 (Qxwidget_display_event, event->arg);
+      }
 #endif
 
 #ifdef USE_FILE_NOTIFY
@@ -11732,6 +11738,7 @@ syms_of_keyboard (void)
 
 #ifdef HAVE_XWIDGETS
   DEFSYM (Qxwidget_event, "xwidget-event");
+  DEFSYM (Qxwidget_display_event, "xwidget-display-event");
 #endif
 
 #ifdef USE_FILE_NOTIFY
diff --git a/src/keyboard.h b/src/keyboard.h
index 8bdffaa2bf..21c51ec386 100644
--- a/src/keyboard.h
+++ b/src/keyboard.h
@@ -491,7 +491,7 @@ kbd_buffer_store_event_hold (struct input_event *event,
 extern struct timespec timer_check (void);
 extern void mark_kboards (void);
 
-#ifdef HAVE_NTGUI
+#if defined HAVE_NTGUI || defined HAVE_X_WINDOWS
 extern const char *const lispy_function_keys[];
 #endif
 
diff --git a/src/print.c b/src/print.c
index c13294c8e6..eca389158f 100644
--- a/src/print.c
+++ b/src/print.c
@@ -1521,8 +1521,20 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
       printchar ('>', printcharfun);
       break;
 
-    case PVEC_XWIDGET: case PVEC_XWIDGET_VIEW:
-      print_c_string ("#<xwidget ", printcharfun);
+    case PVEC_XWIDGET:
+#ifdef HAVE_XWIDGETS
+      {
+	int len = sprintf (buf, "#<xwidget %u %p>",
+			   XXWIDGET (obj)->xwidget_id,
+			   XXWIDGET (obj)->widget_osr);
+	strout (buf, len, len, printcharfun);
+	break;
+      }
+#else
+      emacs_abort ();
+#endif
+    case PVEC_XWIDGET_VIEW:
+      print_c_string ("#<xwidget view", printcharfun);
       printchar ('>', printcharfun);
       break;
 
diff --git a/src/termhooks.h b/src/termhooks.h
index 1d3cdc8fe8..e7539bbce2 100644
--- a/src/termhooks.h
+++ b/src/termhooks.h
@@ -255,6 +255,8 @@ #define EMACS_TERMHOOKS_H
 #ifdef HAVE_XWIDGETS
   /* events generated by xwidgets*/
    , XWIDGET_EVENT
+   /* Event generated when WebKit asks us to display another widget.  */
+   , XWIDGET_DISPLAY_EVENT
 #endif
 
 #ifdef USE_FILE_NOTIFY
diff --git a/src/xdisp.c b/src/xdisp.c
index 86c4e704d5..d7ad548917 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -28429,7 +28429,7 @@ fill_xwidget_glyph_string (struct glyph_string *s)
     }
   s->width = s->first_glyph->pixel_width;
   s->ybase += s->first_glyph->voffset;
-  s->xwidget = s->first_glyph->u.xwidget;
+  s->xwidget = xwidget_from_id (s->first_glyph->u.xwidget);
 }
 #endif
 /* Fill glyph string S from a sequence of stretch glyphs.
@@ -29832,7 +29832,7 @@ produce_xwidget_glyph (struct it *it)
           glyph->padding_p = 0;
 	  glyph->glyph_not_available_p = 0;
 	  glyph->face_id = it->face_id;
-          glyph->u.xwidget = it->xwidget;
+          glyph->u.xwidget = it->xwidget->xwidget_id;
 	  glyph->font_type = FONT_TYPE_UNKNOWN;
 	  if (it->bidi_p)
 	    {
diff --git a/src/xterm.c b/src/xterm.c
index aa1a1a5eed..9b434bffcc 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -4390,6 +4390,86 @@ x_scroll_run (struct window *w, struct run *run)
   /* Cursor off.  Will be switched on again in gui_update_window_end.  */
   gui_clear_cursor (w);
 
+#ifdef HAVE_XWIDGETS
+  /* "Copy" xwidget windows in the area that will be scrolled.  */
+  Display *dpy = FRAME_X_DISPLAY (f);
+  Window window = FRAME_X_WINDOW (f);
+
+  Window root, parent, *children;
+  unsigned int nchildren;
+
+  if (XQueryTree (dpy, window, &root, &parent, &children, &nchildren))
+    {
+      /* Now find xwidget views situated between from_y and to_y, and
+	 attached to w.  */
+      for (unsigned int i = 0; i < nchildren; ++i)
+	{
+	  Window child = children[i];
+	  struct xwidget_view *view = xwidget_view_from_window (child);
+
+	  if (view)
+	    {
+	      int window_y = view->y + view->clip_top;
+	      int window_height = view->clip_bottom - view->clip_top;
+
+	      Emacs_Rectangle r1, r2, result;
+	      r1.x = w->pixel_left;
+	      r1.y = from_y;
+	      r1.width = w->pixel_width;
+	      r1.height = height;
+	      r2 = r1;
+	      r2.y = window_y;
+	      r2.height = window_height;
+
+	      /* The window is offscreen, just unmap it.  */
+	      if (window_height == 0)
+		{
+		  view->hidden = true;
+		  XUnmapWindow (dpy, child);
+		  continue;
+		}
+
+	      bool intersects_p =
+		gui_intersect_rectangles (&r1, &r2, &result);
+
+	      if (XWINDOW (view->w) == w && intersects_p)
+		{
+		  int y = view->y + (to_y - from_y);
+		  int text_area_x, text_area_y, text_area_width, text_area_height;
+		  int clip_top, clip_bottom;
+
+		  window_box (w, TEXT_AREA, &text_area_x, &text_area_y,
+			      &text_area_width, &text_area_height);
+
+		  clip_top = max (0, text_area_y - y);
+		  clip_bottom = max (clip_top,
+				     min (XXWIDGET (view->model)->height,
+					  text_area_y + text_area_height - y));
+
+		  view->y = y;
+		  view->clip_top = clip_top;
+		  view->clip_bottom = clip_bottom;
+
+		  /* This means the view has moved offscreen.  Unmap
+		     it and hide it here.  */
+		  if ((view->clip_top - view->clip_bottom) <= 0)
+		    {
+		      view->hidden = true;
+		      XUnmapWindow (dpy, child);
+		    }
+		  else
+		    XMoveResizeWindow (dpy, child, view->x + view->clip_left,
+				       view->y + view->clip_top,
+				       view->clip_right - view->clip_left,
+				       view->clip_top - view->clip_bottom);
+		  XFlush (dpy);
+		}
+            }
+	}
+      XFree (children);
+    }
+#endif
+
 #ifdef USE_CAIRO
   if (FRAME_CR_CONTEXT (f))
     {
@@ -4563,8 +4643,9 @@ x_focus_changed (int type, int state, struct x_display_info *dpyinfo, struct fra
     }
 }
 
-/* Return the Emacs frame-object corresponding to an X window.
-   It could be the frame's main window or an icon window.  */
+/* Return the Emacs frame-object corresponding to an X window.  It
+   could be the frame's main window, an icon window, or an xwidget
+   window.  */
 
 static struct frame *
 x_window_to_frame (struct x_display_info *dpyinfo, int wdesc)
@@ -4575,6 +4656,13 @@ x_window_to_frame (struct x_display_info *dpyinfo, int wdesc)
   if (wdesc == None)
     return NULL;
 
+#ifdef HAVE_XWIDGETS
+  struct xwidget_view *xvw = xwidget_view_from_window (wdesc);
+
+  if (xvw && xvw->frame)
+    return xvw->frame;
+#endif
+
   FOR_EACH_FRAME (tail, frame)
     {
       f = XFRAME (frame);
@@ -4997,7 +5085,7 @@ x_x_to_emacs_modifiers (struct x_display_info *dpyinfo, int state)
             | ((state & dpyinfo->hyper_mod_mask)	? mod_hyper	: 0));
 }
 
-static int
+int
 x_emacs_to_x_modifiers (struct x_display_info *dpyinfo, intmax_t state)
 {
   EMACS_INT mod_ctrl = ctrl_modifier;
@@ -8211,6 +8299,18 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 
     case Expose:
       f = x_window_to_frame (dpyinfo, event->xexpose.window);
+#ifdef HAVE_XWIDGETS
+      {
+	struct xwidget_view *xv =
+	  xwidget_view_from_window (event->xexpose.window);
+
+	if (xv)
+	  {
+	    xwidget_expose (xv);
+	    goto OTHER;
+	  }
+      }
+#endif
       if (f)
         {
           if (!FRAME_VISIBLE_P (f))
@@ -8791,6 +8891,31 @@ handle_one_xevent (struct x_display_info *dpyinfo,
       x_display_set_last_user_time (dpyinfo, event->xcrossing.time);
       x_detect_focus_change (dpyinfo, any, event, &inev.ie);
 
+#ifdef HAVE_XWIDGETS
+      {
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xcrossing.window);
+	Mouse_HLInfo *hlinfo;
+
+	if (xvw)
+	  {
+	    xwidget_motion_or_crossing (xvw, event);
+	    hlinfo = MOUSE_HL_INFO (xvw->frame);
+
+	    if (xvw->frame == hlinfo->mouse_face_mouse_frame)
+	      {
+		clear_mouse_face (hlinfo);
+		hlinfo->mouse_face_mouse_frame = 0;
+	      }
+
+	    if (any_help_event_p)
+	      {
+		do_help = -1;
+	      }
+	    goto OTHER;
+	  }
+      }
+#endif
+
       f = any;
 
       if (f && x_mouse_click_focus_ignore_position)
@@ -8834,6 +8959,17 @@ handle_one_xevent (struct x_display_info *dpyinfo,
       goto OTHER;
 
     case LeaveNotify:
+#ifdef HAVE_XWIDGETS
+      {
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xcrossing.window);
+
+	if (xvw)
+	  {
+	    xwidget_motion_or_crossing (xvw, event);
+	    goto OTHER;
+	  }
+      }
+#endif
       x_display_set_last_user_time (dpyinfo, event->xcrossing.time);
       x_detect_focus_change (dpyinfo, any, event, &inev.ie);
 
@@ -8883,6 +9019,12 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 #ifdef USE_GTK
         if (f && xg_event_is_for_scrollbar (f, event))
           f = 0;
+#endif
+#ifdef HAVE_XWIDGETS
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xmotion.window);
+
+	if (xvw)
+	  xwidget_motion_or_crossing (xvw, event);
 #endif
         if (f)
           {
@@ -9138,6 +9280,24 @@ handle_one_xevent (struct x_display_info *dpyinfo,
     case ButtonRelease:
     case ButtonPress:
       {
+#ifdef HAVE_XWIDGETS
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xmotion.window);
+
+	if (xvw)
+	  {
+	    xwidget_button (xvw, event->type == ButtonPress,
+			    event->xbutton.x, event->xbutton.y,
+			    event->xbutton.button, event->xbutton.state,
+			    event->xbutton.time);
+
+	    if (!EQ (selected_window, xvw->w))
+	      {
+		inev.ie.kind = SELECT_WINDOW_EVENT;
+		inev.ie.frame_or_window = xvw->w;
+	      }
+	    goto OTHER;
+	  }
+#endif
         /* If we decide we want to generate an event to be seen
            by the rest of Emacs, we put it here.  */
         Lisp_Object tab_bar_arg = Qnil;
@@ -12108,6 +12268,10 @@ x_free_frame_resources (struct frame *f)
 	xfree (f->shell_position);
 #else  /* !USE_X_TOOLKIT */
 
+#ifdef HAVE_XWIDGETS
+      kill_frame_xwidget_views (f);
+#endif
+
 #ifdef USE_GTK
       xg_free_frame_widgets (f);
 #endif /* USE_GTK */
diff --git a/src/xterm.h b/src/xterm.h
index de6ea50385..9d9534dd62 100644
--- a/src/xterm.h
+++ b/src/xterm.h
@@ -1108,6 +1108,7 @@ #define SELECTION_EVENT_TIME(eventp)	\
 extern int x_dispatch_event (XEvent *, Display *);
 #endif
 extern int x_x_to_emacs_modifiers (struct x_display_info *, int);
+extern int x_emacs_to_x_modifiers (struct x_display_info *, intmax_t);
 #ifdef USE_CAIRO
 extern void x_cr_destroy_frame_context (struct frame *);
 extern void x_cr_update_surface_desired_size (struct frame *, int, int);
diff --git a/src/xwidget.c b/src/xwidget.c
index e4b42e6e0c..bf69f262fb 100644
--- a/src/xwidget.c
+++ b/src/xwidget.c
@@ -19,6 +19,7 @@ Copyright (C) 2011-2021 Free Software Foundation, Inc.
 
 #include <config.h>
 
+#include "buffer.h"
 #include "xwidget.h"
 
 #include "lisp.h"
@@ -35,10 +36,22 @@ Copyright (C) 2011-2021 Free Software Foundation, Inc.
 #ifdef USE_GTK
 #include <webkit2/webkit2.h>
 #include <JavaScriptCore/JavaScript.h>
+#include <cairo.h>
+#include <X11/Xlib.h>
 #elif defined NS_IMPL_COCOA
 #include "nsxwidget.h"
 #endif
 
+static Lisp_Object id_to_xwidget_map;
+static uint32_t xwidget_counter = 0;
+
+#ifdef USE_GTK
+static Lisp_Object x_window_to_xwv_map;
+static gboolean offscreen_damage_event (GtkWidget *, GdkEvent *, gpointer);
+static void synthesize_focus_in_event (GtkWidget *);
+static GdkDevice *find_suitable_keyboard (struct frame *);
+#endif
+
 static struct xwidget *
 allocate_xwidget (void)
 {
@@ -64,18 +77,32 @@ #define XSETXWIDGET_VIEW(a, b) XSETPSEUDOVECTOR (a, b, PVEC_XWIDGET_VIEW)
                                            GAsyncResult *,
                                            gpointer);
 static gboolean webkit_download_cb (WebKitWebContext *, WebKitDownload *, gpointer);
-
+static GtkWidget *webkit_create_cb (WebKitWebView *, WebKitNavigationAction *, gpointer);
 static gboolean
 webkit_decide_policy_cb (WebKitWebView *,
                          WebKitPolicyDecision *,
                          WebKitPolicyDecisionType,
                          gpointer);
+static GtkWidget *find_widget_at_pos (GtkWidget *, int, int, int *, int *);
+
+struct widget_search_data
+{
+  int x;
+  int y;
+  bool foundp;
+  bool first;
+  GtkWidget *data;
+};
+
+static void find_widget (GtkWidget *t, struct widget_search_data *);
+static void mouse_target_changed (WebKitWebView *, WebKitHitTestResult *, guint,
+				  gpointer);
 #endif
 
 
 DEFUN ("make-xwidget",
        Fmake_xwidget, Smake_xwidget,
-       5, 6, 0,
+       5, 7, 0,
        doc: /* Make an xwidget of TYPE.
 If BUFFER is nil, use the current buffer.
 If BUFFER is a string and no such buffer exists, create it.
@@ -83,10 +110,12 @@ DEFUN ("make-xwidget",
 
 - webkit
 
-Returns the newly constructed xwidget, or nil if construction fails.  */)
+RELATED is nil, or an xwidget.  This argument is used internally.
+Returns the newly constructed xwidget, or nil if construction
+fails.  */)
   (Lisp_Object type,
    Lisp_Object title, Lisp_Object width, Lisp_Object height,
-   Lisp_Object arguments, Lisp_Object buffer)
+   Lisp_Object arguments, Lisp_Object buffer, Lisp_Object related)
 {
 #ifdef USE_GTK
   if (!xg_gtk_initialized)
@@ -108,13 +137,19 @@ DEFUN ("make-xwidget",
   XSETXWIDGET (val, xw);
   Vxwidget_list = Fcons (val, Vxwidget_list);
   xw->plist = Qnil;
+  xw->xwidget_id = ++xwidget_counter;
+
+  Fputhash (make_fixnum (xw->xwidget_id), val, id_to_xwidget_map);
 
 #ifdef USE_GTK
   xw->widgetwindow_osr = NULL;
   xw->widget_osr = NULL;
+  xw->hit_result = 0;
+  xw->find_text = NULL;
   if (EQ (xw->type, Qwebkit))
     {
       block_input ();
+      WebKitSettings *settings;
       WebKitWebContext *webkit_context = webkit_web_context_get_default ();
 
 # if WEBKIT_CHECK_VERSION (2, 26, 0)
@@ -128,18 +163,33 @@ DEFUN ("make-xwidget",
 
       if (EQ (xw->type, Qwebkit))
         {
-          xw->widget_osr = webkit_web_view_new ();
-
-          /* webkitgtk uses GSubprocess which sets sigaction causing
-             Emacs to not catch SIGCHLD with its usual handle setup in
-             catch_child_signal().  This resets the SIGCHLD
-             sigaction.  */
-          struct sigaction old_action;
-          sigaction (SIGCHLD, NULL, &old_action);
-          webkit_web_view_load_uri(WEBKIT_WEB_VIEW (xw->widget_osr),
-                                   "about:blank");
-          sigaction (SIGCHLD, &old_action, NULL);
-        }
+	  WebKitWebView *related_view;
+
+	  if (NILP (related)
+	      || !XWIDGETP (related)
+	      || !EQ (XXWIDGET (related)->type, Qwebkit))
+	    {
+	      /* Enable the developer extras */
+	      settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (xw->widget_osr));
+	      g_object_set (G_OBJECT (settings), "enable-developer-extras", TRUE, NULL);
+	      xw->widget_osr = webkit_web_view_new ();
+
+	      /* webkitgtk uses GSubprocess which sets sigaction causing
+		 Emacs to not catch SIGCHLD with its usual handle setup in
+		 catch_child_signal().  This resets the SIGCHLD
+		 sigaction.  */
+	      struct sigaction old_action;
+	      sigaction (SIGCHLD, NULL, &old_action);
+	      webkit_web_view_load_uri (WEBKIT_WEB_VIEW (xw->widget_osr),
+					"about:blank");
+	      sigaction (SIGCHLD, &old_action, NULL);
+	    }
+	  else
+	    {
+	      related_view = WEBKIT_WEB_VIEW (XXWIDGET (related)->widget_osr);
+	      xw->widget_osr = webkit_web_view_new_with_related_view (related_view);
+	    }
+	}
 
       gtk_widget_set_size_request (GTK_WIDGET (xw->widget_osr), xw->width,
                                    xw->height);
@@ -157,6 +207,7 @@ DEFUN ("make-xwidget",
 
       gtk_widget_show (xw->widget_osr);
       gtk_widget_show (xw->widgetwindow_osr);
+      synthesize_focus_in_event (xw->widgetwindow_osr);
 
       /* Store some xwidget data in the gtk widgets for convenient
          retrieval in the event handlers.  */
@@ -179,8 +230,20 @@ DEFUN ("make-xwidget",
                             G_CALLBACK
                             (webkit_decide_policy_cb),
                             xw);
+
+	  g_signal_connect (G_OBJECT (xw->widget_osr),
+			    "mouse-target-changed",
+			    G_CALLBACK (mouse_target_changed),
+			    xw);
+	  g_signal_connect (G_OBJECT (xw->widget_osr),
+			    "create",
+			    G_CALLBACK (webkit_create_cb),
+			    xw);
         }
 
+      g_signal_connect (G_OBJECT (xw->widgetwindow_osr), "damage-event",
+			G_CALLBACK (offscreen_damage_event), xw);
+
       unblock_input ();
     }
 #elif defined NS_IMPL_COCOA
@@ -190,6 +253,158 @@ DEFUN ("make-xwidget",
   return val;
 }
 
+#ifdef USE_GTK
+static void
+set_widget_if_text_view (GtkWidget *widget, void *data)
+{
+  GtkWidget **pointer = data;
+
+  if (GTK_IS_TEXT_VIEW (widget))
+    {
+      *pointer = widget;
+    }
+}
+#endif
+
+DEFUN ("xwidget-perform-lispy-event",
+       Fxwidget_perform_lispy_event, Sxwidget_perform_lispy_event,
+       2, 3, 0, doc: /* Send a lispy event to XWIDGET.
+EVENT should be the event that will be sent.  FRAME should be the
+frame which generated the event, or nil.  On X11, modifier keys will
+not be processed if FRAME is nil and the selected frame is not an
+X-Windows frame.  */)
+  (Lisp_Object xwidget, Lisp_Object event, Lisp_Object frame)
+{
+  struct xwidget *xw;
+  struct frame *f = NULL;
+  int character = -1, keycode = -1;
+  int modifiers = 0;
+
+#ifdef USE_GTK
+  GdkEvent *xg_event;
+  GtkContainerClass *klass;
+  GtkWidget *widget;
+  GtkWidget *temp = NULL;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!NILP (frame))
+    f = decode_window_system_frame (frame);
+  else if (FRAME_X_P (SELECTED_FRAME ()))
+    f = SELECTED_FRAME ();
+
+#ifdef USE_GTK
+  widget = gtk_window_get_focus (GTK_WINDOW (xw->widgetwindow_osr));
+
+  if (!widget)
+    widget = xw->widget_osr;
+
+  if (RANGED_FIXNUMP (0, event, INT_MAX))
+    {
+      character = XFIXNUM (event);
+
+      if (character < 32)
+	modifiers |= ctrl_modifier;
+
+      modifiers |= character & meta_modifier;
+      modifiers |= character & hyper_modifier;
+      modifiers |= character & super_modifier;
+      modifiers |= character & shift_modifier;
+      modifiers |= character & ctrl_modifier;
+
+      character = character & ~(1 << 21);
+
+      if (character < 32)
+	character += '_';
+
+      if (f)
+	modifiers = x_emacs_to_x_modifiers (FRAME_DISPLAY_INFO (f), modifiers);
+      else
+	modifiers = 0;
+    }
+  else if (SYMBOLP (event))
+    {
+      Lisp_Object decoded = parse_modifiers (event);
+      Lisp_Object decoded_name = SYMBOL_NAME (XCAR (decoded));
+
+      int off = 0;
+      bool found = false;
+
+      while (off < 256)
+	{
+	  if (lispy_function_keys[off]
+	      && !strcmp (lispy_function_keys[off],
+			  SSDATA (decoded_name)))
+	    {
+	      found = true;
+	      break;
+	    }
+	  ++off;
+	}
+
+      if (f)
+	modifiers = x_emacs_to_x_modifiers (FRAME_DISPLAY_INFO (f),
+					    XFIXNUM (XCAR (XCDR (decoded))));
+      else
+	modifiers = 0;
+
+      if (found)
+	keycode = off + 0xff00;
+    }
+
+  if (character == -1 && keycode == -1)
+    return Qnil;
+
+  block_input ();
+  xg_event = gdk_event_new (GDK_KEY_PRESS);
+  xg_event->any.window = gtk_widget_get_window (xw->widget_osr);
+  g_object_ref (xg_event->any.window);
+
+  if (character > -1)
+    keycode = gdk_unicode_to_keyval (character);
+
+  xg_event->key.keyval = keycode;
+  xg_event->key.state = modifiers;
+
+  if (keycode > -1)
+    {
+      /* WebKitGTK internals abuse follows.  */
+      if (WEBKIT_IS_WEB_VIEW (widget))
+	{
+	  /* WebKitGTK relies on an internal GtkTextView object to
+	     "translate" keys such as backspace.  We must find that
+	     widget and activate its binding to this key if any.  */
+	  klass = GTK_CONTAINER_CLASS (G_OBJECT_GET_CLASS (widget));
+
+	  klass->forall (GTK_CONTAINER (xw->widget_osr), TRUE,
+			 set_widget_if_text_view, &temp);
+
+	  if (GTK_IS_WIDGET (temp))
+	    {
+	      if (!gtk_widget_get_realized (temp))
+		gtk_widget_realize (temp);
+
+	      gtk_bindings_activate (G_OBJECT (temp), keycode, modifiers);
+	    }
+	}
+    }
+
+  if (f)
+    gdk_event_set_device (xg_event,
+			  find_suitable_keyboard (SELECTED_FRAME ()));
+
+  gtk_main_do_event (xg_event);
+  xg_event->type = GDK_KEY_RELEASE;
+  gtk_main_do_event (xg_event);
+  gdk_event_free (xg_event);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
 DEFUN ("get-buffer-xwidgets", Fget_buffer_xwidgets, Sget_buffer_xwidgets,
        1, 1, 0,
        doc: /* Return a list of xwidgets associated with BUFFER.
@@ -221,16 +436,397 @@ xwidget_hidden (struct xwidget_view *xv)
   return xv->hidden;
 }
 
+struct xwidget *
+xwidget_from_id (uint32_t id)
+{
+  Lisp_Object key = make_fixnum (id);
+  Lisp_Object xwidget = Fgethash (key, id_to_xwidget_map, Qnil);
+
+  if (NILP (xwidget))
+    emacs_abort ();
+
+  return XXWIDGET (xwidget);
+}
+
 #ifdef USE_GTK
+
+static GdkDevice *
+find_suitable_pointer (struct frame *f)
+{
+  GdkSeat *seat = gdk_display_get_default_seat
+    (gtk_widget_get_display (FRAME_GTK_WIDGET (f)));
+
+  if (!seat)
+    return NULL;
+
+  return gdk_seat_get_pointer (seat);
+}
+
+static GdkDevice *
+find_suitable_keyboard (struct frame *f)
+{
+  GdkSeat *seat = gdk_display_get_default_seat
+    (gtk_widget_get_display (FRAME_GTK_WIDGET (f)));
+
+  if (!seat)
+    return NULL;
+
+  return gdk_seat_get_keyboard (seat);
+}
+
+static void
+find_widget_cb (GtkWidget *widget, void *user)
+{
+  find_widget (widget, user);
+}
+
+static void
+find_widget (GtkWidget *widget,
+	     struct widget_search_data *data)
+{
+  GtkAllocation new_allocation;
+  GdkWindow *window;
+  int x_offset = 0;
+  int y_offset = 0;
+
+  gtk_widget_get_allocation (widget, &new_allocation);
+
+  if (gtk_widget_get_has_window (widget))
+    {
+      new_allocation.x = 0;
+      new_allocation.y = 0;
+    }
+
+  if (gtk_widget_get_parent (widget) && !data->first)
+    {
+      window = gtk_widget_get_window (widget);
+      while (window != gtk_widget_get_window (gtk_widget_get_parent (widget)))
+        {
+          gint tx, ty, twidth, theight;
+
+	  if (!window)
+	    return;
+
+          twidth = gdk_window_get_width (window);
+          theight = gdk_window_get_height (window);
+
+          if (new_allocation.x < 0)
+            {
+              new_allocation.width += new_allocation.x;
+              new_allocation.x = 0;
+            }
+
+          if (new_allocation.y < 0)
+            {
+              new_allocation.height += new_allocation.y;
+              new_allocation.y = 0;
+            }
+
+          if (new_allocation.x + new_allocation.width > twidth)
+            new_allocation.width = twidth - new_allocation.x;
+          if (new_allocation.y + new_allocation.height > theight)
+            new_allocation.height = theight - new_allocation.y;
+
+          gdk_window_get_position (window, &tx, &ty);
+          new_allocation.x += tx;
+          x_offset += tx;
+          new_allocation.y += ty;
+          y_offset += ty;
+
+          window = gdk_window_get_parent (window);
+	}
+    }
+
+  if ((data->x >= new_allocation.x) && (data->y >= new_allocation.y) &&
+      (data->x < new_allocation.x + new_allocation.width) &&
+      (data->y < new_allocation.y + new_allocation.height))
+    {
+      /* First, check if the drag is in a valid drop site in
+       * one of our children
+       */
+      if (GTK_IS_CONTAINER (widget))
+        {
+          struct widget_search_data new_data = *data;
+
+          new_data.x -= x_offset;
+          new_data.y -= y_offset;
+          new_data.foundp = false;
+          new_data.first = false;
+
+          gtk_container_forall (GTK_CONTAINER (widget),
+                                find_widget_cb, &new_data);
+
+          data->foundp = new_data.foundp;
+          if (data->foundp)
+            data->data = new_data.data;
+        }
+
+      /* If not, and this widget is registered as a drop site, check to
+       * emit "drag_motion" to check if we are actually in
+       * a drop site.
+       */
+      if (!data->foundp)
+        {
+          data->foundp = true;
+          data->data = widget;
+        }
+    }
+}
+
+static GtkWidget *
+find_widget_at_pos (GtkWidget *w, int x, int y,
+		    int *new_x, int *new_y)
+{
+  struct widget_search_data data;
+
+  data.x = x;
+  data.y = y;
+  data.foundp = false;
+  data.first = true;
+
+  find_widget (w, &data);
+
+  if (data.foundp)
+    {
+      gtk_widget_translate_coordinates (w, data.data, x,
+					y, new_x, new_y);
+      return data.data;
+    }
+
+  *new_x = x;
+  *new_y = y;
+
+  return NULL;
+}
+
+static Emacs_Cursor
+cursor_for_hit (guint result, struct frame *frame)
+{
+  Emacs_Cursor cursor = FRAME_OUTPUT_DATA (frame)->nontext_cursor;
+
+  if ((result & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)
+      || (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION)
+      || (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT))
+    cursor = FRAME_X_OUTPUT (frame)->text_cursor;
+
+  if (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR)
+    cursor = FRAME_X_OUTPUT (frame)->vertical_drag_cursor;
+
+  if (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK)
+    cursor = FRAME_X_OUTPUT (frame)->hand_cursor;
+
+  return cursor;
+}
+
+static void
+define_cursors (struct xwidget *xw, WebKitHitTestResult *res)
+{
+  struct xwidget_view *xvw;
+
+  xw->hit_result = webkit_hit_test_result_get_context (res);
+
+  for (Lisp_Object tem = Vxwidget_view_list; CONSP (tem);
+       tem = XCDR (tem))
+    {
+      if (XWIDGET_VIEW_P (XCAR (tem)))
+	{
+	  xvw = XXWIDGET_VIEW (XCAR (tem));
+
+	  if (XXWIDGET (xvw->model) == xw)
+	    {
+	      xvw->cursor = cursor_for_hit (xw->hit_result, xvw->frame);
+	      if (xvw->wdesc != None)
+		XDefineCursor (xvw->dpy, xvw->wdesc, xvw->cursor);
+	    }
+	}
+    }
+}
+
+static void
+mouse_target_changed (WebKitWebView *webview,
+		      WebKitHitTestResult *hitresult,
+		      guint modifiers, gpointer xw)
+{
+  define_cursors (xw, hitresult);
+}
+
+
+static void
+xwidget_button_1 (struct xwidget_view *view,
+		  bool down_p, int x, int y, int button,
+		  int modifier_state, Time time)
+{
+  GdkEvent *xg_event = gdk_event_new (down_p ? GDK_BUTTON_PRESS : GDK_BUTTON_RELEASE);
+  struct xwidget *model = XXWIDGET (view->model);
+  GtkWidget *target;
+
+  /* X and Y should be relative to the origin of view->wdesc.  */
+  x += view->clip_left;
+  y += view->clip_top;
+
+  target = find_widget_at_pos (model->widgetwindow_osr, x, y, &x, &y);
+
+  if (!target)
+    target = model->widget_osr;
+
+  xg_event->any.window = gtk_widget_get_window (target);
+  g_object_ref (xg_event->any.window); /* The window will be unrefed
+					  later by gdk_event_free. */
+
+  xg_event->button.x = x;
+  xg_event->button.x_root = x;
+  xg_event->button.y = y;
+  xg_event->button.y_root = y;
+  xg_event->button.button = button;
+  xg_event->button.state = modifier_state;
+  xg_event->button.time = time;
+  xg_event->button.device = find_suitable_pointer (view->frame);
+
+  gtk_main_do_event (xg_event);
+  gdk_event_free (xg_event);
+}
+
+void
+xwidget_button (struct xwidget_view *view,
+		bool down_p, int x, int y, int button,
+		int modifier_state, Time time)
+{
+  if (button < 4 || button > 8)
+    xwidget_button_1 (view, down_p, x, y, button, modifier_state, time);
+  else
+    {
+      GdkEvent *xg_event = gdk_event_new (GDK_SCROLL);
+      struct xwidget *model = XXWIDGET (view->model);
+      GtkWidget *target;
+
+      x += view->clip_left;
+      y += view->clip_top;
+
+      target = find_widget_at_pos (model->widgetwindow_osr, x, y, &x, &y);
+
+      if (!target)
+	target = model->widget_osr;
+
+      xg_event->any.window = gtk_widget_get_window (target);
+      g_object_ref (xg_event->any.window); /* The window will be unrefed
+					      later by gdk_event_free. */
+      if (button == 4)
+	xg_event->scroll.direction = GDK_SCROLL_UP;
+      else if (button == 5)
+	xg_event->scroll.direction = GDK_SCROLL_DOWN;
+      else if (button == 6)
+	xg_event->scroll.direction = GDK_SCROLL_LEFT;
+      else
+	xg_event->scroll.direction = GDK_SCROLL_RIGHT;
+
+      xg_event->scroll.device = find_suitable_pointer (view->frame);
+
+      xg_event->scroll.x = x;
+      xg_event->scroll.x_root = x;
+      xg_event->scroll.y = y;
+      xg_event->scroll.y_root = y;
+      xg_event->scroll.state = modifier_state;
+      xg_event->scroll.time = time;
+
+      xg_event->scroll.delta_x = 0;
+      xg_event->scroll.delta_y = 0;
+
+      gtk_main_do_event (xg_event);
+      gdk_event_free (xg_event);
+    }
+}
+
+void
+xwidget_motion_or_crossing (struct xwidget_view *view, const XEvent *event)
+{
+  GdkEvent *xg_event = gdk_event_new (event->type == MotionNotify ? GDK_MOTION_NOTIFY :
+				      (event->type == LeaveNotify ? GDK_LEAVE_NOTIFY :
+				       GDK_ENTER_NOTIFY));
+  struct xwidget *model = XXWIDGET (view->model);
+  int x;
+  int y;
+  GtkWidget *target = find_widget_at_pos (model->widgetwindow_osr,
+					  (event->type == MotionNotify
+					   ? event->xmotion.x + view->clip_left
+					   : event->xmotion.y + view->clip_top),
+					  (event->type == MotionNotify
+					   ? event->xmotion.y + view->clip_left
+					   : event->xcrossing.y + view->clip_top),
+					  &x, &y);
+
+  if (!target)
+    target = model->widgetwindow_osr;
+
+  xg_event->any.window = gtk_widget_get_window (target);
+  g_object_ref (xg_event->any.window); /* The window will be unrefed
+					  later by gdk_event_free. */
+
+  if (event->type == MotionNotify)
+    {
+      xg_event->motion.x = x;
+      xg_event->motion.y = y;
+      xg_event->motion.x_root = event->xmotion.x_root;
+      xg_event->motion.y_root = event->xmotion.y_root;
+      xg_event->motion.time = event->xmotion.time;
+      xg_event->motion.state = event->xmotion.state;
+      xg_event->motion.device = find_suitable_pointer (view->frame);
+    }
+  else
+    {
+      xg_event->crossing.detail = min (5, event->xcrossing.detail);
+      xg_event->crossing.time = event->xcrossing.time;
+      xg_event->crossing.x = x;
+      xg_event->crossing.y = y;
+      xg_event->crossing.x_root = event->xcrossing.x_root;
+      xg_event->crossing.y_root = event->xcrossing.y_root;
+      gdk_event_set_device (xg_event, find_suitable_pointer (view->frame));
+    }
+
+  gtk_main_do_event (xg_event);
+  gdk_event_free (xg_event);
+}
+
+static void
+synthesize_focus_in_event (GtkWidget *offscreen_window)
+{
+  GdkWindow *wnd;
+  GdkEvent *focus_event;
+
+  if (!gtk_widget_get_realized (offscreen_window))
+    gtk_widget_realize (offscreen_window);
+
+  wnd = gtk_widget_get_window (offscreen_window);
+
+  focus_event = gdk_event_new (GDK_FOCUS_CHANGE);
+  focus_event->any.window = wnd;
+  focus_event->focus_change.in = TRUE;
+  g_object_ref (wnd);
+
+  gtk_main_do_event (focus_event);
+  gdk_event_free (focus_event);
+}
+
+struct xwidget_view *
+xwidget_view_from_window (Window wdesc)
+{
+  Lisp_Object key = make_fixnum (wdesc);
+  Lisp_Object xwv = Fgethash (key, x_window_to_xwv_map, Qnil);
+
+  if (NILP (xwv))
+    return NULL;
+
+  return XXWIDGET_VIEW (xwv);
+}
+
 static void
 xwidget_show_view (struct xwidget_view *xv)
 {
   xv->hidden = false;
-  gtk_widget_show (xv->widgetwindow);
-  gtk_fixed_move (GTK_FIXED (xv->emacswindow),
-                  xv->widgetwindow,
-                  xv->x + xv->clip_left,
-                  xv->y + xv->clip_top);
+  XMoveWindow (xv->dpy, xv->wdesc,
+	       xv->x + xv->clip_left,
+	       xv->y + xv->clip_top);
+  XMapWindow (xv->dpy, xv->wdesc);
+  XFlush (xv->dpy);
 }
 
 /* Hide an xwidget view.  */
@@ -238,28 +834,64 @@ xwidget_show_view (struct xwidget_view *xv)
 xwidget_hide_view (struct xwidget_view *xv)
 {
   xv->hidden = true;
-  gtk_fixed_move (GTK_FIXED (xv->emacswindow), xv->widgetwindow,
-                  10000, 10000);
+  XUnmapWindow (xv->dpy, xv->wdesc);
+  XFlush (xv->dpy);
+}
+
+static void
+xv_do_draw (struct xwidget_view *xw, struct xwidget *w)
+{
+  GtkOffscreenWindow *wnd;
+  cairo_surface_t *surface;
+  block_input ();
+  wnd = GTK_OFFSCREEN_WINDOW (w->widgetwindow_osr);
+  surface = gtk_offscreen_window_get_surface (wnd);
+
+  cairo_save (xw->cr_context);
+  if (surface)
+    {
+      cairo_set_source_surface (xw->cr_context, surface, xw->clip_left,
+				xw->clip_top);
+      cairo_set_operator (xw->cr_context, CAIRO_OPERATOR_SOURCE);
+      cairo_paint (xw->cr_context);
+    }
+  cairo_restore (xw->cr_context);
+
+  unblock_input ();
 }
 
 /* When the off-screen webkit master view changes this signal is called.
    It copies the bitmap from the off-screen instance.  */
 static gboolean
 offscreen_damage_event (GtkWidget *widget, GdkEvent *event,
-                        gpointer xv_widget)
-{
-  /* Queue a redraw of onscreen widget.
-     There is a guard against receiving an invalid widget,
-     which should only happen if we failed to remove the
-     specific signal handler for the damage event.  */
-  if (GTK_IS_WIDGET (xv_widget))
-    gtk_widget_queue_draw (GTK_WIDGET (xv_widget));
-  else
-    message ("Warning, offscreen_damage_event received invalid xv pointer:%p\n",
-             xv_widget);
+                        gpointer xwidget)
+{
+  block_input ();
+
+  for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail);
+       tail = XCDR (tail))
+    {
+      if (XWIDGET_VIEW_P (XCAR (tail)))
+	{
+	  struct xwidget_view *view = XXWIDGET_VIEW (XCAR (tail));
+
+	  if (view->wdesc && XXWIDGET (view->model) == xwidget)
+	    xv_do_draw (view, XXWIDGET (view->model));
+	}
+    }
+
+  unblock_input ();
 
   return FALSE;
 }
+
+void
+xwidget_expose (struct xwidget_view *xv)
+{
+  struct xwidget *xw = XXWIDGET (xv->model);
+
+  xv_do_draw (xv, xw);
+}
 #endif /* USE_GTK */
 
 void
@@ -313,22 +945,108 @@ store_xwidget_js_callback_event (struct xwidget *xw,
 
 
 #ifdef USE_GTK
+static void
+store_xwidget_display_event (struct xwidget *xw)
+{
+  struct input_event evt;
+  Lisp_Object val;
+
+  XSETXWIDGET (val, xw);
+  EVENT_INIT (evt);
+  evt.kind = XWIDGET_DISPLAY_EVENT;
+  evt.frame_or_window = Qnil;
+  evt.arg = val;
+  kbd_buffer_store_event (&evt);
+}
+
+static void
+webkit_ready_to_show (WebKitWebView *new_view,
+		      gpointer user_data)
+{
+  Lisp_Object tem;
+  struct xwidget *xw;
+
+  for (tem = Vxwidget_list; CONSP (tem); tem = XCDR (tem))
+    {
+      if (XWIDGETP (XCAR (tem)))
+	{
+	  xw = XXWIDGET (XCAR (tem));
+
+	  if (EQ (xw->type, Qwebkit)
+	      && WEBKIT_WEB_VIEW (xw->widget_osr) == new_view)
+	    store_xwidget_display_event (xw);
+	}
+    }
+}
+
+static GtkWidget *
+webkit_create_cb_1 (WebKitWebView *webview,
+		    struct xwidget_view *xv)
+{
+  Lisp_Object related;
+  Lisp_Object xwidget;
+  GtkWidget *widget;
+
+  XSETXWIDGET (related, xv);
+  xwidget = Fmake_xwidget (Qwebkit, Qnil, make_fixnum (0),
+			   make_fixnum (0), Qnil,
+			   build_string (" *detached xwidget buffer*"),
+			   related);
+
+  if (NILP (xwidget))
+    return NULL;
+
+  widget = XXWIDGET (xwidget)->widget_osr;
+
+  g_signal_connect (G_OBJECT (widget), "ready-to-show",
+		    G_CALLBACK (webkit_ready_to_show), NULL);
+
+  return widget;
+}
+
+static GtkWidget *
+webkit_create_cb (WebKitWebView *webview,
+		  WebKitNavigationAction *nav_action,
+		  gpointer user_data)
+{
+  switch (webkit_navigation_action_get_navigation_type (nav_action))
+    {
+    case WEBKIT_NAVIGATION_TYPE_OTHER:
+      return webkit_create_cb_1 (webview, user_data);
+
+    case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD:
+    case WEBKIT_NAVIGATION_TYPE_RELOAD:
+    case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED:
+    case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
+    case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED:
+    default:
+      return NULL;
+    }
+}
+
 void
 webkit_view_load_changed_cb (WebKitWebView *webkitwebview,
                              WebKitLoadEvent load_event,
                              gpointer data)
 {
-  switch (load_event) {
-  case WEBKIT_LOAD_FINISHED:
+  struct xwidget *xw = g_object_get_data (G_OBJECT (webkitwebview),
+					  XG_XWIDGET);
+
+  switch (load_event)
     {
-      struct xwidget *xw = g_object_get_data (G_OBJECT (webkitwebview),
-                                              XG_XWIDGET);
-      store_xwidget_event_string (xw, "load-changed", "");
+    case WEBKIT_LOAD_FINISHED:
+      store_xwidget_event_string (xw, "load-changed", "load-finished");
+      break;
+    case WEBKIT_LOAD_STARTED:
+      store_xwidget_event_string (xw, "load-changed", "load-started");
+      break;
+    case WEBKIT_LOAD_REDIRECTED:
+      store_xwidget_event_string (xw, "load-changed", "load-redirected");
+      break;
+    case WEBKIT_LOAD_COMMITTED:
+      store_xwidget_event_string (xw, "load-changed", "load-committed");
       break;
     }
-  default:
-    break;
-  }
 }
 
 /* Recursively convert a JavaScript value to a Lisp value. */
@@ -498,51 +1216,6 @@ webkit_decide_policy_cb (WebKitWebView *webView,
     return FALSE;
   }
 }
-
-
-/* For gtk3 offscreen rendered widgets.  */
-static gboolean
-xwidget_osr_draw_cb (GtkWidget *widget, cairo_t *cr, gpointer data)
-{
-  struct xwidget *xw = g_object_get_data (G_OBJECT (widget), XG_XWIDGET);
-  struct xwidget_view *xv = g_object_get_data (G_OBJECT (widget),
-                                               XG_XWIDGET_VIEW);
-
-  cairo_rectangle (cr, 0, 0, xv->clip_right, xv->clip_bottom);
-  cairo_clip (cr);
-
-  gtk_widget_draw (xw->widget_osr, cr);
-  return FALSE;
-}
-
-static gboolean
-xwidget_osr_event_forward (GtkWidget *widget, GdkEvent *event,
-			   gpointer user_data)
-{
-  /* Copy events that arrive at the outer widget to the offscreen widget.  */
-  struct xwidget *xw = g_object_get_data (G_OBJECT (widget), XG_XWIDGET);
-  GdkEvent *eventcopy = gdk_event_copy (event);
-  eventcopy->any.window = gtk_widget_get_window (xw->widget_osr);
-
-  /* TODO: This might leak events.  They should be deallocated later,
-     perhaps in xwgir_event_cb.  */
-  gtk_main_do_event (eventcopy);
-
-  /* Don't propagate this event further.  */
-  return TRUE;
-}
-
-static gboolean
-xwidget_osr_event_set_embedder (GtkWidget *widget, GdkEvent *event,
-				gpointer data)
-{
-  struct xwidget_view *xv = data;
-  struct xwidget *xww = XXWIDGET (xv->model);
-  gdk_offscreen_window_set_embedder (gtk_widget_get_window
-				     (xww->widgetwindow_osr),
-                                     gtk_widget_get_window (xv->widget));
-  return FALSE;
-}
 #endif /* USE_GTK */
 
 
@@ -568,63 +1241,19 @@ xwidget_init_view (struct xwidget *xww,
   XSETXWIDGET (xv->model, xww);
 
 #ifdef USE_GTK
-  if (EQ (xww->type, Qwebkit))
-    {
-      xv->widget = gtk_drawing_area_new ();
-      /* Expose event handling.  */
-      gtk_widget_set_app_paintable (xv->widget, TRUE);
-      gtk_widget_add_events (xv->widget, GDK_ALL_EVENTS_MASK);
-
-      /* Draw the view on damage-event.  */
-      g_signal_connect (G_OBJECT (xww->widgetwindow_osr), "damage-event",
-                        G_CALLBACK (offscreen_damage_event), xv->widget);
-
-      if (EQ (xww->type, Qwebkit))
-        {
-          g_signal_connect (G_OBJECT (xv->widget), "button-press-event",
-                            G_CALLBACK (xwidget_osr_event_forward), NULL);
-          g_signal_connect (G_OBJECT (xv->widget), "button-release-event",
-                            G_CALLBACK (xwidget_osr_event_forward), NULL);
-          g_signal_connect (G_OBJECT (xv->widget), "motion-notify-event",
-                            G_CALLBACK (xwidget_osr_event_forward), NULL);
-        }
-      else
-        {
-          /* xwgir debug, orthogonal to forwarding.  */
-          g_signal_connect (G_OBJECT (xv->widget), "enter-notify-event",
-                            G_CALLBACK (xwidget_osr_event_set_embedder), xv);
-        }
-      g_signal_connect (G_OBJECT (xv->widget), "draw",
-                        G_CALLBACK (xwidget_osr_draw_cb), NULL);
-    }
+  xv->dpy = FRAME_X_DISPLAY (s->f);
 
-  /* Widget realization.
-
-     Make container widget first, and put the actual widget inside the
-     container later.  Drawing should crop container window if necessary
-     to handle case where xwidget is partially obscured by other Emacs
-     windows.  Other containers than gtk_fixed where explored, but
-     gtk_fixed had the most predictable behavior so far.  */
-
-  xv->emacswindow = FRAME_GTK_WIDGET (s->f);
-  xv->widgetwindow = gtk_fixed_new ();
-  gtk_widget_set_has_window (xv->widgetwindow, TRUE);
-  gtk_container_add (GTK_CONTAINER (xv->widgetwindow), xv->widget);
-
-  /* Store some xwidget data in the gtk widgets.  */
-  g_object_set_data (G_OBJECT (xv->widget), XG_FRAME_DATA, s->f);
-  g_object_set_data (G_OBJECT (xv->widget), XG_XWIDGET, xww);
-  g_object_set_data (G_OBJECT (xv->widget), XG_XWIDGET_VIEW, xv);
-  g_object_set_data (G_OBJECT (xv->widgetwindow), XG_XWIDGET, xww);
-  g_object_set_data (G_OBJECT (xv->widgetwindow), XG_XWIDGET_VIEW, xv);
-
-  gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xww->width,
-                               xww->height);
-  gtk_widget_set_size_request (xv->widgetwindow, xww->width, xww->height);
-  gtk_fixed_put (GTK_FIXED (FRAME_GTK_WIDGET (s->f)), xv->widgetwindow, x, y);
   xv->x = x;
   xv->y = y;
-  gtk_widget_show_all (xv->widgetwindow);
+
+  xv->clip_left = 0;
+  xv->clip_right = xww->width;
+  xv->clip_top = 0;
+  xv->clip_bottom = xww->height;
+
+  xv->wdesc = None;
+  xv->frame = s->f;
+  xv->cursor = cursor_for_hit (xww->hit_result, s->f);
 #elif defined NS_IMPL_COCOA
   nsxwidget_init_view (xv, xww, s, x, y);
   nsxwidget_resize_view(xv, xww->width, xww->height);
@@ -681,6 +1310,8 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
   window_box (s->w, TEXT_AREA, &text_area_x, &text_area_y,
               &text_area_width, &text_area_height);
 
+  /* On X11, this keeps generating expose events.  */
+#ifndef USE_GTK
   /* Resize xwidget webkit if its container window size is changed in
      some ways, for example, a buffer became hidden in small split
      window, then it can appear front in merged whole window.  */
@@ -693,6 +1324,7 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
                        make_int (text_area_width),
                        make_int (text_area_height));
     }
+#endif
 
   clip_left = max (0, text_area_x - x);
   clip_right = max (clip_left,
@@ -711,15 +1343,51 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
      later.  */
   bool moved = (xv->x + xv->clip_left != x + clip_left
 		|| xv->y + xv->clip_top != y + clip_top);
+
+#ifdef USE_GTK
+  bool wdesc_was_none = xv->wdesc == None;
+#endif
   xv->x = x;
   xv->y = y;
 
+#ifdef USE_GTK
+  block_input ();
+  if (xv->wdesc == None)
+    {
+      Lisp_Object xvw;
+      XSETXWIDGET_VIEW (xvw, xv);
+      XSetWindowAttributes a;
+      a.event_mask = (ExposureMask | ButtonPressMask | ButtonReleaseMask
+		      | PointerMotionMask | EnterWindowMask | LeaveWindowMask);
+
+      xv->wdesc = XCreateWindow (xv->dpy, FRAME_X_WINDOW (s->f),
+				 x + clip_left, y + clip_top,
+				 clip_right - clip_left,
+				 clip_bottom - clip_top, 0,
+				 CopyFromParent, CopyFromParent,
+				 CopyFromParent, CWEventMask, &a);
+      XDefineCursor (xv->dpy, xv->wdesc, xv->cursor);
+      xv->cr_surface = cairo_xlib_surface_create (xv->dpy,
+						  xv->wdesc,
+						  FRAME_DISPLAY_INFO (s->f)->visual,
+						  clip_right - clip_left,
+						  clip_bottom - clip_top);
+      xv->cr_context = cairo_create (xv->cr_surface);
+      Fputhash (make_fixnum (xv->wdesc), xvw, x_window_to_xwv_map);
+
+      moved = false;
+    }
+#endif
+
   /* Has it moved?  */
   if (moved)
     {
 #ifdef USE_GTK
-      gtk_fixed_move (GTK_FIXED (FRAME_GTK_WIDGET (s->f)),
-                      xv->widgetwindow, x + clip_left, y + clip_top);
+      XMoveResizeWindow (xv->dpy, xv->wdesc, x + clip_left, y + clip_top,
+			 clip_right - clip_left, clip_bottom - clip_top);
+      XFlush (xv->dpy);
+      cairo_xlib_surface_set_size (xv->cr_surface, clip_right - clip_left,
+				   clip_bottom - clip_top);
 #elif defined NS_IMPL_COCOA
       nsxwidget_move_view (xv, x + clip_left, y + clip_top);
 #endif
@@ -735,10 +1403,14 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
       || xv->clip_top != clip_top || xv->clip_left != clip_left)
     {
 #ifdef USE_GTK
-      gtk_widget_set_size_request (xv->widgetwindow, clip_right - clip_left,
-                                   clip_bottom - clip_top);
-      gtk_fixed_move (GTK_FIXED (xv->widgetwindow), xv->widget, -clip_left,
-                      -clip_top);
+      if (!wdesc_was_none && !moved)
+	{
+	  XResizeWindow (xv->dpy, xv->wdesc, clip_right - clip_left,
+			 clip_bottom - clip_top);
+	  XFlush (xv->dpy);
+	  cairo_xlib_surface_set_size (xv->cr_surface, clip_right - clip_left,
+				       clip_bottom - clip_top);
+	}
 #elif defined NS_IMPL_COCOA
       nsxwidget_resize_view (xv, clip_right - clip_left,
                              clip_bottom - clip_top);
@@ -758,12 +1430,15 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
   if (!xwidget_hidden (xv))
     {
 #ifdef USE_GTK
-      gtk_widget_queue_draw (xv->widgetwindow);
-      gtk_widget_queue_draw (xv->widget);
+      gtk_widget_queue_draw (xww->widget_osr);
 #elif defined NS_IMPL_COCOA
       nsxwidget_set_needsdisplay (xv);
 #endif
     }
+
+#ifdef USE_GTK
+  unblock_input ();
+#endif
 }
 
 static bool
@@ -975,16 +1650,13 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
           struct xwidget_view *xv = XXWIDGET_VIEW (XCAR (tail));
           if (XXWIDGET (xv->model) == xw)
             {
-#ifdef USE_GTK
-              gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xw->width,
-                                           xw->height);
-#elif defined NS_IMPL_COCOA
-              nsxwidget_resize_view(xv, xw->width, xw->height);
-#endif
+	      wset_redisplay (XWINDOW (xv->w));
             }
         }
     }
 
+  redisplay ();
+
   return Qnil;
 }
 
@@ -1084,13 +1756,15 @@ DEFUN ("delete-xwidget-view",
   CHECK_XWIDGET_VIEW (xwidget_view);
   struct xwidget_view *xv = XXWIDGET_VIEW (xwidget_view);
 #ifdef USE_GTK
-  gtk_widget_destroy (xv->widgetwindow);
-  /* xv->model still has signals pointing to the view.  There can be
-     several views.  Find the matching signals and delete them all.  */
-  g_signal_handlers_disconnect_matched  (XXWIDGET (xv->model)->widgetwindow_osr,
-                                         G_SIGNAL_MATCH_DATA,
-                                         0, 0, 0, 0,
-                                         xv->widget);
+  if (xv->wdesc != None)
+    {
+      block_input ();
+      cairo_destroy (xv->cr_context);
+      cairo_surface_destroy (xv->cr_surface);
+      XDestroyWindow (xv->dpy, xv->wdesc);
+      Fremhash (make_fixnum (xv->wdesc), x_window_to_xwv_map);
+      unblock_input ();
+    }
 #elif defined NS_IMPL_COCOA
   nsxwidget_delete_view (xv);
 #endif
@@ -1145,6 +1819,19 @@ DEFUN ("xwidget-buffer",
   return XXWIDGET (xwidget)->buffer;
 }
 
+DEFUN ("set-xwidget-buffer",
+       Fset_xwidget_buffer, Sset_xwidget_buffer,
+       2, 2, 0,
+       doc: /* Set XWIDGET's buffer to BUFFER.  */)
+  (Lisp_Object xwidget, Lisp_Object buffer)
+{
+  CHECK_XWIDGET (xwidget);
+  CHECK_BUFFER (buffer);
+
+  XXWIDGET (xwidget)->buffer = buffer;
+  return Qnil;
+}
+
 DEFUN ("set-xwidget-plist",
        Fset_xwidget_plist, Sset_xwidget_plist,
        2, 2, 0,
@@ -1183,6 +1870,166 @@ DEFUN ("xwidget-query-on-exit-flag",
   return (XXWIDGET (xwidget)->kill_without_query ? Qnil : Qt);
 }
 
+DEFUN ("xwidget-webkit-search", Fxwidget_webkit_search, Sxwidget_webkit_search,
+       2, 5, 0,
+       doc: /* Begin an incremental search operation in an xwidget.
+QUERY should be a string containing the text to search for.  XWIDGET
+should be a WebKit xwidget where the search will take place.  When the
+search operation is complete, callers should also call
+`xwidget-webkit-finish-search' to complete the search operation.
+
+CASE-INSENSITIVE, when non-nil, will cause the search to ignore the
+case of characters inside QUERY.  BACKWARDS, when non-nil, will cause
+the search to proceed towards the beginning of the widget's contents.
+WRAP-AROUND, when nil, will cause the search to stop upon hitting the
+end of the widget's contents.
+
+It is OK to call this function even when a search is already in
+progress.  In that case, the previous search query will be replaced
+with QUERY.  */)
+  (Lisp_Object query, Lisp_Object xwidget, Lisp_Object case_insensitive,
+   Lisp_Object backwards, Lisp_Object wrap_around)
+{
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+  WebKitFindOptions opt;
+  struct xwidget *xw;
+  gchar *g_query;
+#endif
+
+  CHECK_STRING (query);
+  CHECK_XWIDGET (xwidget);
+
+#ifdef USE_GTK
+  xw = XXWIDGET (xwidget);
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  query = ENCODE_UTF_8 (query);
+  opt = WEBKIT_FIND_OPTIONS_NONE;
+  g_query = xstrdup (SSDATA (query));
+
+  if (!NILP (case_insensitive))
+    opt |= WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE;
+  if (!NILP (backwards))
+    opt |= WEBKIT_FIND_OPTIONS_BACKWARDS;
+  if (!NILP (wrap_around))
+    opt |= WEBKIT_FIND_OPTIONS_WRAP_AROUND;
+
+  if (xw->find_text)
+    xfree (xw->find_text);
+  xw->find_text = g_query;
+
+  block_input ();
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search (controller, g_query, opt, G_MAXUINT);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
+DEFUN ("xwidget-webkit-next-result", Fxwidget_webkit_next_result,
+       Sxwidget_webkit_next_result, 1, 1, 0,
+       doc: /* Show the next result matching the current search query.
+
+XWIDGET should be an xwidget that currently has a search query.
+Before calling this function, you should start a search operation
+using `xwidget-webkit-search'.  */)
+  (Lisp_Object xwidget)
+{
+  struct xwidget *xw;
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!xw->find_text)
+    error ("Widget has no ongoing search operation");
+
+#ifdef USE_GTK
+  block_input ();
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search_next (controller);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
+DEFUN ("xwidget-webkit-previous-result", Fxwidget_webkit_previous_result,
+       Sxwidget_webkit_previous_result, 1, 1, 0,
+       doc: /* Show the previous result matching the current search query.
+
+XWIDGET should be an xwidget that currently has a search query.
+Before calling this function, you should start a search operation
+using `xwidget-webkit-search'.  */)
+  (Lisp_Object xwidget)
+{
+  struct xwidget *xw;
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!xw->find_text)
+    error ("Widget has no ongoing search operation");
+
+#ifdef USE_GTK
+  block_input ();
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search_previous (controller);
+
+  if (xw->find_text)
+    {
+      xfree (xw->find_text);
+      xw->find_text = NULL;
+    }
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
+DEFUN ("xwidget-webkit-finish-search", Fxwidget_webkit_finish_search,
+       Sxwidget_webkit_finish_search, 1, 1, 0,
+       doc: /* Finish XWIDGET's search operation.
+
+XWIDGET should be an xwidget that currently has a search query.
+Before calling this function, you should start a search operation
+using `xwidget-webkit-search'.  */)
+  (Lisp_Object xwidget)
+{
+  struct xwidget *xw;
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!xw->find_text)
+    error ("Widget has no ongoing search operation");
+
+#ifdef USE_GTK
+  block_input ();
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search_finish (controller);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
 void
 syms_of_xwidget (void)
 {
@@ -1215,6 +2062,12 @@ syms_of_xwidget (void)
   defsubr (&Sxwidget_plist);
   defsubr (&Sxwidget_buffer);
   defsubr (&Sset_xwidget_plist);
+  defsubr (&Sxwidget_perform_lispy_event);
+  defsubr (&Sxwidget_webkit_search);
+  defsubr (&Sxwidget_webkit_finish_search);
+  defsubr (&Sxwidget_webkit_next_result);
+  defsubr (&Sxwidget_webkit_previous_result);
+  defsubr (&Sset_xwidget_buffer);
 
   DEFSYM (QCxwidget, ":xwidget");
   DEFSYM (QCtitle, ":title");
@@ -1236,6 +2089,15 @@ syms_of_xwidget (void)
   Vxwidget_view_list = Qnil;
 
   Fprovide (intern ("xwidget-internal"), Qnil);
+
+  id_to_xwidget_map = CALLN (Fmake_hash_table, QCtest, Qeq);
+  staticpro (&id_to_xwidget_map);
+
+#ifdef USE_GTK
+  x_window_to_xwv_map = CALLN (Fmake_hash_table, QCtest, Qeq);
+
+  staticpro (&x_window_to_xwv_map);
+#endif
 }
 
 
@@ -1374,7 +2236,7 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
 		  /* The only call to xwidget_end_redisplay is in dispnew.
 		     xwidget_end_redisplay (w->current_matrix);  */
 		  struct xwidget_view *xv
-		    = xwidget_view_lookup (glyph->u.xwidget, w);
+		    = xwidget_view_lookup (xwidget_from_id (glyph->u.xwidget), w);
 #ifdef USE_GTK
 		  /* FIXME: Is it safe to assume xwidget_view_lookup
 		     always succeeds here?  If so, this comment can be removed.
@@ -1424,6 +2286,26 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
     }
 }
 
+#ifdef USE_GTK
+void
+kill_frame_xwidget_views (struct frame *f)
+{
+  Lisp_Object rem = Qnil;
+
+  for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail);
+       tail = XCDR (tail))
+    {
+      if (XXWIDGET_VIEW (XCAR (tail))->frame == f)
+	rem = Fcons (XCAR (tail), rem);
+    }
+
+  for (; CONSP (rem); rem = XCDR (rem))
+    {
+      Fdelete_xwidget_view (XCAR (rem));
+    }
+}
+#endif
+
 /* Kill all xwidget in BUFFER.  */
 void
 kill_buffer_xwidgets (Lisp_Object buffer)
@@ -1437,12 +2319,15 @@ kill_buffer_xwidgets (Lisp_Object buffer)
       {
         CHECK_XWIDGET (xwidget);
         struct xwidget *xw = XXWIDGET (xwidget);
+	Fremhash (make_fixnum (xw->xwidget_id), id_to_xwidget_map);
 #ifdef USE_GTK
         if (xw->widget_osr && xw->widgetwindow_osr)
           {
             gtk_widget_destroy (xw->widget_osr);
             gtk_widget_destroy (xw->widgetwindow_osr);
           }
+	if (xw->find_text)
+	  xfree (xw->find_text);
 	if (!NILP (xw->script_callbacks))
 	  for (ptrdiff_t idx = 0; idx < ASIZE (xw->script_callbacks); idx++)
 	    {
diff --git a/src/xwidget.h b/src/xwidget.h
index 591f23489d..3bab6d5b00 100644
--- a/src/xwidget.h
+++ b/src/xwidget.h
@@ -32,6 +32,8 @@ #define XWIDGET_H_INCLUDED
 
 #if defined (USE_GTK)
 #include <gtk/gtk.h>
+#include <X11/Xlib.h>
+#include "xterm.h"
 #elif defined (NS_IMPL_COCOA) && defined (__OBJC__)
 #import <AppKit/NSView.h>
 #import "nsxwidget.h"
@@ -59,11 +61,14 @@ #define XWIDGET_H_INCLUDED
 
   int height;
   int width;
+  uint32_t xwidget_id;
 
 #if defined (USE_GTK)
   /* For offscreen widgets, unused if not osr.  */
   GtkWidget *widget_osr;
   GtkWidget *widgetwindow_osr;
+  guint hit_result;
+  gchar *find_text;
 #elif defined (NS_IMPL_COCOA)
 # ifdef __OBJC__
   /* For offscreen widgets, unused if not osr.  */
@@ -98,9 +103,13 @@ #define XWIDGET_H_INCLUDED
   bool hidden;
 
 #if defined (USE_GTK)
-  GtkWidget *widget;
-  GtkWidget *widgetwindow;
-  GtkWidget *emacswindow;
+  Display *dpy;
+  Window wdesc;
+  Emacs_Cursor cursor;
+  struct frame *frame;
+
+  cairo_surface_t *cr_surface;
+  cairo_t *cr_context;
 #elif defined (NS_IMPL_COCOA)
 # ifdef __OBJC__
   XvWindow *xvWindow;
@@ -162,6 +171,15 @@ #define XG_XWIDGET_VIEW "emacs_xwidget_view"
 void store_xwidget_js_callback_event (struct xwidget *xw,
                                       Lisp_Object proc,
                                       Lisp_Object argument);
+struct xwidget_view *xwidget_view_from_window (Window wdesc);
+void xwidget_expose (struct xwidget_view *xv);
+
+extern struct xwidget *xwidget_from_id (uint32_t id);
+extern void kill_frame_xwidget_views (struct frame *f);
+extern void xwidget_button (struct xwidget_view *, bool, int,
+			    int, int, int, Time);
+extern void xwidget_motion_or_crossing (struct xwidget_view *,
+					const XEvent *);
 #else
 INLINE_HEADER_BEGIN
 INLINE void syms_of_xwidget (void) {}

^ permalink raw reply related	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07  0:41                         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-07  0:55                           ` Lars Ingebrigtsen
  2021-11-07  1:16                             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-07  1:04                           ` Lars Ingebrigtsen
  2021-11-07 10:09                           ` Eli Zaretskii
  2 siblings, 1 reply; 39+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-07  0:55 UTC (permalink / raw)
  To: Po Lu; +Cc: 51473

Po Lu <luangruo@yahoo.com> writes:

> Thanks, here you go.

Thanks!  I've now tried on Debian/bookworm, and everything seems to be
working fine.  (But truth be told, I don't see much difference
before/after the patches with some very cursory browsing on
youtube.com and google.com.)

Now for Macos...

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07  0:41                         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-07  0:55                           ` Lars Ingebrigtsen
@ 2021-11-07  1:04                           ` Lars Ingebrigtsen
  2021-11-07  1:18                             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-07 10:09                           ` Eli Zaretskii
  2 siblings, 1 reply; 39+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-07  1:04 UTC (permalink / raw)
  To: Po Lu; +Cc: 51473

On Macos, it fails with:

  CC       dispnew.o
In file included from dispnew.c:43:
./xwidget.h:182:12: error: unknown type name 'XEvent'
                                        const XEvent *);
                                              ^
1 error generated.
make[1]: *** [dispnew.o] Error 1


-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no






^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07  0:55                           ` Lars Ingebrigtsen
@ 2021-11-07  1:16                             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 0 replies; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-07  1:16 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: Eli Zaretskii, 51473

Lars Ingebrigtsen <larsi@gnus.org> writes:

> Thanks!  I've now tried on Debian/bookworm, and everything seems to be
> working fine.  (But truth be told, I don't see much difference
> before/after the patches with some very cursory browsing on
> youtube.com and google.com.)

Less flickering, for one, and you can also press "e" and try typing.





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07  1:04                           ` Lars Ingebrigtsen
@ 2021-11-07  1:18                             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-07  1:23                               ` Lars Ingebrigtsen
  0 siblings, 1 reply; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-07  1:18 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: Eli Zaretskii, 51473

[-- Attachment #1: Type: text/plain, Size: 307 bytes --]

Lars Ingebrigtsen <larsi@gnus.org> writes:

>   CC       dispnew.o
> In file included from dispnew.c:43:
> ./xwidget.h:182:12: error: unknown type name 'XEvent'
>                                         const XEvent *);
>                                               ^

Does this version fix the problem?


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: test.diff --]
[-- Type: text/x-patch, Size: 94256 bytes --]

diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi
index 7f91e1c188..1207ab5e9a 100644
--- a/doc/emacs/misc.texi
+++ b/doc/emacs/misc.texi
@@ -2953,6 +2953,34 @@ Embedded WebKit Widgets
 reloading it.  Type @w{@kbd{C-h b}} in that buffer to see the key
 bindings.
 
+@findex xwidget-webkit-edit-mode
+@cindex xwidget-webkit-edit-mode
+  By default, typing a self-inserting character inside an xwidget
+webkit buffer will do nothing, or trigger some special action.  To
+make those characters and other common editing keys insert themselves
+when pressed, you can enable @code{xwidget-webkit-edit-mode}, which
+redefines them to be passed through to the WebKit xwidget.
+
+You can also enable @code{xwidget-webkit-edit-mode} by typing @kbd{e}
+inside the xwidget webkit buffer.
+
+@findex xwidget-webkit-isearch-mode
+@cindex xwidget-webkit-isearch-mode
+@cindex searching in webkit buffers
+  @code{xwidget-webkit-isearch-mode} is a minor mode that behaves
+similarly to incremental search (@pxref{Incremental Search}), but
+operates on the contents of a WebKit widget instead of the current
+buffer.  It is bound to @kbd{C-s} and @kbd{C-r} inside xwidget-webkit
+buffers.  When it is enabled through @kbd{C-r}, the initial search
+will be performed in reverse direction.
+
+Typing any self-inserting character will cause the character to be
+inserted into the current search query.  Typing @kbd{C-s} will cause
+the WebKit widget to display the next search result, while typing
+@kbd{C-r} will cause it to display the last.
+
+To leave incremental search, you can type @kbd{C-g}.
+
 @node Browse-URL
 @subsection  Following URLs
 @cindex World Wide Web
diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi
index b38a83b4fe..832b570b6a 100644
--- a/doc/lispref/commands.texi
+++ b/doc/lispref/commands.texi
@@ -1176,6 +1176,7 @@ Input Events
 * Repeat Events::               Double and triple click (or drag, or down).
 * Motion Events::               Just moving the mouse, not pushing a button.
 * Focus Events::                Moving the mouse between frames.
+* Xwidget Events::              Events generated by xwidgets.
 * Misc Events::                 Other events the system can generate.
 * Event Examples::              Examples of the lists for mouse events.
 * Classifying Events::          Finding the modifier keys in an event symbol.
@@ -1871,6 +1872,76 @@ Focus Events
 so that the focus event comes either before or after the multi-event key
 sequence, and not within it.
 
+@node Xwidget Events
+@subsection Xwidget events
+
+Xwidgets (@pxref{Xwidgets}) can send events to update Lisp programs on
+their status.  These events are dubbed @code{xwidget-events}, and
+contain various data describing the nature of the change.
+
+@table @code
+@cindex @code{xwidget-event} event
+@item (xwidget-event @var{kind} @var{xwidget} @var{arg})
+This event is sent whenever some kind of update occurs in
+@var{xwidget}.  There are several types of updates, which are
+identified by @var{kind}.
+
+@cindex @code{load-changed} xwidget events
+An xwidget event with @var{kind} set to @code{load-changed} indicates
+that the @var{xwidget} has reached a particular point of the
+page-loading process.  When these events are sent, @var{arg} will
+contain a string that futher describes the status of the widget.
+
+@cindex @samp{"load-finished"} in xwidgets
+When @var{arg} is @samp{"load-finished"}, it means the xwidget has
+finished processing whatever page-loading operation that it was
+previously performing.
+
+@cindex @samp{"load-started"} in xwidgets
+Otherwise, if it is @samp{"load-started"}, then the widget has begun a
+page-loading operation.
+
+@cindex @samp{"load-redirected"} in xwidgets
+If @var{arg} is @samp{"load-redirected"}, it means the widget has
+encountered and followed a redirect during the page-loading operation.
+
+@cindex @samp{"load-committed"} in xwidgets
+If @var{arg} is @samp{"load-committed"}, then the widget has committed
+to a given URL during the page-loading operation.  This means that the
+URL is the final URL that will be rendered by @var{xwidget} during the
+current page-loading operation.
+
+@cindex @code{download-callback} xwidget events
+An event with @var{kind} set to @code{download-callback} indicates
+that a download of some kind has been completed.
+
+In these events, there can be arguments after @var{arg}, which itself
+indicates the URL that the download file was retrieved from: the first
+argument after @var{arg} indicates the MIME type of the download, as a
+string, while the second such argument contains the full file path to
+the downloaded file.
+
+@cindex @code{download-started} xwidget events
+An event with @var{kind} set to @code{download-started} indicates that
+a download has been started.  In these events, @var{arg} contains the
+URL of the file that is currently being downloaded.
+
+@cindex @code{javascript-callback} xwidget events
+An event with @var{kind} set to @code{javascript-callback} contains
+JavaScript callback data.  These events are used internally by
+@code{xwidget-webkit-execute-script}.
+
+@cindex @code{xwidget-display-event} event
+@item (xwidget-display-event @var{xwidget})
+This event is sent whenever an xwidget requests that another xwidget
+be displayed.  @var{xwidget} is the xwidget that should be displayed.
+
+@var{xwidget}'s buffer will be set to a temporary buffer.  When
+displaying the widget, care should be taken to replace the buffer with
+the buffer in which the xwidget will be displayed, using
+@code{set-xwidget-buffer}  (@pxref{Xwidgets}).
+@end table
+
 @node Misc Events
 @subsection Miscellaneous System Events
 
diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi
index 22528a1b0f..60bca15eb2 100644
--- a/doc/lispref/display.texi
+++ b/doc/lispref/display.texi
@@ -6784,7 +6784,10 @@ Xwidgets
 in a @code{display} text or overlay property (@pxref{Display
 Property}).
 
-@defun make-xwidget type title width height arguments &optional buffer
+  Embedded widgets can send events notifying Lisp code about changes
+occurring within them.  (@pxref{Xwidget Events}).
+
+@defun make-xwidget type title width height arguments &optional buffer related
 This creates and returns an xwidget object.  If
 @var{buffer} is omitted or @code{nil}, it defaults to the current
 buffer.  If @var{buffer} names a buffer that doesn't exist, it will be
@@ -6797,7 +6800,10 @@ Xwidgets
 @end table
 
 The @var{width} and @var{height} arguments specify the widget size in
-pixels, and @var{title}, a string, specifies its title.
+pixels, and @var{title}, a string, specifies its title.  @var{related}
+is used internally by the WebKit widget, and specifies another WebKit
+widget that the newly created widget should share settings and
+subprocesses with.
 @end defun
 
 @defun xwidgetp object
@@ -6818,6 +6824,10 @@ Xwidgets
 This function returns the buffer of @var{xwidget}.
 @end defun
 
+@defun set-xwidget-buffer xwidget buffer
+This function sets the buffer of @var{xwidget} to @var{buffer}.
+@end defun
+
 @defun get-buffer-xwidgets buffer
 This function returns a list of xwidget objects associated with the
 @var{buffer}, which can be specified as a buffer object or a name of
@@ -6878,6 +6888,61 @@ Xwidgets
 query-on-exit flag, either @code{t} or @code{nil}.
 @end defun
 
+@defun xwidget-perform-lispy-event xwidget event frame
+Send an input event @var{event} to @var{xwidget}.  The precise action
+performed is platform-specific.  See @ref{Input Events}.
+
+You can optionally pass the frame the event was generated from via
+@var{frame}.  On X11, modifier keys in key events will not be
+considered if @var{frame} is @code{nil}, and the selected frame is not
+an X-Windows frame.
+
+On GTK, only keyboard and function key events are implemented.  Mouse,
+motion, and click events are dispatched to the xwidget without going
+through Lisp code, and as such shouldn't require this function to be
+sent.
+@end defun
+
+@defun xwidget-webkit-search query xwidget &optional case-insensitive backwards wrap-around
+Start an incremental search on the WebKit widget @var{xwidget} with
+the string @var{query} as a query.  @var{case-insensitive} denotes
+whether or not the search is case-insensitive, @var{backwards}
+determines if the search is performed backwards towards the start of
+the document, and @var{wrap-around} determines whether or not the
+search terminates at the end of the document.
+
+If the function is called while a search query is already present,
+then the query specified here will replace the existing query.
+
+To stop a search query, use @code{xwidget-webkit-finish-search}.
+@end defun
+
+@defun xwidget-webkit-next-result xwidget
+Display the next search result in @var{xwidget}.  This function will
+error unless a search query has already been started in @var{xwidget}
+through @code{xwidget-webkit-search}.
+
+If @code{wrap-around} was non-nil when @code{xwidget-webkit-search}
+was called, then the search will restart from the beginning of the
+document if the end is reached.
+@end defun
+
+@defun xwidget-webkit-previous-result xwidget
+Display the previous search result in @var{xwidget}.  This function
+will error unless a search query has already been started in
+@var{xwidget} through @code{xwidget-webkit-search}.
+
+If @code{wrap-around} was non-nil when @code{xwidget-webkit-search}
+was called, then the search will restart from the end of the
+document if the beginning is reached.
+@end defun
+
+@defun xwidget-webkit-finish-search xwidget
+Finish a search operation started with @code{xwidget-webkit-search} in
+@var{xwidget}.  If there is no query currently ongoing, then this
+function will error.
+@end defun
+
 @node Buttons
 @section Buttons
 @cindex buttons in buffers
diff --git a/etc/NEWS b/etc/NEWS
index a50229916f..b14f9a2549 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -495,6 +495,31 @@ the buffer will take you to that directory.
 This is a convenience function to extract the field data from
 'exif-parse-file' and 'exif-parse-buffer'.
 
+** Xwidgets
+
++++
+*** New minor mode `xwidget-webkit-edit-mode'.
+When this mode is enabled, self-inserting characters and other common
+web browser shotcut keys are redefined to send themselves to the
+WebKit widget.
+
++++
+*** New minor mode `xwidget-webkit-isearch-mode'.
+This mode acts similarly to incremental search, and allows to search
+the contents of a WebKit widget.  In xwidget-webkit mode, it is bound
+to `C-s' and `C-r'.
+
+---
+*** On X11, the WebKit inspector is now available inside xwidgets.
+To access the inspector, right click on the widget and select "Inspect
+Element".
+
+---
+*** "Open in New Window" in a WebKit widget's context menu now works.
+The newly created buffer will be displayed via display-buffer, which
+can be customized through the usual mechanism of display-buffer-alist
+and friends.
+
 \f
 * New Modes and Packages in Emacs 29.1
 
@@ -719,6 +744,39 @@ an exact match, then the lowercased '[menu-bar foo\ bar]' and finally
 '[menu-bar foo-bar]'.  This further improves backwards-compatibility
 when converting menus to use 'easy-menu-define'.
 
++++
+** The function `make-xwidget' now accepts an optional RELATED argument.
+This argument is used as another widget for the newly created WebKit
+widget to share settings and subprocesses with.  It must be another
+WebKit widget.
+
++++
+** New function `xwidget-perform-lispy-event'.
+This function allows you to send events to xwidgets.  Usually, some
+equivalent of the event will be sent, but there is no guarantee of
+what the widget will actually receive.
+
+On GTK+, only key and function key events are implemented.
+
++++
+** New functions for performing searches on WebKit xwidgets.
+Some new functions, such as `xwidget-webkit-search', have been added
+for performing searches on WebKit xwidgets.
+
++++
+** `load-changed' xwidget events are now more detailed.
+In particular, they can now have different arguments based on the
+state of the WebKit widget.  `load-finished' is sent when a load has
+completed, `load-started' when a load first starts, `load-redirected'
+after a redirect, and `load-committed' when the WebKit widget first
+commits to the load.
+
++++
+** New event type `xwidget-display-event'.
+These events are sent whenever an xwidget requests that Emacs display
+another.  The only argument to this event is the xwidget that should
+be displayed.
+
 \f
 * Changes in Emacs 29.1 on Non-Free Operating Systems
 
diff --git a/etc/images/README b/etc/images/README
index 9bbe796cc9..561cfff765 100644
--- a/etc/images/README
+++ b/etc/images/README
@@ -68,6 +68,7 @@ Emacs images and their source in the GNOME icons stock/ directory:
   bookmark_add.xpm          actions/bookmark_add
   cancel.xpm                slightly modified generic/stock_stop
   connect.xpm               net/stock_connect
+  connect-to-url.xpm        net/stock_connect-to-url
   contact.xpm               net/stock_contact
   data-save.xpm             data/stock_data-save
   delete.xpm                generic/stock_delete
diff --git a/etc/images/connect-to-url.pbm b/etc/images/connect-to-url.pbm
new file mode 100644
index 0000000000..f142349f4a
Binary files /dev/null and b/etc/images/connect-to-url.pbm differ
diff --git a/etc/images/connect-to-url.xpm b/etc/images/connect-to-url.xpm
new file mode 100644
index 0000000000..38fefeaf61
--- /dev/null
+++ b/etc/images/connect-to-url.xpm
@@ -0,0 +1,281 @@
+/* XPM */
+static char *connect_to_url[] = {
+/* columns rows colors chars-per-pixel */
+"24 24 251 2 ",
+"   c black",
+".  c #010101",
+"X  c #000103",
+"o  c #010204",
+"O  c #010305",
+"+  c #020407",
+"@  c #020609",
+"#  c #03070C",
+"$  c #04080D",
+"%  c #0F0F0D",
+"&  c #030A10",
+"*  c #050B10",
+"=  c #060C11",
+"-  c #070D13",
+";  c #070D14",
+":  c #060C15",
+">  c #070E14",
+",  c #0B1824",
+"<  c #0A1B2B",
+"1  c #0A1C2E",
+"2  c #141A20",
+"3  c #161E25",
+"4  c #181E23",
+"5  c #0D2032",
+"6  c #142534",
+"7  c #1F2830",
+"8  c #1D2933",
+"9  c #102438",
+"0  c #272622",
+"q  c #21292F",
+"w  c #272F36",
+"e  c #282F33",
+"r  c #222F3A",
+"t  c #2E3337",
+"y  c #2D373E",
+"u  c #32383C",
+"i  c #33383C",
+"p  c #343A3E",
+"a  c #43423C",
+"s  c #112941",
+"d  c #102A44",
+"f  c #132D47",
+"g  c #192F46",
+"h  c #17314B",
+"j  c #15314F",
+"k  c #163351",
+"l  c #163554",
+"z  c #173554",
+"x  c #1F3A53",
+"c  c #1D3955",
+"v  c #1A3958",
+"b  c #1C3B5B",
+"n  c #1F3C58",
+"m  c #1D3C5C",
+"M  c #1E3E5D",
+"N  c #1F3F5F",
+"B  c #303B44",
+"V  c #313C44",
+"C  c #313D47",
+"Z  c #213C56",
+"A  c #233E57",
+"S  c #1F405F",
+"D  c #374148",
+"F  c #2D4050",
+"G  c #25405B",
+"H  c #25425E",
+"J  c #214262",
+"K  c #244565",
+"L  c #264665",
+"P  c #254666",
+"I  c #2A4967",
+"U  c #284969",
+"Y  c #2A4C6C",
+"T  c #2C4F6F",
+"R  c #33526E",
+"E  c #385269",
+"W  c #2D5070",
+"Q  c #2E5172",
+"!  c #335473",
+"~  c #3F5B75",
+"^  c #3D5F7D",
+"/  c #41494F",
+"(  c #646056",
+")  c #6C685E",
+"_  c #505F6C",
+"`  c #48657C",
+"'  c #556A7A",
+"]  c #5B6C78",
+"[  c #5F6F7B",
+"{  c #5D6F7D",
+"}  c #706C62",
+"|  c #726D63",
+" . c #78756B",
+".. c #7D786E",
+"X. c #60727F",
+"o. c #807D74",
+"O. c #8A857B",
+"+. c #8B877E",
+"@. c #4E6A83",
+"#. c #4A6A86",
+"$. c #4A7090",
+"%. c #587790",
+"&. c #5F7E95",
+"*. c #587B98",
+"=. c #6F7980",
+"-. c #697F8F",
+";. c #66839B",
+":. c #6A879F",
+">. c #708391",
+",. c #728A9A",
+"<. c #748898",
+"1. c #758A99",
+"2. c #7B8F9F",
+"3. c #708DA4",
+"4. c #7990A1",
+"5. c #7292AB",
+"6. c #7691A8",
+"7. c #7693AB",
+"8. c #7B98AE",
+"9. c #7E98AD",
+"0. c #7E9DB3",
+"q. c #7F9EB4",
+"w. c #8C8981",
+"e. c #989389",
+"r. c #A6A29B",
+"t. c #8093A1",
+"y. c #8598A3",
+"u. c #8498A7",
+"i. c #809AAD",
+"p. c #8F9FAA",
+"a. c #899FAE",
+"s. c #819FB5",
+"d. c #86A2B8",
+"f. c #87A5BB",
+"g. c #88A3B8",
+"h. c #89A5BA",
+"j. c #8FABBF",
+"k. c #97A7B1",
+"l. c #90AABE",
+"z. c #91ABBF",
+"x. c #98ACB9",
+"c. c #AAA7A0",
+"v. c #B1ADA4",
+"b. c #B3B1AA",
+"n. c #B7B3AA",
+"m. c #A3B1BC",
+"M. c #A5B1BC",
+"N. c #A9B6BF",
+"B. c #BEBBB5",
+"V. c #C4C2BD",
+"C. c #94AEC1",
+"Z. c #96AEC1",
+"A. c #94AFC2",
+"S. c #95AFC2",
+"D. c #96B0C3",
+"F. c #98B0C3",
+"G. c #9FB5C3",
+"H. c #99B3C6",
+"J. c #98B3C7",
+"K. c #9AB3C6",
+"L. c #9BB4C7",
+"P. c #9FB8CA",
+"I. c #9FB8CB",
+"U. c #A2B8C9",
+"Y. c #A3B9C9",
+"T. c #A0B9CB",
+"R. c #A3BACB",
+"E. c #A0B9CC",
+"W. c #A2BACC",
+"Q. c #A4BDCE",
+"!. c #A6BECF",
+"~. c #B8BEC2",
+"^. c #B8C3CA",
+"/. c #BCC5CB",
+"(. c #BDC8CE",
+"). c #A8C0D1",
+"_. c #AAC0D0",
+"`. c #ABC1D1",
+"'. c #ACC2D3",
+"]. c #AAC5D7",
+"[. c #B4C8D6",
+"{. c #BDCBD5",
+"}. c #B4C9D8",
+"|. c #B6CAD8",
+" X c #B8CBD9",
+".X c #BBCDDB",
+"XX c #B7D0E0",
+"oX c #BDD3E2",
+"OX c #BCD5E5",
+"+X c #CECAC3",
+"@X c #C5D2C8",
+"#X c #C0D2DE",
+"$X c #C4D3DF",
+"%X c #CCD7DE",
+"&X c #D2D8DC",
+"*X c #E1DFDB",
+"=X c #E2E1DD",
+"-X c #C2D3E0",
+";X c #C2D4E1",
+":X c #C5D5E1",
+">X c #C6D6E1",
+",X c #C4D6E2",
+"<X c #C5D6E3",
+"1X c #C6D7E3",
+"2X c #C3D7E4",
+"3X c #C1D7E6",
+"4X c #C7D8E3",
+"5X c #C5D8E5",
+"6X c #C7D9E5",
+"7X c #CBD9E4",
+"8X c #CBDAE5",
+"9X c #CDDAE4",
+"0X c #CCDBE5",
+"qX c #CFDBE5",
+"wX c #CBDCE7",
+"eX c #C0D9E8",
+"rX c #C2DBEA",
+"tX c #C4DAE8",
+"yX c #D0DEE7",
+"uX c #D1DFE8",
+"iX c #D0DFE9",
+"pX c #D0E0EA",
+"aX c #D1E1EB",
+"sX c #D3E1EA",
+"dX c #D4E1E9",
+"fX c #D4E1EA",
+"gX c #D5E2EA",
+"hX c #D4E2EB",
+"jX c #D6E2EB",
+"kX c #D3E2EC",
+"lX c #D8E3EA",
+"zX c #DFE6EB",
+"xX c #D9E4EC",
+"cX c #D9E5ED",
+"vX c #DAE5ED",
+"bX c #DAE6ED",
+"nX c #DCE7EE",
+"mX c #DBE8EF",
+"MX c #DDE8EF",
+"NX c #DFE8EF",
+"BX c #EAE8E3",
+"VX c #EBEAE6",
+"CX c #ECEBE8",
+"ZX c #E9EEEA",
+"AX c #F0EFEC",
+"SX c #F2F0ED",
+"DX c #E1ECF3",
+"FX c #E4EDF3",
+"GX c #E8EFF4",
+"HX c #F0F3F1",
+"JX c None",
+/* pixels */
+"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJXu D p t i V w JXJXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXC X./.&XDXGX%X{.m._ r JXJXJXJXJXJXJX",
+"JXJXJXJXJXi /.DXnXnXFXuX7X$X$XjXM.w JXJXJXJXJXJX",
+"JXJXJXJX/ ^.qXbX1XkX5X5X-X;XsXqXjXN.B JXJXJXJXJX",
+"JXJXJXe (.bXMXDXaXtXtX3XoXbXjXsXyX7Xx.q JXJXJXJX",
+"JXJX7 k.jXbXbX5X3XeXrXOXXX1XsXyXwX$X|.4.3 JXJXJX",
+"JXJXX.:XuXjX'.]._.y.    G.sXW.|..X$X[.H.' JXJXJX",
+"JXJXu.$XqXT.H.>.    e.o.  sXwX}.R.R.`.H.1.- JXJX",
+"JX4 a.9.C.h.] a n.V.BXo.        p.!.T.l.4.- JXJX",
+"JX2 F.d.5.7.  =XAXc.BXo.  @X@XZX  !.C.F.@.> JXJX",
+"            o.=XAXc.BXo.        t.U.z.3.Y $ JXJX",
+"BXBXBXBXVXBXBXAXVXO.CXo.  P.C.!.I.J.C.;.L * JXJX",
+"o.o.o.o.o. . .B.b...*X .  $.*.T.J.A.h.Y c @ JXJX",
+"             .w.r.| +X .        1.C.3.L h   JXJX",
+"JXJX6 Q ^ 1.% w.r.| +X .  @X@XHX  h.:.M ,   JXJX",
+"JXJXO x T #.] 0 +.} v.)         -.s.H 9 O JXJXJX",
+"JXJXJX+ n ! i.X.% % e.(   Q Y %.0.&.f O   JXJXJX",
+"JXJXJXJX& A s.8.E A % % A K J R ` g @   JXJXJXJX",
+"JXJXJXJXJX@ C ~ m M J N M b v l < O   JXJXJXJXJX",
+"JXJXJXJXJXJX  : 5 d k z k d 1 &     JXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJX                JXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX"
+};
diff --git a/lisp/xwidget.el b/lisp/xwidget.el
index 8c593abea8..4046140895 100644
--- a/lisp/xwidget.el
+++ b/lisp/xwidget.el
@@ -35,8 +35,9 @@
 (require 'bookmark)
 
 (declare-function make-xwidget "xwidget.c"
-                  (type title width height arguments &optional buffer))
+                  (type title width height arguments &optional buffer related))
 (declare-function xwidget-buffer "xwidget.c" (xwidget))
+(declare-function set-xwidget-buffer "xwidget.c" (xwidget buffer))
 (declare-function xwidget-size-request "xwidget.c" (xwidget))
 (declare-function xwidget-resize "xwidget.c" (xwidget new-width new-height))
 (declare-function xwidget-webkit-execute-script "xwidget.c"
@@ -58,14 +59,14 @@ xwidget
   "Displaying native widgets in Emacs buffers."
   :group 'widgets)
 
-(defun xwidget-insert (pos type title width height &optional args)
+(defun xwidget-insert (pos type title width height &optional args related)
   "Insert an xwidget at position POS.
-Supply the xwidget's TYPE, TITLE, WIDTH, and HEIGHT.
+Supply the xwidget's TYPE, TITLE, WIDTH, HEIGHT, and RELATED.
 See `make-xwidget' for the possible TYPE values.
 The usage of optional argument ARGS depends on the xwidget.
 This returns the result of `make-xwidget'."
   (goto-char pos)
-  (let ((id (make-xwidget type title width height args)))
+  (let ((id (make-xwidget type title width height args nil related)))
     (put-text-property (point) (+ 1 (point))
                        'display (list 'xwidget ':xwidget id))
     id))
@@ -88,6 +89,9 @@ xwidget-at
 (require 'seq)
 (require 'url-handlers)
 
+(defvar-local xwidget-webkit--title ""
+  "The title of the WebKit widget, used for the header line.")
+
 ;;;###autoload
 (defun xwidget-webkit-browse-url (url &optional new-session)
   "Ask xwidget-webkit to browse URL.
@@ -124,6 +128,14 @@ xwidget-webkit-clone-and-split-right
     (with-selected-window (split-window-right)
       (xwidget-webkit-new-session url))))
 
+(declare-function xwidget-perform-lispy-event "xwidget.c")
+
+(defun xwidget-webkit-pass-command-event ()
+  "Pass `last-command-event' to the current buffer's WebKit widget."
+  (interactive)
+  (xwidget-perform-lispy-event (xwidget-webkit-current-session)
+                               last-command-event))
+
 ;;todo.
 ;; - check that the webkit support is compiled in
 (defvar xwidget-webkit-mode-map
@@ -138,6 +150,9 @@ xwidget-webkit-mode-map
     (define-key map "w" 'xwidget-webkit-current-url)
     (define-key map "+" 'xwidget-webkit-zoom-in)
     (define-key map "-" 'xwidget-webkit-zoom-out)
+    (define-key map "e" 'xwidget-webkit-edit-mode)
+    (define-key map "\C-r" 'xwidget-webkit-isearch-mode)
+    (define-key map "\C-s" 'xwidget-webkit-isearch-mode)
 
     ;;similar to image mode bindings
     (define-key map (kbd "SPC")                 'xwidget-webkit-scroll-up)
@@ -164,6 +179,63 @@ xwidget-webkit-mode-map
     map)
   "Keymap for `xwidget-webkit-mode'.")
 
+(easy-menu-define nil xwidget-webkit-mode-map "Xwidget WebKit menu."
+  (list "Xwidget WebKit"
+        ["Browse URL" xwidget-webkit-browse-url
+         :active t
+         :help "Prompt for a URL, then instruct WebKit to browse it"]
+        ["Back" xwidget-webkit-back t]
+        ["Forward" xwidget-webkit-forward t]
+        ["Reload" xwidget-webkit-reload t]
+        ["Insert String" xwidget-webkit-insert-string
+         :active t
+         :help "Insert a string into the currently active field"]
+        ["Zoom In" xwidget-webkit-zoom-in t]
+        ["Zoom Out" xwidget-webkit-zoom-out t]
+        ["Edit Mode" xwidget-webkit-edit-mode
+         :active t
+         :style toggle
+         :selected xwidget-webkit-edit-mode
+         :help "Send self inserting characters to the WebKit widget"]
+        ["Save Selection" xwidget-webkit-copy-selection-as-kill
+         :active t
+         :help "Save the browser's selection in the kill ring"]
+        ["Incremental Search" xwidget-webkit-isearch-mode
+         :active (not xwidget-webkit-isearch-mode)
+         :help "Perform incremental search inside the WebKit widget"]))
+
+(defvar xwidget-webkit-tool-bar-map
+  (let ((map (make-sparse-keymap)))
+    (prog1 map
+      (tool-bar-local-item-from-menu 'xwidget-webkit-back
+                                     "left-arrow"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-forward
+                                     "right-arrow"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-reload
+                                     "refresh"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-zoom-in
+                                     "zoom-in"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-zoom-out
+                                     "zoom-out"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-browse-url
+                                     "connect-to-url"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-isearch-mode
+                                     "search"
+                                     map
+                                     xwidget-webkit-mode-map))))
+
 (defun xwidget-webkit-zoom-in ()
   "Increase webkit view zoom factor."
   (interactive nil xwidget-webkit-mode)
@@ -276,6 +348,8 @@ xwidget-webkit-callback
     (with-current-buffer (xwidget-buffer xwidget)
       (cond ((eq xwidget-event-type 'load-changed)
              (let ((title (xwidget-webkit-title xwidget)))
+               (setq xwidget-webkit--title title)
+               (force-mode-line-update)
                (xwidget-log "webkit finished loading: %s" title)
                ;; Do not adjust webkit size to window here, the selected window
                ;; can be the mini-buffer window unwantedly.
@@ -309,8 +383,10 @@ bookmark-make-record-function
 (define-derived-mode xwidget-webkit-mode special-mode "xwidget-webkit"
   "Xwidget webkit view mode."
   (setq buffer-read-only t)
+  (setq-local tool-bar-map xwidget-webkit-tool-bar-map)
   (setq-local bookmark-make-record-function
               #'xwidget-webkit-bookmark-make-record)
+  (setq-local header-line-format 'xwidget-webkit--title)
   ;; Keep track of [vh]scroll when switching buffers
   (image-mode-setup-winprops))
 
@@ -609,6 +685,7 @@ xwidget-webkit-new-session
   (let*
       ((bufname (generate-new-buffer-name "*xwidget-webkit*"))
        (callback (or callback #'xwidget-webkit-callback))
+       (current-session (xwidget-webkit-current-session))
        xw)
     (setq xwidget-webkit-last-session-buffer (switch-to-buffer
                                               (get-buffer-create bufname)))
@@ -621,11 +698,35 @@ xwidget-webkit-new-session
       (setq xw (xwidget-insert
                 start 'webkit bufname
                 (xwidget-window-inside-pixel-width (selected-window))
-                (xwidget-window-inside-pixel-height (selected-window)))))
+                (xwidget-window-inside-pixel-height (selected-window))
+                nil current-session)))
     (xwidget-put xw 'callback callback)
     (xwidget-webkit-mode)
     (xwidget-webkit-goto-uri (xwidget-webkit-last-session) url)))
 
+(defun xwidget-webkit-import-widget (xwidget)
+  "Create a new webkit session buffer from XWIDGET, an existing xwidget.
+Return the buffer."
+  (let* ((bufname (generate-new-buffer-name "*xwidget-webkit*"))
+         (callback #'xwidget-webkit-callback)
+         (buffer (get-buffer-create bufname)))
+    (with-current-buffer buffer
+      (save-excursion
+        (erase-buffer)
+        (insert ".")
+        (put-text-property (point-min) (point-max)
+                           'display (list 'xwidget :xwidget xwidget)))
+      (xwidget-put xwidget 'callback callback)
+      (set-xwidget-buffer xwidget buffer)
+      (xwidget-webkit-mode))
+    buffer))
+
+(defun xwidget-webkit-display-event (event)
+  "Import the xwidget inside EVENT and display it."
+  (interactive "e")
+  (display-buffer (xwidget-webkit-import-widget (nth 1 event))))
+
+(global-set-key [xwidget-display-event] 'xwidget-webkit-display-event)
 
 (defun xwidget-webkit-goto-url (url)
   "Goto URL with xwidget webkit."
@@ -684,6 +785,165 @@ xwidget-put
   (set-xwidget-plist xwidget
                      (plist-put (xwidget-plist xwidget) propname value)))
 
+(defvar xwidget-webkit-edit-mode-map (make-keymap))
+
+(define-key xwidget-webkit-edit-mode-map [backspace] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [tab] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [return] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-return] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-return] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-return] 'xwidget-webkit-pass-command-event)
+
+(define-minor-mode xwidget-webkit-edit-mode
+  "Minor mode for editing the content of WebKit buffers.
+
+This defines most self-inserting characters and some common
+keyboard shortcuts to `xwidget-webkit-pass-command-event', which
+will pass the key events corresponding to these characters to the
+WebKit widget."
+  :keymap xwidget-webkit-edit-mode-map)
+
+(substitute-key-definition 'self-insert-command
+                           'xwidget-webkit-pass-command-event
+                           xwidget-webkit-edit-mode-map
+                           global-map)
+
+(declare-function xwidget-webkit-search "xwidget.c")
+(declare-function xwidget-webkit-next-result "xwidget.c")
+(declare-function xwidget-webkit-previous-result "xwidget.c")
+(declare-function xwidget-webkit-finish-search "xwidget.c")
+
+(defvar-local xwidget-webkit-isearch--string ""
+  "The current search query.")
+(defvar-local xwidget-webkit-isearch--is-reverse nil
+  "Whether or not the current isearch should be reverse.")
+
+(defun xwidget-webkit-isearch--update (&optional only-message)
+  "Update the current buffer's WebKit widget's search query.
+If ONLY-MESSAGE is non-nil, the query will not be sent to the
+WebKit widget.  The query will be set to the contents of
+`xwidget-webkit-isearch--string'."
+  (unless only-message
+    (xwidget-webkit-search xwidget-webkit-isearch--string
+                           (xwidget-webkit-current-session)
+                           t xwidget-webkit-isearch--is-reverse t))
+  (message (concat (propertize "Search contents: " 'face 'minibuffer-prompt)
+                   xwidget-webkit-isearch--string)))
+
+(defun xwidget-webkit-isearch-erasing-char (count)
+  "Erase the last COUNT characters of the current query."
+  (interactive (list (prefix-numeric-value current-prefix-arg)))
+  (when (> (length xwidget-webkit-isearch--string) 0)
+    (setq xwidget-webkit-isearch--string
+          (substring xwidget-webkit-isearch--string 0
+                     (- (length xwidget-webkit-isearch--string) count))))
+  (xwidget-webkit-isearch--update))
+
+(defun xwidget-webkit-isearch-printing-char (char &optional count)
+  "Add ordinary character CHAR to the search string and search.
+With argument, add COUNT copies of CHAR."
+  (interactive (list last-command-event
+                     (prefix-numeric-value current-prefix-arg)))
+  (setq xwidget-webkit-isearch--string (concat xwidget-webkit-isearch--string
+                                               (make-string (or count 1) char)))
+  (xwidget-webkit-isearch--update))
+
+(defun xwidget-webkit-isearch-forward (count)
+  "Move to the next search result COUNT times."
+  (interactive (list (prefix-numeric-value current-prefix-arg)))
+  (let ((was-reverse xwidget-webkit-isearch--is-reverse))
+    (setq xwidget-webkit-isearch--is-reverse nil)
+    (when was-reverse
+      (xwidget-webkit-isearch--update)))
+  (let ((i 0))
+    (while (< i count)
+      (xwidget-webkit-next-result (xwidget-webkit-current-session))
+      (cl-incf i)))
+  (xwidget-webkit-isearch--update t))
+
+(defun xwidget-webkit-isearch-backward (count)
+  "Move to the previous search result COUNT times."
+  (interactive (list (prefix-numeric-value current-prefix-arg)))
+  (let ((was-reverse xwidget-webkit-isearch--is-reverse))
+    (setq xwidget-webkit-isearch--is-reverse t)
+    (unless was-reverse
+      (xwidget-webkit-isearch--update)))
+  (let ((i 0))
+    (while (< i count)
+      (xwidget-webkit-next-result (xwidget-webkit-current-session))
+      (cl-incf i)))
+  (xwidget-webkit-isearch--update t))
+
+(defun xwidget-webkit-isearch-exit ()
+  "Exit incremental search of a WebKit buffer."
+  (interactive)
+  (xwidget-webkit-isearch-mode 0))
+
+(defvar xwidget-webkit-isearch-mode-map (make-keymap)
+  "The keymap used inside xwidget-webkit-isearch-mode.")
+
+(set-char-table-range (nth 1 xwidget-webkit-isearch-mode-map)
+                      (cons 0 (max-char))
+                      'xwidget-webkit-isearch-exit)
+
+(substitute-key-definition 'self-insert-command
+                           'xwidget-webkit-isearch-printing-char
+                           xwidget-webkit-isearch-mode-map
+                           global-map)
+
+(define-key xwidget-webkit-isearch-mode-map (kbd "DEL")
+  'xwidget-webkit-isearch-erasing-char)
+(define-key xwidget-webkit-isearch-mode-map [return] 'xwidget-webkit-isearch-exit)
+(define-key xwidget-webkit-isearch-mode-map "\r" 'xwidget-webkit-isearch-exit)
+(define-key xwidget-webkit-isearch-mode-map "\C-g" 'xwidget-webkit-isearch-exit)
+(define-key xwidget-webkit-isearch-mode-map "\C-r" 'xwidget-webkit-isearch-backward)
+(define-key xwidget-webkit-isearch-mode-map "\C-s" 'xwidget-webkit-isearch-forward)
+(define-key xwidget-webkit-isearch-mode-map "\t" 'xwidget-webkit-isearch-printing-char)
+
+(let ((meta-map (make-keymap)))
+  (set-char-table-range (nth 1 meta-map)
+                        (cons 0 (max-char))
+                        'xwidget-webkit-isearch-exit)
+  (define-key xwidget-webkit-isearch-mode-map (char-to-string meta-prefix-char) meta-map))
+
+(define-minor-mode xwidget-webkit-isearch-mode
+  "Minor mode for performing incremental search inside WebKit buffers.
+
+An attempt was made for this to resemble regular incremental
+search, but it suffers from several limitations, such as not
+supporting recursive edits.
+
+If this mode is enabled with `C-r', then the search will default
+to being performed in reverse direction.
+
+To navigate around the search results, type
+\\[xwidget-webkit-isearch-forward] to move forward, and
+\\[xwidget-webkit-isearch-backward] to move backward.
+
+Press \\[xwidget-webkit-isearch-exit] to exit incremental search."
+  :keymap xwidget-webkit-isearch-mode-map
+  (if xwidget-webkit-isearch-mode
+      (progn
+        (setq xwidget-webkit-isearch--string "")
+        (setq xwidget-webkit-isearch--is-reverse (eq last-command-event ?\C-r))
+        (xwidget-webkit-isearch--update))
+    (xwidget-webkit-finish-search (xwidget-webkit-current-session))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
diff --git a/src/dispextern.h b/src/dispextern.h
index 5b28fe7666..f17f095e0d 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -536,8 +536,8 @@ #define FACE_ID_BITS	20
     int img_id;
 
 #ifdef HAVE_XWIDGETS
-    /* Xwidget reference (type == XWIDGET_GLYPH).  */
-    struct xwidget *xwidget;
+    /* Xwidget ID.  */
+    uint32_t xwidget;
 #endif
 
     /* Sub-structure for type == STRETCH_GLYPH.  */
diff --git a/src/dispnew.c b/src/dispnew.c
index 4a73244c89..632eec2f03 100644
--- a/src/dispnew.c
+++ b/src/dispnew.c
@@ -4449,16 +4449,6 @@ scrolling_window (struct window *w, int tab_line_p)
 	break;
     }
 
-#ifdef HAVE_XWIDGETS
-  /* Currently this seems needed to detect xwidget movement reliably.
-     This is most probably because an xwidget glyph is represented in
-     struct glyph's 'union u' by a pointer to a struct, which takes 8
-     bytes in 64-bit builds, and thus the comparison of u.val values
-     done by GLYPH_EQUAL_P doesn't work reliably, since it assumes the
-     size of the union is 4 bytes.  FIXME.  */
-    return 0;
-#endif
-
   /* Can't scroll the display of w32 GUI frames when position of point
      is indicated by the system caret, because scrolling the display
      will then "copy" the pixels used by the caret.  */
diff --git a/src/keyboard.c b/src/keyboard.c
index aa6a4b9e97..c4a5671b10 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -3993,6 +3993,7 @@ kbd_buffer_get_event (KBOARD **kbp,
 #endif
 #ifdef HAVE_XWIDGETS
       case XWIDGET_EVENT:
+      case XWIDGET_DISPLAY_EVENT:
 #endif
       case SAVE_SESSION_EVENT:
       case NO_EVENT:
@@ -4897,7 +4898,7 @@ #define FUNCTION_KEY_OFFSET 0xff00
 
 /* You'll notice that this table is arranged to be conveniently
    indexed by X Windows keysym values.  */
-static const char *const lispy_function_keys[] =
+const char *const lispy_function_keys[] =
   {
     /* X Keysym value */
 
@@ -6139,6 +6140,11 @@ make_lispy_event (struct input_event *event)
       {
         return Fcons (Qxwidget_event, event->arg);
       }
+
+    case XWIDGET_DISPLAY_EVENT:
+      {
+	return list2 (Qxwidget_display_event, event->arg);
+      }
 #endif
 
 #ifdef USE_FILE_NOTIFY
@@ -11732,6 +11738,7 @@ syms_of_keyboard (void)
 
 #ifdef HAVE_XWIDGETS
   DEFSYM (Qxwidget_event, "xwidget-event");
+  DEFSYM (Qxwidget_display_event, "xwidget-display-event");
 #endif
 
 #ifdef USE_FILE_NOTIFY
diff --git a/src/keyboard.h b/src/keyboard.h
index 8bdffaa2bf..21c51ec386 100644
--- a/src/keyboard.h
+++ b/src/keyboard.h
@@ -491,7 +491,7 @@ kbd_buffer_store_event_hold (struct input_event *event,
 extern struct timespec timer_check (void);
 extern void mark_kboards (void);
 
-#ifdef HAVE_NTGUI
+#if defined HAVE_NTGUI || defined HAVE_X_WINDOWS
 extern const char *const lispy_function_keys[];
 #endif
 
diff --git a/src/print.c b/src/print.c
index c13294c8e6..eca389158f 100644
--- a/src/print.c
+++ b/src/print.c
@@ -1521,8 +1521,20 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
       printchar ('>', printcharfun);
       break;
 
-    case PVEC_XWIDGET: case PVEC_XWIDGET_VIEW:
-      print_c_string ("#<xwidget ", printcharfun);
+    case PVEC_XWIDGET:
+#ifdef HAVE_XWIDGETS
+      {
+	int len = sprintf (buf, "#<xwidget %u %p>",
+			   XXWIDGET (obj)->xwidget_id,
+			   XXWIDGET (obj)->widget_osr);
+	strout (buf, len, len, printcharfun);
+	break;
+      }
+#else
+      emacs_abort ();
+#endif
+    case PVEC_XWIDGET_VIEW:
+      print_c_string ("#<xwidget view", printcharfun);
       printchar ('>', printcharfun);
       break;
 
diff --git a/src/termhooks.h b/src/termhooks.h
index 1d3cdc8fe8..e7539bbce2 100644
--- a/src/termhooks.h
+++ b/src/termhooks.h
@@ -255,6 +255,8 @@ #define EMACS_TERMHOOKS_H
 #ifdef HAVE_XWIDGETS
   /* events generated by xwidgets*/
    , XWIDGET_EVENT
+   /* Event generated when WebKit asks us to display another widget.  */
+   , XWIDGET_DISPLAY_EVENT
 #endif
 
 #ifdef USE_FILE_NOTIFY
diff --git a/src/xdisp.c b/src/xdisp.c
index 86c4e704d5..d7ad548917 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -28429,7 +28429,7 @@ fill_xwidget_glyph_string (struct glyph_string *s)
     }
   s->width = s->first_glyph->pixel_width;
   s->ybase += s->first_glyph->voffset;
-  s->xwidget = s->first_glyph->u.xwidget;
+  s->xwidget = xwidget_from_id (s->first_glyph->u.xwidget);
 }
 #endif
 /* Fill glyph string S from a sequence of stretch glyphs.
@@ -29832,7 +29832,7 @@ produce_xwidget_glyph (struct it *it)
           glyph->padding_p = 0;
 	  glyph->glyph_not_available_p = 0;
 	  glyph->face_id = it->face_id;
-          glyph->u.xwidget = it->xwidget;
+          glyph->u.xwidget = it->xwidget->xwidget_id;
 	  glyph->font_type = FONT_TYPE_UNKNOWN;
 	  if (it->bidi_p)
 	    {
diff --git a/src/xterm.c b/src/xterm.c
index aa1a1a5eed..9b434bffcc 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -4390,6 +4390,86 @@ x_scroll_run (struct window *w, struct run *run)
   /* Cursor off.  Will be switched on again in gui_update_window_end.  */
   gui_clear_cursor (w);
 
+#ifdef HAVE_XWIDGETS
+  /* "Copy" xwidget windows in the area that will be scrolled.  */
+  Display *dpy = FRAME_X_DISPLAY (f);
+  Window window = FRAME_X_WINDOW (f);
+
+  Window root, parent, *children;
+  unsigned int nchildren;
+
+  if (XQueryTree (dpy, window, &root, &parent, &children, &nchildren))
+    {
+      /* Now find xwidget views situated between from_y and to_y, and
+	 attached to w.  */
+      for (unsigned int i = 0; i < nchildren; ++i)
+	{
+	  Window child = children[i];
+	  struct xwidget_view *view = xwidget_view_from_window (child);
+
+	  if (view)
+	    {
+	      int window_y = view->y + view->clip_top;
+	      int window_height = view->clip_bottom - view->clip_top;
+
+	      Emacs_Rectangle r1, r2, result;
+	      r1.x = w->pixel_left;
+	      r1.y = from_y;
+	      r1.width = w->pixel_width;
+	      r1.height = height;
+	      r2 = r1;
+	      r2.y = window_y;
+	      r2.height = window_height;
+
+	      /* The window is offscreen, just unmap it.  */
+	      if (window_height == 0)
+		{
+		  view->hidden = true;
+		  XUnmapWindow (dpy, child);
+		  continue;
+		}
+
+	      bool intersects_p =
+		gui_intersect_rectangles (&r1, &r2, &result);
+
+	      if (XWINDOW (view->w) == w && intersects_p)
+		{
+		  int y = view->y + (to_y - from_y);
+		  int text_area_x, text_area_y, text_area_width, text_area_height;
+		  int clip_top, clip_bottom;
+
+		  window_box (w, TEXT_AREA, &text_area_x, &text_area_y,
+			      &text_area_width, &text_area_height);
+
+		  clip_top = max (0, text_area_y - y);
+		  clip_bottom = max (clip_top,
+				     min (XXWIDGET (view->model)->height,
+					  text_area_y + text_area_height - y));
+
+		  view->y = y;
+		  view->clip_top = clip_top;
+		  view->clip_bottom = clip_bottom;
+
+		  /* This means the view has moved offscreen.  Unmap
+		     it and hide it here.  */
+		  if ((view->clip_top - view->clip_bottom) <= 0)
+		    {
+		      view->hidden = true;
+		      XUnmapWindow (dpy, child);
+		    }
+		  else
+		    XMoveResizeWindow (dpy, child, view->x + view->clip_left,
+				       view->y + view->clip_top,
+				       view->clip_right - view->clip_left,
+				       view->clip_top - view->clip_bottom);
+		  XFlush (dpy);
+		}
+            }
+	}
+      XFree (children);
+    }
+#endif
+
 #ifdef USE_CAIRO
   if (FRAME_CR_CONTEXT (f))
     {
@@ -4563,8 +4643,9 @@ x_focus_changed (int type, int state, struct x_display_info *dpyinfo, struct fra
     }
 }
 
-/* Return the Emacs frame-object corresponding to an X window.
-   It could be the frame's main window or an icon window.  */
+/* Return the Emacs frame-object corresponding to an X window.  It
+   could be the frame's main window, an icon window, or an xwidget
+   window.  */
 
 static struct frame *
 x_window_to_frame (struct x_display_info *dpyinfo, int wdesc)
@@ -4575,6 +4656,13 @@ x_window_to_frame (struct x_display_info *dpyinfo, int wdesc)
   if (wdesc == None)
     return NULL;
 
+#ifdef HAVE_XWIDGETS
+  struct xwidget_view *xvw = xwidget_view_from_window (wdesc);
+
+  if (xvw && xvw->frame)
+    return xvw->frame;
+#endif
+
   FOR_EACH_FRAME (tail, frame)
     {
       f = XFRAME (frame);
@@ -4997,7 +5085,7 @@ x_x_to_emacs_modifiers (struct x_display_info *dpyinfo, int state)
             | ((state & dpyinfo->hyper_mod_mask)	? mod_hyper	: 0));
 }
 
-static int
+int
 x_emacs_to_x_modifiers (struct x_display_info *dpyinfo, intmax_t state)
 {
   EMACS_INT mod_ctrl = ctrl_modifier;
@@ -8211,6 +8299,18 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 
     case Expose:
       f = x_window_to_frame (dpyinfo, event->xexpose.window);
+#ifdef HAVE_XWIDGETS
+      {
+	struct xwidget_view *xv =
+	  xwidget_view_from_window (event->xexpose.window);
+
+	if (xv)
+	  {
+	    xwidget_expose (xv);
+	    goto OTHER;
+	  }
+      }
+#endif
       if (f)
         {
           if (!FRAME_VISIBLE_P (f))
@@ -8791,6 +8891,31 @@ handle_one_xevent (struct x_display_info *dpyinfo,
       x_display_set_last_user_time (dpyinfo, event->xcrossing.time);
       x_detect_focus_change (dpyinfo, any, event, &inev.ie);
 
+#ifdef HAVE_XWIDGETS
+      {
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xcrossing.window);
+	Mouse_HLInfo *hlinfo;
+
+	if (xvw)
+	  {
+	    xwidget_motion_or_crossing (xvw, event);
+	    hlinfo = MOUSE_HL_INFO (xvw->frame);
+
+	    if (xvw->frame == hlinfo->mouse_face_mouse_frame)
+	      {
+		clear_mouse_face (hlinfo);
+		hlinfo->mouse_face_mouse_frame = 0;
+	      }
+
+	    if (any_help_event_p)
+	      {
+		do_help = -1;
+	      }
+	    goto OTHER;
+	  }
+      }
+#endif
+
       f = any;
 
       if (f && x_mouse_click_focus_ignore_position)
@@ -8834,6 +8959,17 @@ handle_one_xevent (struct x_display_info *dpyinfo,
       goto OTHER;
 
     case LeaveNotify:
+#ifdef HAVE_XWIDGETS
+      {
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xcrossing.window);
+
+	if (xvw)
+	  {
+	    xwidget_motion_or_crossing (xvw, event);
+	    goto OTHER;
+	  }
+      }
+#endif
       x_display_set_last_user_time (dpyinfo, event->xcrossing.time);
       x_detect_focus_change (dpyinfo, any, event, &inev.ie);
 
@@ -8883,6 +9019,12 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 #ifdef USE_GTK
         if (f && xg_event_is_for_scrollbar (f, event))
           f = 0;
+#endif
+#ifdef HAVE_XWIDGETS
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xmotion.window);
+
+	if (xvw)
+	  xwidget_motion_or_crossing (xvw, event);
 #endif
         if (f)
           {
@@ -9138,6 +9280,24 @@ handle_one_xevent (struct x_display_info *dpyinfo,
     case ButtonRelease:
     case ButtonPress:
       {
+#ifdef HAVE_XWIDGETS
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xmotion.window);
+
+	if (xvw)
+	  {
+	    xwidget_button (xvw, event->type == ButtonPress,
+			    event->xbutton.x, event->xbutton.y,
+			    event->xbutton.button, event->xbutton.state,
+			    event->xbutton.time);
+
+	    if (!EQ (selected_window, xvw->w))
+	      {
+		inev.ie.kind = SELECT_WINDOW_EVENT;
+		inev.ie.frame_or_window = xvw->w;
+	      }
+	    goto OTHER;
+	  }
+#endif
         /* If we decide we want to generate an event to be seen
            by the rest of Emacs, we put it here.  */
         Lisp_Object tab_bar_arg = Qnil;
@@ -12108,6 +12268,10 @@ x_free_frame_resources (struct frame *f)
 	xfree (f->shell_position);
 #else  /* !USE_X_TOOLKIT */
 
+#ifdef HAVE_XWIDGETS
+      kill_frame_xwidget_views (f);
+#endif
+
 #ifdef USE_GTK
       xg_free_frame_widgets (f);
 #endif /* USE_GTK */
diff --git a/src/xterm.h b/src/xterm.h
index de6ea50385..9d9534dd62 100644
--- a/src/xterm.h
+++ b/src/xterm.h
@@ -1108,6 +1108,7 @@ #define SELECTION_EVENT_TIME(eventp)	\
 extern int x_dispatch_event (XEvent *, Display *);
 #endif
 extern int x_x_to_emacs_modifiers (struct x_display_info *, int);
+extern int x_emacs_to_x_modifiers (struct x_display_info *, intmax_t);
 #ifdef USE_CAIRO
 extern void x_cr_destroy_frame_context (struct frame *);
 extern void x_cr_update_surface_desired_size (struct frame *, int, int);
diff --git a/src/xwidget.c b/src/xwidget.c
index e4b42e6e0c..90aac4e092 100644
--- a/src/xwidget.c
+++ b/src/xwidget.c
@@ -19,6 +19,7 @@ Copyright (C) 2011-2021 Free Software Foundation, Inc.
 
 #include <config.h>
 
+#include "buffer.h"
 #include "xwidget.h"
 
 #include "lisp.h"
@@ -35,10 +36,22 @@ Copyright (C) 2011-2021 Free Software Foundation, Inc.
 #ifdef USE_GTK
 #include <webkit2/webkit2.h>
 #include <JavaScriptCore/JavaScript.h>
+#include <cairo.h>
+#include <X11/Xlib.h>
 #elif defined NS_IMPL_COCOA
 #include "nsxwidget.h"
 #endif
 
+static Lisp_Object id_to_xwidget_map;
+static uint32_t xwidget_counter = 0;
+
+#ifdef USE_GTK
+static Lisp_Object x_window_to_xwv_map;
+static gboolean offscreen_damage_event (GtkWidget *, GdkEvent *, gpointer);
+static void synthesize_focus_in_event (GtkWidget *);
+static GdkDevice *find_suitable_keyboard (struct frame *);
+#endif
+
 static struct xwidget *
 allocate_xwidget (void)
 {
@@ -64,18 +77,32 @@ #define XSETXWIDGET_VIEW(a, b) XSETPSEUDOVECTOR (a, b, PVEC_XWIDGET_VIEW)
                                            GAsyncResult *,
                                            gpointer);
 static gboolean webkit_download_cb (WebKitWebContext *, WebKitDownload *, gpointer);
-
+static GtkWidget *webkit_create_cb (WebKitWebView *, WebKitNavigationAction *, gpointer);
 static gboolean
 webkit_decide_policy_cb (WebKitWebView *,
                          WebKitPolicyDecision *,
                          WebKitPolicyDecisionType,
                          gpointer);
+static GtkWidget *find_widget_at_pos (GtkWidget *, int, int, int *, int *);
+
+struct widget_search_data
+{
+  int x;
+  int y;
+  bool foundp;
+  bool first;
+  GtkWidget *data;
+};
+
+static void find_widget (GtkWidget *t, struct widget_search_data *);
+static void mouse_target_changed (WebKitWebView *, WebKitHitTestResult *, guint,
+				  gpointer);
 #endif
 
 
 DEFUN ("make-xwidget",
        Fmake_xwidget, Smake_xwidget,
-       5, 6, 0,
+       5, 7, 0,
        doc: /* Make an xwidget of TYPE.
 If BUFFER is nil, use the current buffer.
 If BUFFER is a string and no such buffer exists, create it.
@@ -83,10 +110,13 @@ DEFUN ("make-xwidget",
 
 - webkit
 
-Returns the newly constructed xwidget, or nil if construction fails.  */)
+RELATED is nil, or an xwidget.  When constructing a WebKit widget, it
+will share the same settings and internal subprocess as RELATED.
+Returns the newly constructed xwidget, or nil if construction
+fails.  */)
   (Lisp_Object type,
    Lisp_Object title, Lisp_Object width, Lisp_Object height,
-   Lisp_Object arguments, Lisp_Object buffer)
+   Lisp_Object arguments, Lisp_Object buffer, Lisp_Object related)
 {
 #ifdef USE_GTK
   if (!xg_gtk_initialized)
@@ -108,13 +138,19 @@ DEFUN ("make-xwidget",
   XSETXWIDGET (val, xw);
   Vxwidget_list = Fcons (val, Vxwidget_list);
   xw->plist = Qnil;
+  xw->xwidget_id = ++xwidget_counter;
+
+  Fputhash (make_fixnum (xw->xwidget_id), val, id_to_xwidget_map);
 
 #ifdef USE_GTK
   xw->widgetwindow_osr = NULL;
   xw->widget_osr = NULL;
+  xw->hit_result = 0;
+  xw->find_text = NULL;
   if (EQ (xw->type, Qwebkit))
     {
       block_input ();
+      WebKitSettings *settings;
       WebKitWebContext *webkit_context = webkit_web_context_get_default ();
 
 # if WEBKIT_CHECK_VERSION (2, 26, 0)
@@ -128,18 +164,34 @@ DEFUN ("make-xwidget",
 
       if (EQ (xw->type, Qwebkit))
         {
-          xw->widget_osr = webkit_web_view_new ();
-
-          /* webkitgtk uses GSubprocess which sets sigaction causing
-             Emacs to not catch SIGCHLD with its usual handle setup in
-             catch_child_signal().  This resets the SIGCHLD
-             sigaction.  */
-          struct sigaction old_action;
-          sigaction (SIGCHLD, NULL, &old_action);
-          webkit_web_view_load_uri(WEBKIT_WEB_VIEW (xw->widget_osr),
-                                   "about:blank");
-          sigaction (SIGCHLD, &old_action, NULL);
-        }
+	  WebKitWebView *related_view;
+
+	  if (NILP (related)
+	      || !XWIDGETP (related)
+	      || !EQ (XXWIDGET (related)->type, Qwebkit))
+	    {
+	      xw->widget_osr = webkit_web_view_new ();
+
+	      /* webkitgtk uses GSubprocess which sets sigaction causing
+		 Emacs to not catch SIGCHLD with its usual handle setup in
+		 catch_child_signal().  This resets the SIGCHLD
+		 sigaction.  */
+	      struct sigaction old_action;
+	      sigaction (SIGCHLD, NULL, &old_action);
+	      webkit_web_view_load_uri (WEBKIT_WEB_VIEW (xw->widget_osr),
+					"about:blank");
+	      sigaction (SIGCHLD, &old_action, NULL);
+	    }
+	  else
+	    {
+	      related_view = WEBKIT_WEB_VIEW (XXWIDGET (related)->widget_osr);
+	      xw->widget_osr = webkit_web_view_new_with_related_view (related_view);
+	    }
+
+	  /* Enable the developer extras */
+	  settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (xw->widget_osr));
+	  g_object_set (G_OBJECT (settings), "enable-developer-extras", TRUE, NULL);
+	}
 
       gtk_widget_set_size_request (GTK_WIDGET (xw->widget_osr), xw->width,
                                    xw->height);
@@ -157,6 +209,7 @@ DEFUN ("make-xwidget",
 
       gtk_widget_show (xw->widget_osr);
       gtk_widget_show (xw->widgetwindow_osr);
+      synthesize_focus_in_event (xw->widgetwindow_osr);
 
       /* Store some xwidget data in the gtk widgets for convenient
          retrieval in the event handlers.  */
@@ -179,8 +232,20 @@ DEFUN ("make-xwidget",
                             G_CALLBACK
                             (webkit_decide_policy_cb),
                             xw);
+
+	  g_signal_connect (G_OBJECT (xw->widget_osr),
+			    "mouse-target-changed",
+			    G_CALLBACK (mouse_target_changed),
+			    xw);
+	  g_signal_connect (G_OBJECT (xw->widget_osr),
+			    "create",
+			    G_CALLBACK (webkit_create_cb),
+			    xw);
         }
 
+      g_signal_connect (G_OBJECT (xw->widgetwindow_osr), "damage-event",
+			G_CALLBACK (offscreen_damage_event), xw);
+
       unblock_input ();
     }
 #elif defined NS_IMPL_COCOA
@@ -190,6 +255,158 @@ DEFUN ("make-xwidget",
   return val;
 }
 
+#ifdef USE_GTK
+static void
+set_widget_if_text_view (GtkWidget *widget, void *data)
+{
+  GtkWidget **pointer = data;
+
+  if (GTK_IS_TEXT_VIEW (widget))
+    {
+      *pointer = widget;
+    }
+}
+#endif
+
+DEFUN ("xwidget-perform-lispy-event",
+       Fxwidget_perform_lispy_event, Sxwidget_perform_lispy_event,
+       2, 3, 0, doc: /* Send a lispy event to XWIDGET.
+EVENT should be the event that will be sent.  FRAME should be the
+frame which generated the event, or nil.  On X11, modifier keys will
+not be processed if FRAME is nil and the selected frame is not an
+X-Windows frame.  */)
+  (Lisp_Object xwidget, Lisp_Object event, Lisp_Object frame)
+{
+  struct xwidget *xw;
+  struct frame *f = NULL;
+  int character = -1, keycode = -1;
+  int modifiers = 0;
+
+#ifdef USE_GTK
+  GdkEvent *xg_event;
+  GtkContainerClass *klass;
+  GtkWidget *widget;
+  GtkWidget *temp = NULL;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!NILP (frame))
+    f = decode_window_system_frame (frame);
+  else if (FRAME_X_P (SELECTED_FRAME ()))
+    f = SELECTED_FRAME ();
+
+#ifdef USE_GTK
+  widget = gtk_window_get_focus (GTK_WINDOW (xw->widgetwindow_osr));
+
+  if (!widget)
+    widget = xw->widget_osr;
+
+  if (RANGED_FIXNUMP (0, event, INT_MAX))
+    {
+      character = XFIXNUM (event);
+
+      if (character < 32)
+	modifiers |= ctrl_modifier;
+
+      modifiers |= character & meta_modifier;
+      modifiers |= character & hyper_modifier;
+      modifiers |= character & super_modifier;
+      modifiers |= character & shift_modifier;
+      modifiers |= character & ctrl_modifier;
+
+      character = character & ~(1 << 21);
+
+      if (character < 32)
+	character += '_';
+
+      if (f)
+	modifiers = x_emacs_to_x_modifiers (FRAME_DISPLAY_INFO (f), modifiers);
+      else
+	modifiers = 0;
+    }
+  else if (SYMBOLP (event))
+    {
+      Lisp_Object decoded = parse_modifiers (event);
+      Lisp_Object decoded_name = SYMBOL_NAME (XCAR (decoded));
+
+      int off = 0;
+      bool found = false;
+
+      while (off < 256)
+	{
+	  if (lispy_function_keys[off]
+	      && !strcmp (lispy_function_keys[off],
+			  SSDATA (decoded_name)))
+	    {
+	      found = true;
+	      break;
+	    }
+	  ++off;
+	}
+
+      if (f)
+	modifiers = x_emacs_to_x_modifiers (FRAME_DISPLAY_INFO (f),
+					    XFIXNUM (XCAR (XCDR (decoded))));
+      else
+	modifiers = 0;
+
+      if (found)
+	keycode = off + 0xff00;
+    }
+
+  if (character == -1 && keycode == -1)
+    return Qnil;
+
+  block_input ();
+  xg_event = gdk_event_new (GDK_KEY_PRESS);
+  xg_event->any.window = gtk_widget_get_window (xw->widget_osr);
+  g_object_ref (xg_event->any.window);
+
+  if (character > -1)
+    keycode = gdk_unicode_to_keyval (character);
+
+  xg_event->key.keyval = keycode;
+  xg_event->key.state = modifiers;
+
+  if (keycode > -1)
+    {
+      /* WebKitGTK internals abuse follows.  */
+      if (WEBKIT_IS_WEB_VIEW (widget))
+	{
+	  /* WebKitGTK relies on an internal GtkTextView object to
+	     "translate" keys such as backspace.  We must find that
+	     widget and activate its binding to this key if any.  */
+	  klass = GTK_CONTAINER_CLASS (G_OBJECT_GET_CLASS (widget));
+
+	  klass->forall (GTK_CONTAINER (xw->widget_osr), TRUE,
+			 set_widget_if_text_view, &temp);
+
+	  if (GTK_IS_WIDGET (temp))
+	    {
+	      if (!gtk_widget_get_realized (temp))
+		gtk_widget_realize (temp);
+
+	      gtk_bindings_activate (G_OBJECT (temp), keycode, modifiers);
+	    }
+	}
+    }
+
+  if (f)
+    gdk_event_set_device (xg_event,
+			  find_suitable_keyboard (SELECTED_FRAME ()));
+
+  gtk_main_do_event (xg_event);
+  xg_event->type = GDK_KEY_RELEASE;
+  gtk_main_do_event (xg_event);
+  gdk_event_free (xg_event);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
 DEFUN ("get-buffer-xwidgets", Fget_buffer_xwidgets, Sget_buffer_xwidgets,
        1, 1, 0,
        doc: /* Return a list of xwidgets associated with BUFFER.
@@ -221,16 +438,397 @@ xwidget_hidden (struct xwidget_view *xv)
   return xv->hidden;
 }
 
+struct xwidget *
+xwidget_from_id (uint32_t id)
+{
+  Lisp_Object key = make_fixnum (id);
+  Lisp_Object xwidget = Fgethash (key, id_to_xwidget_map, Qnil);
+
+  if (NILP (xwidget))
+    emacs_abort ();
+
+  return XXWIDGET (xwidget);
+}
+
 #ifdef USE_GTK
+
+static GdkDevice *
+find_suitable_pointer (struct frame *f)
+{
+  GdkSeat *seat = gdk_display_get_default_seat
+    (gtk_widget_get_display (FRAME_GTK_WIDGET (f)));
+
+  if (!seat)
+    return NULL;
+
+  return gdk_seat_get_pointer (seat);
+}
+
+static GdkDevice *
+find_suitable_keyboard (struct frame *f)
+{
+  GdkSeat *seat = gdk_display_get_default_seat
+    (gtk_widget_get_display (FRAME_GTK_WIDGET (f)));
+
+  if (!seat)
+    return NULL;
+
+  return gdk_seat_get_keyboard (seat);
+}
+
+static void
+find_widget_cb (GtkWidget *widget, void *user)
+{
+  find_widget (widget, user);
+}
+
+static void
+find_widget (GtkWidget *widget,
+	     struct widget_search_data *data)
+{
+  GtkAllocation new_allocation;
+  GdkWindow *window;
+  int x_offset = 0;
+  int y_offset = 0;
+
+  gtk_widget_get_allocation (widget, &new_allocation);
+
+  if (gtk_widget_get_has_window (widget))
+    {
+      new_allocation.x = 0;
+      new_allocation.y = 0;
+    }
+
+  if (gtk_widget_get_parent (widget) && !data->first)
+    {
+      window = gtk_widget_get_window (widget);
+      while (window != gtk_widget_get_window (gtk_widget_get_parent (widget)))
+        {
+          gint tx, ty, twidth, theight;
+
+	  if (!window)
+	    return;
+
+          twidth = gdk_window_get_width (window);
+          theight = gdk_window_get_height (window);
+
+          if (new_allocation.x < 0)
+            {
+              new_allocation.width += new_allocation.x;
+              new_allocation.x = 0;
+            }
+
+          if (new_allocation.y < 0)
+            {
+              new_allocation.height += new_allocation.y;
+              new_allocation.y = 0;
+            }
+
+          if (new_allocation.x + new_allocation.width > twidth)
+            new_allocation.width = twidth - new_allocation.x;
+          if (new_allocation.y + new_allocation.height > theight)
+            new_allocation.height = theight - new_allocation.y;
+
+          gdk_window_get_position (window, &tx, &ty);
+          new_allocation.x += tx;
+          x_offset += tx;
+          new_allocation.y += ty;
+          y_offset += ty;
+
+          window = gdk_window_get_parent (window);
+	}
+    }
+
+  if ((data->x >= new_allocation.x) && (data->y >= new_allocation.y) &&
+      (data->x < new_allocation.x + new_allocation.width) &&
+      (data->y < new_allocation.y + new_allocation.height))
+    {
+      /* First, check if the drag is in a valid drop site in
+       * one of our children
+       */
+      if (GTK_IS_CONTAINER (widget))
+        {
+          struct widget_search_data new_data = *data;
+
+          new_data.x -= x_offset;
+          new_data.y -= y_offset;
+          new_data.foundp = false;
+          new_data.first = false;
+
+          gtk_container_forall (GTK_CONTAINER (widget),
+                                find_widget_cb, &new_data);
+
+          data->foundp = new_data.foundp;
+          if (data->foundp)
+            data->data = new_data.data;
+        }
+
+      /* If not, and this widget is registered as a drop site, check to
+       * emit "drag_motion" to check if we are actually in
+       * a drop site.
+       */
+      if (!data->foundp)
+        {
+          data->foundp = true;
+          data->data = widget;
+        }
+    }
+}
+
+static GtkWidget *
+find_widget_at_pos (GtkWidget *w, int x, int y,
+		    int *new_x, int *new_y)
+{
+  struct widget_search_data data;
+
+  data.x = x;
+  data.y = y;
+  data.foundp = false;
+  data.first = true;
+
+  find_widget (w, &data);
+
+  if (data.foundp)
+    {
+      gtk_widget_translate_coordinates (w, data.data, x,
+					y, new_x, new_y);
+      return data.data;
+    }
+
+  *new_x = x;
+  *new_y = y;
+
+  return NULL;
+}
+
+static Emacs_Cursor
+cursor_for_hit (guint result, struct frame *frame)
+{
+  Emacs_Cursor cursor = FRAME_OUTPUT_DATA (frame)->nontext_cursor;
+
+  if ((result & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)
+      || (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION)
+      || (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT))
+    cursor = FRAME_X_OUTPUT (frame)->text_cursor;
+
+  if (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR)
+    cursor = FRAME_X_OUTPUT (frame)->vertical_drag_cursor;
+
+  if (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK)
+    cursor = FRAME_X_OUTPUT (frame)->hand_cursor;
+
+  return cursor;
+}
+
+static void
+define_cursors (struct xwidget *xw, WebKitHitTestResult *res)
+{
+  struct xwidget_view *xvw;
+
+  xw->hit_result = webkit_hit_test_result_get_context (res);
+
+  for (Lisp_Object tem = Vxwidget_view_list; CONSP (tem);
+       tem = XCDR (tem))
+    {
+      if (XWIDGET_VIEW_P (XCAR (tem)))
+	{
+	  xvw = XXWIDGET_VIEW (XCAR (tem));
+
+	  if (XXWIDGET (xvw->model) == xw)
+	    {
+	      xvw->cursor = cursor_for_hit (xw->hit_result, xvw->frame);
+	      if (xvw->wdesc != None)
+		XDefineCursor (xvw->dpy, xvw->wdesc, xvw->cursor);
+	    }
+	}
+    }
+}
+
+static void
+mouse_target_changed (WebKitWebView *webview,
+		      WebKitHitTestResult *hitresult,
+		      guint modifiers, gpointer xw)
+{
+  define_cursors (xw, hitresult);
+}
+
+
+static void
+xwidget_button_1 (struct xwidget_view *view,
+		  bool down_p, int x, int y, int button,
+		  int modifier_state, Time time)
+{
+  GdkEvent *xg_event = gdk_event_new (down_p ? GDK_BUTTON_PRESS : GDK_BUTTON_RELEASE);
+  struct xwidget *model = XXWIDGET (view->model);
+  GtkWidget *target;
+
+  /* X and Y should be relative to the origin of view->wdesc.  */
+  x += view->clip_left;
+  y += view->clip_top;
+
+  target = find_widget_at_pos (model->widgetwindow_osr, x, y, &x, &y);
+
+  if (!target)
+    target = model->widget_osr;
+
+  xg_event->any.window = gtk_widget_get_window (target);
+  g_object_ref (xg_event->any.window); /* The window will be unrefed
+					  later by gdk_event_free. */
+
+  xg_event->button.x = x;
+  xg_event->button.x_root = x;
+  xg_event->button.y = y;
+  xg_event->button.y_root = y;
+  xg_event->button.button = button;
+  xg_event->button.state = modifier_state;
+  xg_event->button.time = time;
+  xg_event->button.device = find_suitable_pointer (view->frame);
+
+  gtk_main_do_event (xg_event);
+  gdk_event_free (xg_event);
+}
+
+void
+xwidget_button (struct xwidget_view *view,
+		bool down_p, int x, int y, int button,
+		int modifier_state, Time time)
+{
+  if (button < 4 || button > 8)
+    xwidget_button_1 (view, down_p, x, y, button, modifier_state, time);
+  else
+    {
+      GdkEvent *xg_event = gdk_event_new (GDK_SCROLL);
+      struct xwidget *model = XXWIDGET (view->model);
+      GtkWidget *target;
+
+      x += view->clip_left;
+      y += view->clip_top;
+
+      target = find_widget_at_pos (model->widgetwindow_osr, x, y, &x, &y);
+
+      if (!target)
+	target = model->widget_osr;
+
+      xg_event->any.window = gtk_widget_get_window (target);
+      g_object_ref (xg_event->any.window); /* The window will be unrefed
+					      later by gdk_event_free. */
+      if (button == 4)
+	xg_event->scroll.direction = GDK_SCROLL_UP;
+      else if (button == 5)
+	xg_event->scroll.direction = GDK_SCROLL_DOWN;
+      else if (button == 6)
+	xg_event->scroll.direction = GDK_SCROLL_LEFT;
+      else
+	xg_event->scroll.direction = GDK_SCROLL_RIGHT;
+
+      xg_event->scroll.device = find_suitable_pointer (view->frame);
+
+      xg_event->scroll.x = x;
+      xg_event->scroll.x_root = x;
+      xg_event->scroll.y = y;
+      xg_event->scroll.y_root = y;
+      xg_event->scroll.state = modifier_state;
+      xg_event->scroll.time = time;
+
+      xg_event->scroll.delta_x = 0;
+      xg_event->scroll.delta_y = 0;
+
+      gtk_main_do_event (xg_event);
+      gdk_event_free (xg_event);
+    }
+}
+
+void
+xwidget_motion_or_crossing (struct xwidget_view *view, const XEvent *event)
+{
+  GdkEvent *xg_event = gdk_event_new (event->type == MotionNotify ? GDK_MOTION_NOTIFY :
+				      (event->type == LeaveNotify ? GDK_LEAVE_NOTIFY :
+				       GDK_ENTER_NOTIFY));
+  struct xwidget *model = XXWIDGET (view->model);
+  int x;
+  int y;
+  GtkWidget *target = find_widget_at_pos (model->widgetwindow_osr,
+					  (event->type == MotionNotify
+					   ? event->xmotion.x + view->clip_left
+					   : event->xmotion.y + view->clip_top),
+					  (event->type == MotionNotify
+					   ? event->xmotion.y + view->clip_left
+					   : event->xcrossing.y + view->clip_top),
+					  &x, &y);
+
+  if (!target)
+    target = model->widgetwindow_osr;
+
+  xg_event->any.window = gtk_widget_get_window (target);
+  g_object_ref (xg_event->any.window); /* The window will be unrefed
+					  later by gdk_event_free. */
+
+  if (event->type == MotionNotify)
+    {
+      xg_event->motion.x = x;
+      xg_event->motion.y = y;
+      xg_event->motion.x_root = event->xmotion.x_root;
+      xg_event->motion.y_root = event->xmotion.y_root;
+      xg_event->motion.time = event->xmotion.time;
+      xg_event->motion.state = event->xmotion.state;
+      xg_event->motion.device = find_suitable_pointer (view->frame);
+    }
+  else
+    {
+      xg_event->crossing.detail = min (5, event->xcrossing.detail);
+      xg_event->crossing.time = event->xcrossing.time;
+      xg_event->crossing.x = x;
+      xg_event->crossing.y = y;
+      xg_event->crossing.x_root = event->xcrossing.x_root;
+      xg_event->crossing.y_root = event->xcrossing.y_root;
+      gdk_event_set_device (xg_event, find_suitable_pointer (view->frame));
+    }
+
+  gtk_main_do_event (xg_event);
+  gdk_event_free (xg_event);
+}
+
+static void
+synthesize_focus_in_event (GtkWidget *offscreen_window)
+{
+  GdkWindow *wnd;
+  GdkEvent *focus_event;
+
+  if (!gtk_widget_get_realized (offscreen_window))
+    gtk_widget_realize (offscreen_window);
+
+  wnd = gtk_widget_get_window (offscreen_window);
+
+  focus_event = gdk_event_new (GDK_FOCUS_CHANGE);
+  focus_event->any.window = wnd;
+  focus_event->focus_change.in = TRUE;
+  g_object_ref (wnd);
+
+  gtk_main_do_event (focus_event);
+  gdk_event_free (focus_event);
+}
+
+struct xwidget_view *
+xwidget_view_from_window (Window wdesc)
+{
+  Lisp_Object key = make_fixnum (wdesc);
+  Lisp_Object xwv = Fgethash (key, x_window_to_xwv_map, Qnil);
+
+  if (NILP (xwv))
+    return NULL;
+
+  return XXWIDGET_VIEW (xwv);
+}
+
 static void
 xwidget_show_view (struct xwidget_view *xv)
 {
   xv->hidden = false;
-  gtk_widget_show (xv->widgetwindow);
-  gtk_fixed_move (GTK_FIXED (xv->emacswindow),
-                  xv->widgetwindow,
-                  xv->x + xv->clip_left,
-                  xv->y + xv->clip_top);
+  XMoveWindow (xv->dpy, xv->wdesc,
+	       xv->x + xv->clip_left,
+	       xv->y + xv->clip_top);
+  XMapWindow (xv->dpy, xv->wdesc);
+  XFlush (xv->dpy);
 }
 
 /* Hide an xwidget view.  */
@@ -238,28 +836,64 @@ xwidget_show_view (struct xwidget_view *xv)
 xwidget_hide_view (struct xwidget_view *xv)
 {
   xv->hidden = true;
-  gtk_fixed_move (GTK_FIXED (xv->emacswindow), xv->widgetwindow,
-                  10000, 10000);
+  XUnmapWindow (xv->dpy, xv->wdesc);
+  XFlush (xv->dpy);
+}
+
+static void
+xv_do_draw (struct xwidget_view *xw, struct xwidget *w)
+{
+  GtkOffscreenWindow *wnd;
+  cairo_surface_t *surface;
+  block_input ();
+  wnd = GTK_OFFSCREEN_WINDOW (w->widgetwindow_osr);
+  surface = gtk_offscreen_window_get_surface (wnd);
+
+  cairo_save (xw->cr_context);
+  if (surface)
+    {
+      cairo_set_source_surface (xw->cr_context, surface, xw->clip_left,
+				xw->clip_top);
+      cairo_set_operator (xw->cr_context, CAIRO_OPERATOR_SOURCE);
+      cairo_paint (xw->cr_context);
+    }
+  cairo_restore (xw->cr_context);
+
+  unblock_input ();
 }
 
 /* When the off-screen webkit master view changes this signal is called.
    It copies the bitmap from the off-screen instance.  */
 static gboolean
 offscreen_damage_event (GtkWidget *widget, GdkEvent *event,
-                        gpointer xv_widget)
-{
-  /* Queue a redraw of onscreen widget.
-     There is a guard against receiving an invalid widget,
-     which should only happen if we failed to remove the
-     specific signal handler for the damage event.  */
-  if (GTK_IS_WIDGET (xv_widget))
-    gtk_widget_queue_draw (GTK_WIDGET (xv_widget));
-  else
-    message ("Warning, offscreen_damage_event received invalid xv pointer:%p\n",
-             xv_widget);
+                        gpointer xwidget)
+{
+  block_input ();
+
+  for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail);
+       tail = XCDR (tail))
+    {
+      if (XWIDGET_VIEW_P (XCAR (tail)))
+	{
+	  struct xwidget_view *view = XXWIDGET_VIEW (XCAR (tail));
+
+	  if (view->wdesc && XXWIDGET (view->model) == xwidget)
+	    xv_do_draw (view, XXWIDGET (view->model));
+	}
+    }
+
+  unblock_input ();
 
   return FALSE;
 }
+
+void
+xwidget_expose (struct xwidget_view *xv)
+{
+  struct xwidget *xw = XXWIDGET (xv->model);
+
+  xv_do_draw (xv, xw);
+}
 #endif /* USE_GTK */
 
 void
@@ -313,22 +947,108 @@ store_xwidget_js_callback_event (struct xwidget *xw,
 
 
 #ifdef USE_GTK
+static void
+store_xwidget_display_event (struct xwidget *xw)
+{
+  struct input_event evt;
+  Lisp_Object val;
+
+  XSETXWIDGET (val, xw);
+  EVENT_INIT (evt);
+  evt.kind = XWIDGET_DISPLAY_EVENT;
+  evt.frame_or_window = Qnil;
+  evt.arg = val;
+  kbd_buffer_store_event (&evt);
+}
+
+static void
+webkit_ready_to_show (WebKitWebView *new_view,
+		      gpointer user_data)
+{
+  Lisp_Object tem;
+  struct xwidget *xw;
+
+  for (tem = Vxwidget_list; CONSP (tem); tem = XCDR (tem))
+    {
+      if (XWIDGETP (XCAR (tem)))
+	{
+	  xw = XXWIDGET (XCAR (tem));
+
+	  if (EQ (xw->type, Qwebkit)
+	      && WEBKIT_WEB_VIEW (xw->widget_osr) == new_view)
+	    store_xwidget_display_event (xw);
+	}
+    }
+}
+
+static GtkWidget *
+webkit_create_cb_1 (WebKitWebView *webview,
+		    struct xwidget_view *xv)
+{
+  Lisp_Object related;
+  Lisp_Object xwidget;
+  GtkWidget *widget;
+
+  XSETXWIDGET (related, xv);
+  xwidget = Fmake_xwidget (Qwebkit, Qnil, make_fixnum (0),
+			   make_fixnum (0), Qnil,
+			   build_string (" *detached xwidget buffer*"),
+			   related);
+
+  if (NILP (xwidget))
+    return NULL;
+
+  widget = XXWIDGET (xwidget)->widget_osr;
+
+  g_signal_connect (G_OBJECT (widget), "ready-to-show",
+		    G_CALLBACK (webkit_ready_to_show), NULL);
+
+  return widget;
+}
+
+static GtkWidget *
+webkit_create_cb (WebKitWebView *webview,
+		  WebKitNavigationAction *nav_action,
+		  gpointer user_data)
+{
+  switch (webkit_navigation_action_get_navigation_type (nav_action))
+    {
+    case WEBKIT_NAVIGATION_TYPE_OTHER:
+      return webkit_create_cb_1 (webview, user_data);
+
+    case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD:
+    case WEBKIT_NAVIGATION_TYPE_RELOAD:
+    case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED:
+    case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
+    case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED:
+    default:
+      return NULL;
+    }
+}
+
 void
 webkit_view_load_changed_cb (WebKitWebView *webkitwebview,
                              WebKitLoadEvent load_event,
                              gpointer data)
 {
-  switch (load_event) {
-  case WEBKIT_LOAD_FINISHED:
+  struct xwidget *xw = g_object_get_data (G_OBJECT (webkitwebview),
+					  XG_XWIDGET);
+
+  switch (load_event)
     {
-      struct xwidget *xw = g_object_get_data (G_OBJECT (webkitwebview),
-                                              XG_XWIDGET);
-      store_xwidget_event_string (xw, "load-changed", "");
+    case WEBKIT_LOAD_FINISHED:
+      store_xwidget_event_string (xw, "load-changed", "load-finished");
+      break;
+    case WEBKIT_LOAD_STARTED:
+      store_xwidget_event_string (xw, "load-changed", "load-started");
+      break;
+    case WEBKIT_LOAD_REDIRECTED:
+      store_xwidget_event_string (xw, "load-changed", "load-redirected");
+      break;
+    case WEBKIT_LOAD_COMMITTED:
+      store_xwidget_event_string (xw, "load-changed", "load-committed");
       break;
     }
-  default:
-    break;
-  }
 }
 
 /* Recursively convert a JavaScript value to a Lisp value. */
@@ -498,51 +1218,6 @@ webkit_decide_policy_cb (WebKitWebView *webView,
     return FALSE;
   }
 }
-
-
-/* For gtk3 offscreen rendered widgets.  */
-static gboolean
-xwidget_osr_draw_cb (GtkWidget *widget, cairo_t *cr, gpointer data)
-{
-  struct xwidget *xw = g_object_get_data (G_OBJECT (widget), XG_XWIDGET);
-  struct xwidget_view *xv = g_object_get_data (G_OBJECT (widget),
-                                               XG_XWIDGET_VIEW);
-
-  cairo_rectangle (cr, 0, 0, xv->clip_right, xv->clip_bottom);
-  cairo_clip (cr);
-
-  gtk_widget_draw (xw->widget_osr, cr);
-  return FALSE;
-}
-
-static gboolean
-xwidget_osr_event_forward (GtkWidget *widget, GdkEvent *event,
-			   gpointer user_data)
-{
-  /* Copy events that arrive at the outer widget to the offscreen widget.  */
-  struct xwidget *xw = g_object_get_data (G_OBJECT (widget), XG_XWIDGET);
-  GdkEvent *eventcopy = gdk_event_copy (event);
-  eventcopy->any.window = gtk_widget_get_window (xw->widget_osr);
-
-  /* TODO: This might leak events.  They should be deallocated later,
-     perhaps in xwgir_event_cb.  */
-  gtk_main_do_event (eventcopy);
-
-  /* Don't propagate this event further.  */
-  return TRUE;
-}
-
-static gboolean
-xwidget_osr_event_set_embedder (GtkWidget *widget, GdkEvent *event,
-				gpointer data)
-{
-  struct xwidget_view *xv = data;
-  struct xwidget *xww = XXWIDGET (xv->model);
-  gdk_offscreen_window_set_embedder (gtk_widget_get_window
-				     (xww->widgetwindow_osr),
-                                     gtk_widget_get_window (xv->widget));
-  return FALSE;
-}
 #endif /* USE_GTK */
 
 
@@ -568,63 +1243,19 @@ xwidget_init_view (struct xwidget *xww,
   XSETXWIDGET (xv->model, xww);
 
 #ifdef USE_GTK
-  if (EQ (xww->type, Qwebkit))
-    {
-      xv->widget = gtk_drawing_area_new ();
-      /* Expose event handling.  */
-      gtk_widget_set_app_paintable (xv->widget, TRUE);
-      gtk_widget_add_events (xv->widget, GDK_ALL_EVENTS_MASK);
-
-      /* Draw the view on damage-event.  */
-      g_signal_connect (G_OBJECT (xww->widgetwindow_osr), "damage-event",
-                        G_CALLBACK (offscreen_damage_event), xv->widget);
+  xv->dpy = FRAME_X_DISPLAY (s->f);
 
-      if (EQ (xww->type, Qwebkit))
-        {
-          g_signal_connect (G_OBJECT (xv->widget), "button-press-event",
-                            G_CALLBACK (xwidget_osr_event_forward), NULL);
-          g_signal_connect (G_OBJECT (xv->widget), "button-release-event",
-                            G_CALLBACK (xwidget_osr_event_forward), NULL);
-          g_signal_connect (G_OBJECT (xv->widget), "motion-notify-event",
-                            G_CALLBACK (xwidget_osr_event_forward), NULL);
-        }
-      else
-        {
-          /* xwgir debug, orthogonal to forwarding.  */
-          g_signal_connect (G_OBJECT (xv->widget), "enter-notify-event",
-                            G_CALLBACK (xwidget_osr_event_set_embedder), xv);
-        }
-      g_signal_connect (G_OBJECT (xv->widget), "draw",
-                        G_CALLBACK (xwidget_osr_draw_cb), NULL);
-    }
-
-  /* Widget realization.
-
-     Make container widget first, and put the actual widget inside the
-     container later.  Drawing should crop container window if necessary
-     to handle case where xwidget is partially obscured by other Emacs
-     windows.  Other containers than gtk_fixed where explored, but
-     gtk_fixed had the most predictable behavior so far.  */
-
-  xv->emacswindow = FRAME_GTK_WIDGET (s->f);
-  xv->widgetwindow = gtk_fixed_new ();
-  gtk_widget_set_has_window (xv->widgetwindow, TRUE);
-  gtk_container_add (GTK_CONTAINER (xv->widgetwindow), xv->widget);
-
-  /* Store some xwidget data in the gtk widgets.  */
-  g_object_set_data (G_OBJECT (xv->widget), XG_FRAME_DATA, s->f);
-  g_object_set_data (G_OBJECT (xv->widget), XG_XWIDGET, xww);
-  g_object_set_data (G_OBJECT (xv->widget), XG_XWIDGET_VIEW, xv);
-  g_object_set_data (G_OBJECT (xv->widgetwindow), XG_XWIDGET, xww);
-  g_object_set_data (G_OBJECT (xv->widgetwindow), XG_XWIDGET_VIEW, xv);
-
-  gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xww->width,
-                               xww->height);
-  gtk_widget_set_size_request (xv->widgetwindow, xww->width, xww->height);
-  gtk_fixed_put (GTK_FIXED (FRAME_GTK_WIDGET (s->f)), xv->widgetwindow, x, y);
   xv->x = x;
   xv->y = y;
-  gtk_widget_show_all (xv->widgetwindow);
+
+  xv->clip_left = 0;
+  xv->clip_right = xww->width;
+  xv->clip_top = 0;
+  xv->clip_bottom = xww->height;
+
+  xv->wdesc = None;
+  xv->frame = s->f;
+  xv->cursor = cursor_for_hit (xww->hit_result, s->f);
 #elif defined NS_IMPL_COCOA
   nsxwidget_init_view (xv, xww, s, x, y);
   nsxwidget_resize_view(xv, xww->width, xww->height);
@@ -681,6 +1312,8 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
   window_box (s->w, TEXT_AREA, &text_area_x, &text_area_y,
               &text_area_width, &text_area_height);
 
+  /* On X11, this keeps generating expose events.  */
+#ifndef USE_GTK
   /* Resize xwidget webkit if its container window size is changed in
      some ways, for example, a buffer became hidden in small split
      window, then it can appear front in merged whole window.  */
@@ -693,6 +1326,7 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
                        make_int (text_area_width),
                        make_int (text_area_height));
     }
+#endif
 
   clip_left = max (0, text_area_x - x);
   clip_right = max (clip_left,
@@ -711,15 +1345,51 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
      later.  */
   bool moved = (xv->x + xv->clip_left != x + clip_left
 		|| xv->y + xv->clip_top != y + clip_top);
+
+#ifdef USE_GTK
+  bool wdesc_was_none = xv->wdesc == None;
+#endif
   xv->x = x;
   xv->y = y;
 
+#ifdef USE_GTK
+  block_input ();
+  if (xv->wdesc == None)
+    {
+      Lisp_Object xvw;
+      XSETXWIDGET_VIEW (xvw, xv);
+      XSetWindowAttributes a;
+      a.event_mask = (ExposureMask | ButtonPressMask | ButtonReleaseMask
+		      | PointerMotionMask | EnterWindowMask | LeaveWindowMask);
+
+      xv->wdesc = XCreateWindow (xv->dpy, FRAME_X_WINDOW (s->f),
+				 x + clip_left, y + clip_top,
+				 clip_right - clip_left,
+				 clip_bottom - clip_top, 0,
+				 CopyFromParent, CopyFromParent,
+				 CopyFromParent, CWEventMask, &a);
+      XDefineCursor (xv->dpy, xv->wdesc, xv->cursor);
+      xv->cr_surface = cairo_xlib_surface_create (xv->dpy,
+						  xv->wdesc,
+						  FRAME_DISPLAY_INFO (s->f)->visual,
+						  clip_right - clip_left,
+						  clip_bottom - clip_top);
+      xv->cr_context = cairo_create (xv->cr_surface);
+      Fputhash (make_fixnum (xv->wdesc), xvw, x_window_to_xwv_map);
+
+      moved = false;
+    }
+#endif
+
   /* Has it moved?  */
   if (moved)
     {
 #ifdef USE_GTK
-      gtk_fixed_move (GTK_FIXED (FRAME_GTK_WIDGET (s->f)),
-                      xv->widgetwindow, x + clip_left, y + clip_top);
+      XMoveResizeWindow (xv->dpy, xv->wdesc, x + clip_left, y + clip_top,
+			 clip_right - clip_left, clip_bottom - clip_top);
+      XFlush (xv->dpy);
+      cairo_xlib_surface_set_size (xv->cr_surface, clip_right - clip_left,
+				   clip_bottom - clip_top);
 #elif defined NS_IMPL_COCOA
       nsxwidget_move_view (xv, x + clip_left, y + clip_top);
 #endif
@@ -735,10 +1405,14 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
       || xv->clip_top != clip_top || xv->clip_left != clip_left)
     {
 #ifdef USE_GTK
-      gtk_widget_set_size_request (xv->widgetwindow, clip_right - clip_left,
-                                   clip_bottom - clip_top);
-      gtk_fixed_move (GTK_FIXED (xv->widgetwindow), xv->widget, -clip_left,
-                      -clip_top);
+      if (!wdesc_was_none && !moved)
+	{
+	  XResizeWindow (xv->dpy, xv->wdesc, clip_right - clip_left,
+			 clip_bottom - clip_top);
+	  XFlush (xv->dpy);
+	  cairo_xlib_surface_set_size (xv->cr_surface, clip_right - clip_left,
+				       clip_bottom - clip_top);
+	}
 #elif defined NS_IMPL_COCOA
       nsxwidget_resize_view (xv, clip_right - clip_left,
                              clip_bottom - clip_top);
@@ -758,12 +1432,15 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
   if (!xwidget_hidden (xv))
     {
 #ifdef USE_GTK
-      gtk_widget_queue_draw (xv->widgetwindow);
-      gtk_widget_queue_draw (xv->widget);
+      gtk_widget_queue_draw (xww->widget_osr);
 #elif defined NS_IMPL_COCOA
       nsxwidget_set_needsdisplay (xv);
 #endif
     }
+
+#ifdef USE_GTK
+  unblock_input ();
+#endif
 }
 
 static bool
@@ -975,16 +1652,13 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
           struct xwidget_view *xv = XXWIDGET_VIEW (XCAR (tail));
           if (XXWIDGET (xv->model) == xw)
             {
-#ifdef USE_GTK
-              gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xw->width,
-                                           xw->height);
-#elif defined NS_IMPL_COCOA
-              nsxwidget_resize_view(xv, xw->width, xw->height);
-#endif
+	      wset_redisplay (XWINDOW (xv->w));
             }
         }
     }
 
+  redisplay ();
+
   return Qnil;
 }
 
@@ -1084,13 +1758,15 @@ DEFUN ("delete-xwidget-view",
   CHECK_XWIDGET_VIEW (xwidget_view);
   struct xwidget_view *xv = XXWIDGET_VIEW (xwidget_view);
 #ifdef USE_GTK
-  gtk_widget_destroy (xv->widgetwindow);
-  /* xv->model still has signals pointing to the view.  There can be
-     several views.  Find the matching signals and delete them all.  */
-  g_signal_handlers_disconnect_matched  (XXWIDGET (xv->model)->widgetwindow_osr,
-                                         G_SIGNAL_MATCH_DATA,
-                                         0, 0, 0, 0,
-                                         xv->widget);
+  if (xv->wdesc != None)
+    {
+      block_input ();
+      cairo_destroy (xv->cr_context);
+      cairo_surface_destroy (xv->cr_surface);
+      XDestroyWindow (xv->dpy, xv->wdesc);
+      Fremhash (make_fixnum (xv->wdesc), x_window_to_xwv_map);
+      unblock_input ();
+    }
 #elif defined NS_IMPL_COCOA
   nsxwidget_delete_view (xv);
 #endif
@@ -1145,6 +1821,19 @@ DEFUN ("xwidget-buffer",
   return XXWIDGET (xwidget)->buffer;
 }
 
+DEFUN ("set-xwidget-buffer",
+       Fset_xwidget_buffer, Sset_xwidget_buffer,
+       2, 2, 0,
+       doc: /* Set XWIDGET's buffer to BUFFER.  */)
+  (Lisp_Object xwidget, Lisp_Object buffer)
+{
+  CHECK_XWIDGET (xwidget);
+  CHECK_BUFFER (buffer);
+
+  XXWIDGET (xwidget)->buffer = buffer;
+  return Qnil;
+}
+
 DEFUN ("set-xwidget-plist",
        Fset_xwidget_plist, Sset_xwidget_plist,
        2, 2, 0,
@@ -1183,6 +1872,166 @@ DEFUN ("xwidget-query-on-exit-flag",
   return (XXWIDGET (xwidget)->kill_without_query ? Qnil : Qt);
 }
 
+DEFUN ("xwidget-webkit-search", Fxwidget_webkit_search, Sxwidget_webkit_search,
+       2, 5, 0,
+       doc: /* Begin an incremental search operation in an xwidget.
+QUERY should be a string containing the text to search for.  XWIDGET
+should be a WebKit xwidget where the search will take place.  When the
+search operation is complete, callers should also call
+`xwidget-webkit-finish-search' to complete the search operation.
+
+CASE-INSENSITIVE, when non-nil, will cause the search to ignore the
+case of characters inside QUERY.  BACKWARDS, when non-nil, will cause
+the search to proceed towards the beginning of the widget's contents.
+WRAP-AROUND, when nil, will cause the search to stop upon hitting the
+end of the widget's contents.
+
+It is OK to call this function even when a search is already in
+progress.  In that case, the previous search query will be replaced
+with QUERY.  */)
+  (Lisp_Object query, Lisp_Object xwidget, Lisp_Object case_insensitive,
+   Lisp_Object backwards, Lisp_Object wrap_around)
+{
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+  WebKitFindOptions opt;
+  struct xwidget *xw;
+  gchar *g_query;
+#endif
+
+  CHECK_STRING (query);
+  CHECK_XWIDGET (xwidget);
+
+#ifdef USE_GTK
+  xw = XXWIDGET (xwidget);
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  query = ENCODE_UTF_8 (query);
+  opt = WEBKIT_FIND_OPTIONS_NONE;
+  g_query = xstrdup (SSDATA (query));
+
+  if (!NILP (case_insensitive))
+    opt |= WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE;
+  if (!NILP (backwards))
+    opt |= WEBKIT_FIND_OPTIONS_BACKWARDS;
+  if (!NILP (wrap_around))
+    opt |= WEBKIT_FIND_OPTIONS_WRAP_AROUND;
+
+  if (xw->find_text)
+    xfree (xw->find_text);
+  xw->find_text = g_query;
+
+  block_input ();
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search (controller, g_query, opt, G_MAXUINT);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
+DEFUN ("xwidget-webkit-next-result", Fxwidget_webkit_next_result,
+       Sxwidget_webkit_next_result, 1, 1, 0,
+       doc: /* Show the next result matching the current search query.
+
+XWIDGET should be an xwidget that currently has a search query.
+Before calling this function, you should start a search operation
+using `xwidget-webkit-search'.  */)
+  (Lisp_Object xwidget)
+{
+  struct xwidget *xw;
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!xw->find_text)
+    error ("Widget has no ongoing search operation");
+
+#ifdef USE_GTK
+  block_input ();
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search_next (controller);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
+DEFUN ("xwidget-webkit-previous-result", Fxwidget_webkit_previous_result,
+       Sxwidget_webkit_previous_result, 1, 1, 0,
+       doc: /* Show the previous result matching the current search query.
+
+XWIDGET should be an xwidget that currently has a search query.
+Before calling this function, you should start a search operation
+using `xwidget-webkit-search'.  */)
+  (Lisp_Object xwidget)
+{
+  struct xwidget *xw;
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!xw->find_text)
+    error ("Widget has no ongoing search operation");
+
+#ifdef USE_GTK
+  block_input ();
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search_previous (controller);
+
+  if (xw->find_text)
+    {
+      xfree (xw->find_text);
+      xw->find_text = NULL;
+    }
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
+DEFUN ("xwidget-webkit-finish-search", Fxwidget_webkit_finish_search,
+       Sxwidget_webkit_finish_search, 1, 1, 0,
+       doc: /* Finish XWIDGET's search operation.
+
+XWIDGET should be an xwidget that currently has a search query.
+Before calling this function, you should start a search operation
+using `xwidget-webkit-search'.  */)
+  (Lisp_Object xwidget)
+{
+  struct xwidget *xw;
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!xw->find_text)
+    error ("Widget has no ongoing search operation");
+
+#ifdef USE_GTK
+  block_input ();
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search_finish (controller);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
 void
 syms_of_xwidget (void)
 {
@@ -1215,6 +2064,12 @@ syms_of_xwidget (void)
   defsubr (&Sxwidget_plist);
   defsubr (&Sxwidget_buffer);
   defsubr (&Sset_xwidget_plist);
+  defsubr (&Sxwidget_perform_lispy_event);
+  defsubr (&Sxwidget_webkit_search);
+  defsubr (&Sxwidget_webkit_finish_search);
+  defsubr (&Sxwidget_webkit_next_result);
+  defsubr (&Sxwidget_webkit_previous_result);
+  defsubr (&Sset_xwidget_buffer);
 
   DEFSYM (QCxwidget, ":xwidget");
   DEFSYM (QCtitle, ":title");
@@ -1236,6 +2091,15 @@ syms_of_xwidget (void)
   Vxwidget_view_list = Qnil;
 
   Fprovide (intern ("xwidget-internal"), Qnil);
+
+  id_to_xwidget_map = CALLN (Fmake_hash_table, QCtest, Qeq);
+  staticpro (&id_to_xwidget_map);
+
+#ifdef USE_GTK
+  x_window_to_xwv_map = CALLN (Fmake_hash_table, QCtest, Qeq);
+
+  staticpro (&x_window_to_xwv_map);
+#endif
 }
 
 
@@ -1374,7 +2238,7 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
 		  /* The only call to xwidget_end_redisplay is in dispnew.
 		     xwidget_end_redisplay (w->current_matrix);  */
 		  struct xwidget_view *xv
-		    = xwidget_view_lookup (glyph->u.xwidget, w);
+		    = xwidget_view_lookup (xwidget_from_id (glyph->u.xwidget), w);
 #ifdef USE_GTK
 		  /* FIXME: Is it safe to assume xwidget_view_lookup
 		     always succeeds here?  If so, this comment can be removed.
@@ -1424,6 +2288,26 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
     }
 }
 
+#ifdef USE_GTK
+void
+kill_frame_xwidget_views (struct frame *f)
+{
+  Lisp_Object rem = Qnil;
+
+  for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail);
+       tail = XCDR (tail))
+    {
+      if (XXWIDGET_VIEW (XCAR (tail))->frame == f)
+	rem = Fcons (XCAR (tail), rem);
+    }
+
+  for (; CONSP (rem); rem = XCDR (rem))
+    {
+      Fdelete_xwidget_view (XCAR (rem));
+    }
+}
+#endif
+
 /* Kill all xwidget in BUFFER.  */
 void
 kill_buffer_xwidgets (Lisp_Object buffer)
@@ -1437,12 +2321,15 @@ kill_buffer_xwidgets (Lisp_Object buffer)
       {
         CHECK_XWIDGET (xwidget);
         struct xwidget *xw = XXWIDGET (xwidget);
+	Fremhash (make_fixnum (xw->xwidget_id), id_to_xwidget_map);
 #ifdef USE_GTK
         if (xw->widget_osr && xw->widgetwindow_osr)
           {
             gtk_widget_destroy (xw->widget_osr);
             gtk_widget_destroy (xw->widgetwindow_osr);
           }
+	if (xw->find_text)
+	  xfree (xw->find_text);
 	if (!NILP (xw->script_callbacks))
 	  for (ptrdiff_t idx = 0; idx < ASIZE (xw->script_callbacks); idx++)
 	    {
diff --git a/src/xwidget.h b/src/xwidget.h
index 591f23489d..82ac74543c 100644
--- a/src/xwidget.h
+++ b/src/xwidget.h
@@ -32,6 +32,8 @@ #define XWIDGET_H_INCLUDED
 
 #if defined (USE_GTK)
 #include <gtk/gtk.h>
+#include <X11/Xlib.h>
+#include "xterm.h"
 #elif defined (NS_IMPL_COCOA) && defined (__OBJC__)
 #import <AppKit/NSView.h>
 #import "nsxwidget.h"
@@ -59,11 +61,14 @@ #define XWIDGET_H_INCLUDED
 
   int height;
   int width;
+  uint32_t xwidget_id;
 
 #if defined (USE_GTK)
   /* For offscreen widgets, unused if not osr.  */
   GtkWidget *widget_osr;
   GtkWidget *widgetwindow_osr;
+  guint hit_result;
+  gchar *find_text;
 #elif defined (NS_IMPL_COCOA)
 # ifdef __OBJC__
   /* For offscreen widgets, unused if not osr.  */
@@ -98,9 +103,13 @@ #define XWIDGET_H_INCLUDED
   bool hidden;
 
 #if defined (USE_GTK)
-  GtkWidget *widget;
-  GtkWidget *widgetwindow;
-  GtkWidget *emacswindow;
+  Display *dpy;
+  Window wdesc;
+  Emacs_Cursor cursor;
+  struct frame *frame;
+
+  cairo_surface_t *cr_surface;
+  cairo_t *cr_context;
 #elif defined (NS_IMPL_COCOA)
 # ifdef __OBJC__
   XvWindow *xvWindow;
@@ -162,6 +171,18 @@ #define XG_XWIDGET_VIEW "emacs_xwidget_view"
 void store_xwidget_js_callback_event (struct xwidget *xw,
                                       Lisp_Object proc,
                                       Lisp_Object argument);
+
+extern struct xwidget *xwidget_from_id (uint32_t id);
+
+#ifdef HAVE_X_WINDOWS
+struct xwidget_view *xwidget_view_from_window (Window wdesc);
+void xwidget_expose (struct xwidget_view *xv);
+extern void kill_frame_xwidget_views (struct frame *f);
+extern void xwidget_button (struct xwidget_view *, bool, int,
+			    int, int, int, Time);
+extern void xwidget_motion_or_crossing (struct xwidget_view *,
+					const XEvent *);
+#endif
 #else
 INLINE_HEADER_BEGIN
 INLINE void syms_of_xwidget (void) {}

[-- Attachment #3: Type: text/plain, Size: 9 bytes --]


Thanks.

^ permalink raw reply related	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07  1:18                             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-07  1:23                               ` Lars Ingebrigtsen
  2021-11-07  1:26                                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 39+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-07  1:23 UTC (permalink / raw)
  To: Po Lu; +Cc: 51473

Po Lu <luangruo@yahoo.com> writes:

> Does this version fix the problem?

It gets further:

xwidget.c:1951:12: error: no member named 'find_text' in 'struct xwidget'
  if (!xw->find_text)
       ~~  ^
xwidget.c:1983:12: error: no member named 'find_text' in 'struct xwidget'
  if (!xw->find_text)
       ~~  ^
xwidget.c:2021:12: error: no member named 'find_text' in 'struct xwidget'
  if (!xw->find_text)
       ~~  ^

Oh, and I didn't notice this error the first time around, apparently:

print.c:1529:23: error: no member named 'widget_osr' in 'struct xwidget'
                           XXWIDGET (obj)->widget_osr);
                           ~~~~~~~~~~~~~~  ^


-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07  1:23                               ` Lars Ingebrigtsen
@ 2021-11-07  1:26                                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-07  1:29                                   ` Lars Ingebrigtsen
  0 siblings, 1 reply; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-07  1:26 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: Eli Zaretskii, 51473

[-- Attachment #1: Type: text/plain, Size: 699 bytes --]

Lars Ingebrigtsen <larsi@gnus.org> writes:

> It gets further:
>
> xwidget.c:1951:12: error: no member named 'find_text' in 'struct xwidget'
>   if (!xw->find_text)
>        ~~  ^
> xwidget.c:1983:12: error: no member named 'find_text' in 'struct xwidget'
>   if (!xw->find_text)
>        ~~  ^
> xwidget.c:2021:12: error: no member named 'find_text' in 'struct xwidget'
>   if (!xw->find_text)
>        ~~  ^
>
> Oh, and I didn't notice this error the first time around, apparently:
>
> print.c:1529:23: error: no member named 'widget_osr' in 'struct xwidget'
>                            XXWIDGET (obj)->widget_osr);
>                            ~~~~~~~~~~~~~~  ^

Thanks, does this work instead?

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: test.diff --]
[-- Type: text/x-patch, Size: 94402 bytes --]

diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi
index 7f91e1c188..1207ab5e9a 100644
--- a/doc/emacs/misc.texi
+++ b/doc/emacs/misc.texi
@@ -2953,6 +2953,34 @@ Embedded WebKit Widgets
 reloading it.  Type @w{@kbd{C-h b}} in that buffer to see the key
 bindings.
 
+@findex xwidget-webkit-edit-mode
+@cindex xwidget-webkit-edit-mode
+  By default, typing a self-inserting character inside an xwidget
+webkit buffer will do nothing, or trigger some special action.  To
+make those characters and other common editing keys insert themselves
+when pressed, you can enable @code{xwidget-webkit-edit-mode}, which
+redefines them to be passed through to the WebKit xwidget.
+
+You can also enable @code{xwidget-webkit-edit-mode} by typing @kbd{e}
+inside the xwidget webkit buffer.
+
+@findex xwidget-webkit-isearch-mode
+@cindex xwidget-webkit-isearch-mode
+@cindex searching in webkit buffers
+  @code{xwidget-webkit-isearch-mode} is a minor mode that behaves
+similarly to incremental search (@pxref{Incremental Search}), but
+operates on the contents of a WebKit widget instead of the current
+buffer.  It is bound to @kbd{C-s} and @kbd{C-r} inside xwidget-webkit
+buffers.  When it is enabled through @kbd{C-r}, the initial search
+will be performed in reverse direction.
+
+Typing any self-inserting character will cause the character to be
+inserted into the current search query.  Typing @kbd{C-s} will cause
+the WebKit widget to display the next search result, while typing
+@kbd{C-r} will cause it to display the last.
+
+To leave incremental search, you can type @kbd{C-g}.
+
 @node Browse-URL
 @subsection  Following URLs
 @cindex World Wide Web
diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi
index b38a83b4fe..832b570b6a 100644
--- a/doc/lispref/commands.texi
+++ b/doc/lispref/commands.texi
@@ -1176,6 +1176,7 @@ Input Events
 * Repeat Events::               Double and triple click (or drag, or down).
 * Motion Events::               Just moving the mouse, not pushing a button.
 * Focus Events::                Moving the mouse between frames.
+* Xwidget Events::              Events generated by xwidgets.
 * Misc Events::                 Other events the system can generate.
 * Event Examples::              Examples of the lists for mouse events.
 * Classifying Events::          Finding the modifier keys in an event symbol.
@@ -1871,6 +1872,76 @@ Focus Events
 so that the focus event comes either before or after the multi-event key
 sequence, and not within it.
 
+@node Xwidget Events
+@subsection Xwidget events
+
+Xwidgets (@pxref{Xwidgets}) can send events to update Lisp programs on
+their status.  These events are dubbed @code{xwidget-events}, and
+contain various data describing the nature of the change.
+
+@table @code
+@cindex @code{xwidget-event} event
+@item (xwidget-event @var{kind} @var{xwidget} @var{arg})
+This event is sent whenever some kind of update occurs in
+@var{xwidget}.  There are several types of updates, which are
+identified by @var{kind}.
+
+@cindex @code{load-changed} xwidget events
+An xwidget event with @var{kind} set to @code{load-changed} indicates
+that the @var{xwidget} has reached a particular point of the
+page-loading process.  When these events are sent, @var{arg} will
+contain a string that futher describes the status of the widget.
+
+@cindex @samp{"load-finished"} in xwidgets
+When @var{arg} is @samp{"load-finished"}, it means the xwidget has
+finished processing whatever page-loading operation that it was
+previously performing.
+
+@cindex @samp{"load-started"} in xwidgets
+Otherwise, if it is @samp{"load-started"}, then the widget has begun a
+page-loading operation.
+
+@cindex @samp{"load-redirected"} in xwidgets
+If @var{arg} is @samp{"load-redirected"}, it means the widget has
+encountered and followed a redirect during the page-loading operation.
+
+@cindex @samp{"load-committed"} in xwidgets
+If @var{arg} is @samp{"load-committed"}, then the widget has committed
+to a given URL during the page-loading operation.  This means that the
+URL is the final URL that will be rendered by @var{xwidget} during the
+current page-loading operation.
+
+@cindex @code{download-callback} xwidget events
+An event with @var{kind} set to @code{download-callback} indicates
+that a download of some kind has been completed.
+
+In these events, there can be arguments after @var{arg}, which itself
+indicates the URL that the download file was retrieved from: the first
+argument after @var{arg} indicates the MIME type of the download, as a
+string, while the second such argument contains the full file path to
+the downloaded file.
+
+@cindex @code{download-started} xwidget events
+An event with @var{kind} set to @code{download-started} indicates that
+a download has been started.  In these events, @var{arg} contains the
+URL of the file that is currently being downloaded.
+
+@cindex @code{javascript-callback} xwidget events
+An event with @var{kind} set to @code{javascript-callback} contains
+JavaScript callback data.  These events are used internally by
+@code{xwidget-webkit-execute-script}.
+
+@cindex @code{xwidget-display-event} event
+@item (xwidget-display-event @var{xwidget})
+This event is sent whenever an xwidget requests that another xwidget
+be displayed.  @var{xwidget} is the xwidget that should be displayed.
+
+@var{xwidget}'s buffer will be set to a temporary buffer.  When
+displaying the widget, care should be taken to replace the buffer with
+the buffer in which the xwidget will be displayed, using
+@code{set-xwidget-buffer}  (@pxref{Xwidgets}).
+@end table
+
 @node Misc Events
 @subsection Miscellaneous System Events
 
diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi
index 22528a1b0f..60bca15eb2 100644
--- a/doc/lispref/display.texi
+++ b/doc/lispref/display.texi
@@ -6784,7 +6784,10 @@ Xwidgets
 in a @code{display} text or overlay property (@pxref{Display
 Property}).
 
-@defun make-xwidget type title width height arguments &optional buffer
+  Embedded widgets can send events notifying Lisp code about changes
+occurring within them.  (@pxref{Xwidget Events}).
+
+@defun make-xwidget type title width height arguments &optional buffer related
 This creates and returns an xwidget object.  If
 @var{buffer} is omitted or @code{nil}, it defaults to the current
 buffer.  If @var{buffer} names a buffer that doesn't exist, it will be
@@ -6797,7 +6800,10 @@ Xwidgets
 @end table
 
 The @var{width} and @var{height} arguments specify the widget size in
-pixels, and @var{title}, a string, specifies its title.
+pixels, and @var{title}, a string, specifies its title.  @var{related}
+is used internally by the WebKit widget, and specifies another WebKit
+widget that the newly created widget should share settings and
+subprocesses with.
 @end defun
 
 @defun xwidgetp object
@@ -6818,6 +6824,10 @@ Xwidgets
 This function returns the buffer of @var{xwidget}.
 @end defun
 
+@defun set-xwidget-buffer xwidget buffer
+This function sets the buffer of @var{xwidget} to @var{buffer}.
+@end defun
+
 @defun get-buffer-xwidgets buffer
 This function returns a list of xwidget objects associated with the
 @var{buffer}, which can be specified as a buffer object or a name of
@@ -6878,6 +6888,61 @@ Xwidgets
 query-on-exit flag, either @code{t} or @code{nil}.
 @end defun
 
+@defun xwidget-perform-lispy-event xwidget event frame
+Send an input event @var{event} to @var{xwidget}.  The precise action
+performed is platform-specific.  See @ref{Input Events}.
+
+You can optionally pass the frame the event was generated from via
+@var{frame}.  On X11, modifier keys in key events will not be
+considered if @var{frame} is @code{nil}, and the selected frame is not
+an X-Windows frame.
+
+On GTK, only keyboard and function key events are implemented.  Mouse,
+motion, and click events are dispatched to the xwidget without going
+through Lisp code, and as such shouldn't require this function to be
+sent.
+@end defun
+
+@defun xwidget-webkit-search query xwidget &optional case-insensitive backwards wrap-around
+Start an incremental search on the WebKit widget @var{xwidget} with
+the string @var{query} as a query.  @var{case-insensitive} denotes
+whether or not the search is case-insensitive, @var{backwards}
+determines if the search is performed backwards towards the start of
+the document, and @var{wrap-around} determines whether or not the
+search terminates at the end of the document.
+
+If the function is called while a search query is already present,
+then the query specified here will replace the existing query.
+
+To stop a search query, use @code{xwidget-webkit-finish-search}.
+@end defun
+
+@defun xwidget-webkit-next-result xwidget
+Display the next search result in @var{xwidget}.  This function will
+error unless a search query has already been started in @var{xwidget}
+through @code{xwidget-webkit-search}.
+
+If @code{wrap-around} was non-nil when @code{xwidget-webkit-search}
+was called, then the search will restart from the beginning of the
+document if the end is reached.
+@end defun
+
+@defun xwidget-webkit-previous-result xwidget
+Display the previous search result in @var{xwidget}.  This function
+will error unless a search query has already been started in
+@var{xwidget} through @code{xwidget-webkit-search}.
+
+If @code{wrap-around} was non-nil when @code{xwidget-webkit-search}
+was called, then the search will restart from the end of the
+document if the beginning is reached.
+@end defun
+
+@defun xwidget-webkit-finish-search xwidget
+Finish a search operation started with @code{xwidget-webkit-search} in
+@var{xwidget}.  If there is no query currently ongoing, then this
+function will error.
+@end defun
+
 @node Buttons
 @section Buttons
 @cindex buttons in buffers
diff --git a/etc/NEWS b/etc/NEWS
index a50229916f..b14f9a2549 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -495,6 +495,31 @@ the buffer will take you to that directory.
 This is a convenience function to extract the field data from
 'exif-parse-file' and 'exif-parse-buffer'.
 
+** Xwidgets
+
++++
+*** New minor mode `xwidget-webkit-edit-mode'.
+When this mode is enabled, self-inserting characters and other common
+web browser shotcut keys are redefined to send themselves to the
+WebKit widget.
+
++++
+*** New minor mode `xwidget-webkit-isearch-mode'.
+This mode acts similarly to incremental search, and allows to search
+the contents of a WebKit widget.  In xwidget-webkit mode, it is bound
+to `C-s' and `C-r'.
+
+---
+*** On X11, the WebKit inspector is now available inside xwidgets.
+To access the inspector, right click on the widget and select "Inspect
+Element".
+
+---
+*** "Open in New Window" in a WebKit widget's context menu now works.
+The newly created buffer will be displayed via display-buffer, which
+can be customized through the usual mechanism of display-buffer-alist
+and friends.
+
 \f
 * New Modes and Packages in Emacs 29.1
 
@@ -719,6 +744,39 @@ an exact match, then the lowercased '[menu-bar foo\ bar]' and finally
 '[menu-bar foo-bar]'.  This further improves backwards-compatibility
 when converting menus to use 'easy-menu-define'.
 
++++
+** The function `make-xwidget' now accepts an optional RELATED argument.
+This argument is used as another widget for the newly created WebKit
+widget to share settings and subprocesses with.  It must be another
+WebKit widget.
+
++++
+** New function `xwidget-perform-lispy-event'.
+This function allows you to send events to xwidgets.  Usually, some
+equivalent of the event will be sent, but there is no guarantee of
+what the widget will actually receive.
+
+On GTK+, only key and function key events are implemented.
+
++++
+** New functions for performing searches on WebKit xwidgets.
+Some new functions, such as `xwidget-webkit-search', have been added
+for performing searches on WebKit xwidgets.
+
++++
+** `load-changed' xwidget events are now more detailed.
+In particular, they can now have different arguments based on the
+state of the WebKit widget.  `load-finished' is sent when a load has
+completed, `load-started' when a load first starts, `load-redirected'
+after a redirect, and `load-committed' when the WebKit widget first
+commits to the load.
+
++++
+** New event type `xwidget-display-event'.
+These events are sent whenever an xwidget requests that Emacs display
+another.  The only argument to this event is the xwidget that should
+be displayed.
+
 \f
 * Changes in Emacs 29.1 on Non-Free Operating Systems
 
diff --git a/etc/images/README b/etc/images/README
index 9bbe796cc9..561cfff765 100644
--- a/etc/images/README
+++ b/etc/images/README
@@ -68,6 +68,7 @@ Emacs images and their source in the GNOME icons stock/ directory:
   bookmark_add.xpm          actions/bookmark_add
   cancel.xpm                slightly modified generic/stock_stop
   connect.xpm               net/stock_connect
+  connect-to-url.xpm        net/stock_connect-to-url
   contact.xpm               net/stock_contact
   data-save.xpm             data/stock_data-save
   delete.xpm                generic/stock_delete
diff --git a/etc/images/connect-to-url.pbm b/etc/images/connect-to-url.pbm
new file mode 100644
index 0000000000..f142349f4a
Binary files /dev/null and b/etc/images/connect-to-url.pbm differ
diff --git a/etc/images/connect-to-url.xpm b/etc/images/connect-to-url.xpm
new file mode 100644
index 0000000000..38fefeaf61
--- /dev/null
+++ b/etc/images/connect-to-url.xpm
@@ -0,0 +1,281 @@
+/* XPM */
+static char *connect_to_url[] = {
+/* columns rows colors chars-per-pixel */
+"24 24 251 2 ",
+"   c black",
+".  c #010101",
+"X  c #000103",
+"o  c #010204",
+"O  c #010305",
+"+  c #020407",
+"@  c #020609",
+"#  c #03070C",
+"$  c #04080D",
+"%  c #0F0F0D",
+"&  c #030A10",
+"*  c #050B10",
+"=  c #060C11",
+"-  c #070D13",
+";  c #070D14",
+":  c #060C15",
+">  c #070E14",
+",  c #0B1824",
+"<  c #0A1B2B",
+"1  c #0A1C2E",
+"2  c #141A20",
+"3  c #161E25",
+"4  c #181E23",
+"5  c #0D2032",
+"6  c #142534",
+"7  c #1F2830",
+"8  c #1D2933",
+"9  c #102438",
+"0  c #272622",
+"q  c #21292F",
+"w  c #272F36",
+"e  c #282F33",
+"r  c #222F3A",
+"t  c #2E3337",
+"y  c #2D373E",
+"u  c #32383C",
+"i  c #33383C",
+"p  c #343A3E",
+"a  c #43423C",
+"s  c #112941",
+"d  c #102A44",
+"f  c #132D47",
+"g  c #192F46",
+"h  c #17314B",
+"j  c #15314F",
+"k  c #163351",
+"l  c #163554",
+"z  c #173554",
+"x  c #1F3A53",
+"c  c #1D3955",
+"v  c #1A3958",
+"b  c #1C3B5B",
+"n  c #1F3C58",
+"m  c #1D3C5C",
+"M  c #1E3E5D",
+"N  c #1F3F5F",
+"B  c #303B44",
+"V  c #313C44",
+"C  c #313D47",
+"Z  c #213C56",
+"A  c #233E57",
+"S  c #1F405F",
+"D  c #374148",
+"F  c #2D4050",
+"G  c #25405B",
+"H  c #25425E",
+"J  c #214262",
+"K  c #244565",
+"L  c #264665",
+"P  c #254666",
+"I  c #2A4967",
+"U  c #284969",
+"Y  c #2A4C6C",
+"T  c #2C4F6F",
+"R  c #33526E",
+"E  c #385269",
+"W  c #2D5070",
+"Q  c #2E5172",
+"!  c #335473",
+"~  c #3F5B75",
+"^  c #3D5F7D",
+"/  c #41494F",
+"(  c #646056",
+")  c #6C685E",
+"_  c #505F6C",
+"`  c #48657C",
+"'  c #556A7A",
+"]  c #5B6C78",
+"[  c #5F6F7B",
+"{  c #5D6F7D",
+"}  c #706C62",
+"|  c #726D63",
+" . c #78756B",
+".. c #7D786E",
+"X. c #60727F",
+"o. c #807D74",
+"O. c #8A857B",
+"+. c #8B877E",
+"@. c #4E6A83",
+"#. c #4A6A86",
+"$. c #4A7090",
+"%. c #587790",
+"&. c #5F7E95",
+"*. c #587B98",
+"=. c #6F7980",
+"-. c #697F8F",
+";. c #66839B",
+":. c #6A879F",
+">. c #708391",
+",. c #728A9A",
+"<. c #748898",
+"1. c #758A99",
+"2. c #7B8F9F",
+"3. c #708DA4",
+"4. c #7990A1",
+"5. c #7292AB",
+"6. c #7691A8",
+"7. c #7693AB",
+"8. c #7B98AE",
+"9. c #7E98AD",
+"0. c #7E9DB3",
+"q. c #7F9EB4",
+"w. c #8C8981",
+"e. c #989389",
+"r. c #A6A29B",
+"t. c #8093A1",
+"y. c #8598A3",
+"u. c #8498A7",
+"i. c #809AAD",
+"p. c #8F9FAA",
+"a. c #899FAE",
+"s. c #819FB5",
+"d. c #86A2B8",
+"f. c #87A5BB",
+"g. c #88A3B8",
+"h. c #89A5BA",
+"j. c #8FABBF",
+"k. c #97A7B1",
+"l. c #90AABE",
+"z. c #91ABBF",
+"x. c #98ACB9",
+"c. c #AAA7A0",
+"v. c #B1ADA4",
+"b. c #B3B1AA",
+"n. c #B7B3AA",
+"m. c #A3B1BC",
+"M. c #A5B1BC",
+"N. c #A9B6BF",
+"B. c #BEBBB5",
+"V. c #C4C2BD",
+"C. c #94AEC1",
+"Z. c #96AEC1",
+"A. c #94AFC2",
+"S. c #95AFC2",
+"D. c #96B0C3",
+"F. c #98B0C3",
+"G. c #9FB5C3",
+"H. c #99B3C6",
+"J. c #98B3C7",
+"K. c #9AB3C6",
+"L. c #9BB4C7",
+"P. c #9FB8CA",
+"I. c #9FB8CB",
+"U. c #A2B8C9",
+"Y. c #A3B9C9",
+"T. c #A0B9CB",
+"R. c #A3BACB",
+"E. c #A0B9CC",
+"W. c #A2BACC",
+"Q. c #A4BDCE",
+"!. c #A6BECF",
+"~. c #B8BEC2",
+"^. c #B8C3CA",
+"/. c #BCC5CB",
+"(. c #BDC8CE",
+"). c #A8C0D1",
+"_. c #AAC0D0",
+"`. c #ABC1D1",
+"'. c #ACC2D3",
+"]. c #AAC5D7",
+"[. c #B4C8D6",
+"{. c #BDCBD5",
+"}. c #B4C9D8",
+"|. c #B6CAD8",
+" X c #B8CBD9",
+".X c #BBCDDB",
+"XX c #B7D0E0",
+"oX c #BDD3E2",
+"OX c #BCD5E5",
+"+X c #CECAC3",
+"@X c #C5D2C8",
+"#X c #C0D2DE",
+"$X c #C4D3DF",
+"%X c #CCD7DE",
+"&X c #D2D8DC",
+"*X c #E1DFDB",
+"=X c #E2E1DD",
+"-X c #C2D3E0",
+";X c #C2D4E1",
+":X c #C5D5E1",
+">X c #C6D6E1",
+",X c #C4D6E2",
+"<X c #C5D6E3",
+"1X c #C6D7E3",
+"2X c #C3D7E4",
+"3X c #C1D7E6",
+"4X c #C7D8E3",
+"5X c #C5D8E5",
+"6X c #C7D9E5",
+"7X c #CBD9E4",
+"8X c #CBDAE5",
+"9X c #CDDAE4",
+"0X c #CCDBE5",
+"qX c #CFDBE5",
+"wX c #CBDCE7",
+"eX c #C0D9E8",
+"rX c #C2DBEA",
+"tX c #C4DAE8",
+"yX c #D0DEE7",
+"uX c #D1DFE8",
+"iX c #D0DFE9",
+"pX c #D0E0EA",
+"aX c #D1E1EB",
+"sX c #D3E1EA",
+"dX c #D4E1E9",
+"fX c #D4E1EA",
+"gX c #D5E2EA",
+"hX c #D4E2EB",
+"jX c #D6E2EB",
+"kX c #D3E2EC",
+"lX c #D8E3EA",
+"zX c #DFE6EB",
+"xX c #D9E4EC",
+"cX c #D9E5ED",
+"vX c #DAE5ED",
+"bX c #DAE6ED",
+"nX c #DCE7EE",
+"mX c #DBE8EF",
+"MX c #DDE8EF",
+"NX c #DFE8EF",
+"BX c #EAE8E3",
+"VX c #EBEAE6",
+"CX c #ECEBE8",
+"ZX c #E9EEEA",
+"AX c #F0EFEC",
+"SX c #F2F0ED",
+"DX c #E1ECF3",
+"FX c #E4EDF3",
+"GX c #E8EFF4",
+"HX c #F0F3F1",
+"JX c None",
+/* pixels */
+"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJXu D p t i V w JXJXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXC X./.&XDXGX%X{.m._ r JXJXJXJXJXJXJX",
+"JXJXJXJXJXi /.DXnXnXFXuX7X$X$XjXM.w JXJXJXJXJXJX",
+"JXJXJXJX/ ^.qXbX1XkX5X5X-X;XsXqXjXN.B JXJXJXJXJX",
+"JXJXJXe (.bXMXDXaXtXtX3XoXbXjXsXyX7Xx.q JXJXJXJX",
+"JXJX7 k.jXbXbX5X3XeXrXOXXX1XsXyXwX$X|.4.3 JXJXJX",
+"JXJXX.:XuXjX'.]._.y.    G.sXW.|..X$X[.H.' JXJXJX",
+"JXJXu.$XqXT.H.>.    e.o.  sXwX}.R.R.`.H.1.- JXJX",
+"JX4 a.9.C.h.] a n.V.BXo.        p.!.T.l.4.- JXJX",
+"JX2 F.d.5.7.  =XAXc.BXo.  @X@XZX  !.C.F.@.> JXJX",
+"            o.=XAXc.BXo.        t.U.z.3.Y $ JXJX",
+"BXBXBXBXVXBXBXAXVXO.CXo.  P.C.!.I.J.C.;.L * JXJX",
+"o.o.o.o.o. . .B.b...*X .  $.*.T.J.A.h.Y c @ JXJX",
+"             .w.r.| +X .        1.C.3.L h   JXJX",
+"JXJX6 Q ^ 1.% w.r.| +X .  @X@XHX  h.:.M ,   JXJX",
+"JXJXO x T #.] 0 +.} v.)         -.s.H 9 O JXJXJX",
+"JXJXJX+ n ! i.X.% % e.(   Q Y %.0.&.f O   JXJXJX",
+"JXJXJXJX& A s.8.E A % % A K J R ` g @   JXJXJXJX",
+"JXJXJXJXJX@ C ~ m M J N M b v l < O   JXJXJXJXJX",
+"JXJXJXJXJXJX  : 5 d k z k d 1 &     JXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJX                JXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX"
+};
diff --git a/lisp/xwidget.el b/lisp/xwidget.el
index 8c593abea8..4046140895 100644
--- a/lisp/xwidget.el
+++ b/lisp/xwidget.el
@@ -35,8 +35,9 @@
 (require 'bookmark)
 
 (declare-function make-xwidget "xwidget.c"
-                  (type title width height arguments &optional buffer))
+                  (type title width height arguments &optional buffer related))
 (declare-function xwidget-buffer "xwidget.c" (xwidget))
+(declare-function set-xwidget-buffer "xwidget.c" (xwidget buffer))
 (declare-function xwidget-size-request "xwidget.c" (xwidget))
 (declare-function xwidget-resize "xwidget.c" (xwidget new-width new-height))
 (declare-function xwidget-webkit-execute-script "xwidget.c"
@@ -58,14 +59,14 @@ xwidget
   "Displaying native widgets in Emacs buffers."
   :group 'widgets)
 
-(defun xwidget-insert (pos type title width height &optional args)
+(defun xwidget-insert (pos type title width height &optional args related)
   "Insert an xwidget at position POS.
-Supply the xwidget's TYPE, TITLE, WIDTH, and HEIGHT.
+Supply the xwidget's TYPE, TITLE, WIDTH, HEIGHT, and RELATED.
 See `make-xwidget' for the possible TYPE values.
 The usage of optional argument ARGS depends on the xwidget.
 This returns the result of `make-xwidget'."
   (goto-char pos)
-  (let ((id (make-xwidget type title width height args)))
+  (let ((id (make-xwidget type title width height args nil related)))
     (put-text-property (point) (+ 1 (point))
                        'display (list 'xwidget ':xwidget id))
     id))
@@ -88,6 +89,9 @@ xwidget-at
 (require 'seq)
 (require 'url-handlers)
 
+(defvar-local xwidget-webkit--title ""
+  "The title of the WebKit widget, used for the header line.")
+
 ;;;###autoload
 (defun xwidget-webkit-browse-url (url &optional new-session)
   "Ask xwidget-webkit to browse URL.
@@ -124,6 +128,14 @@ xwidget-webkit-clone-and-split-right
     (with-selected-window (split-window-right)
       (xwidget-webkit-new-session url))))
 
+(declare-function xwidget-perform-lispy-event "xwidget.c")
+
+(defun xwidget-webkit-pass-command-event ()
+  "Pass `last-command-event' to the current buffer's WebKit widget."
+  (interactive)
+  (xwidget-perform-lispy-event (xwidget-webkit-current-session)
+                               last-command-event))
+
 ;;todo.
 ;; - check that the webkit support is compiled in
 (defvar xwidget-webkit-mode-map
@@ -138,6 +150,9 @@ xwidget-webkit-mode-map
     (define-key map "w" 'xwidget-webkit-current-url)
     (define-key map "+" 'xwidget-webkit-zoom-in)
     (define-key map "-" 'xwidget-webkit-zoom-out)
+    (define-key map "e" 'xwidget-webkit-edit-mode)
+    (define-key map "\C-r" 'xwidget-webkit-isearch-mode)
+    (define-key map "\C-s" 'xwidget-webkit-isearch-mode)
 
     ;;similar to image mode bindings
     (define-key map (kbd "SPC")                 'xwidget-webkit-scroll-up)
@@ -164,6 +179,63 @@ xwidget-webkit-mode-map
     map)
   "Keymap for `xwidget-webkit-mode'.")
 
+(easy-menu-define nil xwidget-webkit-mode-map "Xwidget WebKit menu."
+  (list "Xwidget WebKit"
+        ["Browse URL" xwidget-webkit-browse-url
+         :active t
+         :help "Prompt for a URL, then instruct WebKit to browse it"]
+        ["Back" xwidget-webkit-back t]
+        ["Forward" xwidget-webkit-forward t]
+        ["Reload" xwidget-webkit-reload t]
+        ["Insert String" xwidget-webkit-insert-string
+         :active t
+         :help "Insert a string into the currently active field"]
+        ["Zoom In" xwidget-webkit-zoom-in t]
+        ["Zoom Out" xwidget-webkit-zoom-out t]
+        ["Edit Mode" xwidget-webkit-edit-mode
+         :active t
+         :style toggle
+         :selected xwidget-webkit-edit-mode
+         :help "Send self inserting characters to the WebKit widget"]
+        ["Save Selection" xwidget-webkit-copy-selection-as-kill
+         :active t
+         :help "Save the browser's selection in the kill ring"]
+        ["Incremental Search" xwidget-webkit-isearch-mode
+         :active (not xwidget-webkit-isearch-mode)
+         :help "Perform incremental search inside the WebKit widget"]))
+
+(defvar xwidget-webkit-tool-bar-map
+  (let ((map (make-sparse-keymap)))
+    (prog1 map
+      (tool-bar-local-item-from-menu 'xwidget-webkit-back
+                                     "left-arrow"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-forward
+                                     "right-arrow"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-reload
+                                     "refresh"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-zoom-in
+                                     "zoom-in"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-zoom-out
+                                     "zoom-out"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-browse-url
+                                     "connect-to-url"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-isearch-mode
+                                     "search"
+                                     map
+                                     xwidget-webkit-mode-map))))
+
 (defun xwidget-webkit-zoom-in ()
   "Increase webkit view zoom factor."
   (interactive nil xwidget-webkit-mode)
@@ -276,6 +348,8 @@ xwidget-webkit-callback
     (with-current-buffer (xwidget-buffer xwidget)
       (cond ((eq xwidget-event-type 'load-changed)
              (let ((title (xwidget-webkit-title xwidget)))
+               (setq xwidget-webkit--title title)
+               (force-mode-line-update)
                (xwidget-log "webkit finished loading: %s" title)
                ;; Do not adjust webkit size to window here, the selected window
                ;; can be the mini-buffer window unwantedly.
@@ -309,8 +383,10 @@ bookmark-make-record-function
 (define-derived-mode xwidget-webkit-mode special-mode "xwidget-webkit"
   "Xwidget webkit view mode."
   (setq buffer-read-only t)
+  (setq-local tool-bar-map xwidget-webkit-tool-bar-map)
   (setq-local bookmark-make-record-function
               #'xwidget-webkit-bookmark-make-record)
+  (setq-local header-line-format 'xwidget-webkit--title)
   ;; Keep track of [vh]scroll when switching buffers
   (image-mode-setup-winprops))
 
@@ -609,6 +685,7 @@ xwidget-webkit-new-session
   (let*
       ((bufname (generate-new-buffer-name "*xwidget-webkit*"))
        (callback (or callback #'xwidget-webkit-callback))
+       (current-session (xwidget-webkit-current-session))
        xw)
     (setq xwidget-webkit-last-session-buffer (switch-to-buffer
                                               (get-buffer-create bufname)))
@@ -621,11 +698,35 @@ xwidget-webkit-new-session
       (setq xw (xwidget-insert
                 start 'webkit bufname
                 (xwidget-window-inside-pixel-width (selected-window))
-                (xwidget-window-inside-pixel-height (selected-window)))))
+                (xwidget-window-inside-pixel-height (selected-window))
+                nil current-session)))
     (xwidget-put xw 'callback callback)
     (xwidget-webkit-mode)
     (xwidget-webkit-goto-uri (xwidget-webkit-last-session) url)))
 
+(defun xwidget-webkit-import-widget (xwidget)
+  "Create a new webkit session buffer from XWIDGET, an existing xwidget.
+Return the buffer."
+  (let* ((bufname (generate-new-buffer-name "*xwidget-webkit*"))
+         (callback #'xwidget-webkit-callback)
+         (buffer (get-buffer-create bufname)))
+    (with-current-buffer buffer
+      (save-excursion
+        (erase-buffer)
+        (insert ".")
+        (put-text-property (point-min) (point-max)
+                           'display (list 'xwidget :xwidget xwidget)))
+      (xwidget-put xwidget 'callback callback)
+      (set-xwidget-buffer xwidget buffer)
+      (xwidget-webkit-mode))
+    buffer))
+
+(defun xwidget-webkit-display-event (event)
+  "Import the xwidget inside EVENT and display it."
+  (interactive "e")
+  (display-buffer (xwidget-webkit-import-widget (nth 1 event))))
+
+(global-set-key [xwidget-display-event] 'xwidget-webkit-display-event)
 
 (defun xwidget-webkit-goto-url (url)
   "Goto URL with xwidget webkit."
@@ -684,6 +785,165 @@ xwidget-put
   (set-xwidget-plist xwidget
                      (plist-put (xwidget-plist xwidget) propname value)))
 
+(defvar xwidget-webkit-edit-mode-map (make-keymap))
+
+(define-key xwidget-webkit-edit-mode-map [backspace] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [tab] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [return] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-return] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-return] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-return] 'xwidget-webkit-pass-command-event)
+
+(define-minor-mode xwidget-webkit-edit-mode
+  "Minor mode for editing the content of WebKit buffers.
+
+This defines most self-inserting characters and some common
+keyboard shortcuts to `xwidget-webkit-pass-command-event', which
+will pass the key events corresponding to these characters to the
+WebKit widget."
+  :keymap xwidget-webkit-edit-mode-map)
+
+(substitute-key-definition 'self-insert-command
+                           'xwidget-webkit-pass-command-event
+                           xwidget-webkit-edit-mode-map
+                           global-map)
+
+(declare-function xwidget-webkit-search "xwidget.c")
+(declare-function xwidget-webkit-next-result "xwidget.c")
+(declare-function xwidget-webkit-previous-result "xwidget.c")
+(declare-function xwidget-webkit-finish-search "xwidget.c")
+
+(defvar-local xwidget-webkit-isearch--string ""
+  "The current search query.")
+(defvar-local xwidget-webkit-isearch--is-reverse nil
+  "Whether or not the current isearch should be reverse.")
+
+(defun xwidget-webkit-isearch--update (&optional only-message)
+  "Update the current buffer's WebKit widget's search query.
+If ONLY-MESSAGE is non-nil, the query will not be sent to the
+WebKit widget.  The query will be set to the contents of
+`xwidget-webkit-isearch--string'."
+  (unless only-message
+    (xwidget-webkit-search xwidget-webkit-isearch--string
+                           (xwidget-webkit-current-session)
+                           t xwidget-webkit-isearch--is-reverse t))
+  (message (concat (propertize "Search contents: " 'face 'minibuffer-prompt)
+                   xwidget-webkit-isearch--string)))
+
+(defun xwidget-webkit-isearch-erasing-char (count)
+  "Erase the last COUNT characters of the current query."
+  (interactive (list (prefix-numeric-value current-prefix-arg)))
+  (when (> (length xwidget-webkit-isearch--string) 0)
+    (setq xwidget-webkit-isearch--string
+          (substring xwidget-webkit-isearch--string 0
+                     (- (length xwidget-webkit-isearch--string) count))))
+  (xwidget-webkit-isearch--update))
+
+(defun xwidget-webkit-isearch-printing-char (char &optional count)
+  "Add ordinary character CHAR to the search string and search.
+With argument, add COUNT copies of CHAR."
+  (interactive (list last-command-event
+                     (prefix-numeric-value current-prefix-arg)))
+  (setq xwidget-webkit-isearch--string (concat xwidget-webkit-isearch--string
+                                               (make-string (or count 1) char)))
+  (xwidget-webkit-isearch--update))
+
+(defun xwidget-webkit-isearch-forward (count)
+  "Move to the next search result COUNT times."
+  (interactive (list (prefix-numeric-value current-prefix-arg)))
+  (let ((was-reverse xwidget-webkit-isearch--is-reverse))
+    (setq xwidget-webkit-isearch--is-reverse nil)
+    (when was-reverse
+      (xwidget-webkit-isearch--update)))
+  (let ((i 0))
+    (while (< i count)
+      (xwidget-webkit-next-result (xwidget-webkit-current-session))
+      (cl-incf i)))
+  (xwidget-webkit-isearch--update t))
+
+(defun xwidget-webkit-isearch-backward (count)
+  "Move to the previous search result COUNT times."
+  (interactive (list (prefix-numeric-value current-prefix-arg)))
+  (let ((was-reverse xwidget-webkit-isearch--is-reverse))
+    (setq xwidget-webkit-isearch--is-reverse t)
+    (unless was-reverse
+      (xwidget-webkit-isearch--update)))
+  (let ((i 0))
+    (while (< i count)
+      (xwidget-webkit-next-result (xwidget-webkit-current-session))
+      (cl-incf i)))
+  (xwidget-webkit-isearch--update t))
+
+(defun xwidget-webkit-isearch-exit ()
+  "Exit incremental search of a WebKit buffer."
+  (interactive)
+  (xwidget-webkit-isearch-mode 0))
+
+(defvar xwidget-webkit-isearch-mode-map (make-keymap)
+  "The keymap used inside xwidget-webkit-isearch-mode.")
+
+(set-char-table-range (nth 1 xwidget-webkit-isearch-mode-map)
+                      (cons 0 (max-char))
+                      'xwidget-webkit-isearch-exit)
+
+(substitute-key-definition 'self-insert-command
+                           'xwidget-webkit-isearch-printing-char
+                           xwidget-webkit-isearch-mode-map
+                           global-map)
+
+(define-key xwidget-webkit-isearch-mode-map (kbd "DEL")
+  'xwidget-webkit-isearch-erasing-char)
+(define-key xwidget-webkit-isearch-mode-map [return] 'xwidget-webkit-isearch-exit)
+(define-key xwidget-webkit-isearch-mode-map "\r" 'xwidget-webkit-isearch-exit)
+(define-key xwidget-webkit-isearch-mode-map "\C-g" 'xwidget-webkit-isearch-exit)
+(define-key xwidget-webkit-isearch-mode-map "\C-r" 'xwidget-webkit-isearch-backward)
+(define-key xwidget-webkit-isearch-mode-map "\C-s" 'xwidget-webkit-isearch-forward)
+(define-key xwidget-webkit-isearch-mode-map "\t" 'xwidget-webkit-isearch-printing-char)
+
+(let ((meta-map (make-keymap)))
+  (set-char-table-range (nth 1 meta-map)
+                        (cons 0 (max-char))
+                        'xwidget-webkit-isearch-exit)
+  (define-key xwidget-webkit-isearch-mode-map (char-to-string meta-prefix-char) meta-map))
+
+(define-minor-mode xwidget-webkit-isearch-mode
+  "Minor mode for performing incremental search inside WebKit buffers.
+
+An attempt was made for this to resemble regular incremental
+search, but it suffers from several limitations, such as not
+supporting recursive edits.
+
+If this mode is enabled with `C-r', then the search will default
+to being performed in reverse direction.
+
+To navigate around the search results, type
+\\[xwidget-webkit-isearch-forward] to move forward, and
+\\[xwidget-webkit-isearch-backward] to move backward.
+
+Press \\[xwidget-webkit-isearch-exit] to exit incremental search."
+  :keymap xwidget-webkit-isearch-mode-map
+  (if xwidget-webkit-isearch-mode
+      (progn
+        (setq xwidget-webkit-isearch--string "")
+        (setq xwidget-webkit-isearch--is-reverse (eq last-command-event ?\C-r))
+        (xwidget-webkit-isearch--update))
+    (xwidget-webkit-finish-search (xwidget-webkit-current-session))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
diff --git a/src/dispextern.h b/src/dispextern.h
index 5b28fe7666..f17f095e0d 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -536,8 +536,8 @@ #define FACE_ID_BITS	20
     int img_id;
 
 #ifdef HAVE_XWIDGETS
-    /* Xwidget reference (type == XWIDGET_GLYPH).  */
-    struct xwidget *xwidget;
+    /* Xwidget ID.  */
+    uint32_t xwidget;
 #endif
 
     /* Sub-structure for type == STRETCH_GLYPH.  */
diff --git a/src/dispnew.c b/src/dispnew.c
index 4a73244c89..632eec2f03 100644
--- a/src/dispnew.c
+++ b/src/dispnew.c
@@ -4449,16 +4449,6 @@ scrolling_window (struct window *w, int tab_line_p)
 	break;
     }
 
-#ifdef HAVE_XWIDGETS
-  /* Currently this seems needed to detect xwidget movement reliably.
-     This is most probably because an xwidget glyph is represented in
-     struct glyph's 'union u' by a pointer to a struct, which takes 8
-     bytes in 64-bit builds, and thus the comparison of u.val values
-     done by GLYPH_EQUAL_P doesn't work reliably, since it assumes the
-     size of the union is 4 bytes.  FIXME.  */
-    return 0;
-#endif
-
   /* Can't scroll the display of w32 GUI frames when position of point
      is indicated by the system caret, because scrolling the display
      will then "copy" the pixels used by the caret.  */
diff --git a/src/keyboard.c b/src/keyboard.c
index aa6a4b9e97..c4a5671b10 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -3993,6 +3993,7 @@ kbd_buffer_get_event (KBOARD **kbp,
 #endif
 #ifdef HAVE_XWIDGETS
       case XWIDGET_EVENT:
+      case XWIDGET_DISPLAY_EVENT:
 #endif
       case SAVE_SESSION_EVENT:
       case NO_EVENT:
@@ -4897,7 +4898,7 @@ #define FUNCTION_KEY_OFFSET 0xff00
 
 /* You'll notice that this table is arranged to be conveniently
    indexed by X Windows keysym values.  */
-static const char *const lispy_function_keys[] =
+const char *const lispy_function_keys[] =
   {
     /* X Keysym value */
 
@@ -6139,6 +6140,11 @@ make_lispy_event (struct input_event *event)
       {
         return Fcons (Qxwidget_event, event->arg);
       }
+
+    case XWIDGET_DISPLAY_EVENT:
+      {
+	return list2 (Qxwidget_display_event, event->arg);
+      }
 #endif
 
 #ifdef USE_FILE_NOTIFY
@@ -11732,6 +11738,7 @@ syms_of_keyboard (void)
 
 #ifdef HAVE_XWIDGETS
   DEFSYM (Qxwidget_event, "xwidget-event");
+  DEFSYM (Qxwidget_display_event, "xwidget-display-event");
 #endif
 
 #ifdef USE_FILE_NOTIFY
diff --git a/src/keyboard.h b/src/keyboard.h
index 8bdffaa2bf..21c51ec386 100644
--- a/src/keyboard.h
+++ b/src/keyboard.h
@@ -491,7 +491,7 @@ kbd_buffer_store_event_hold (struct input_event *event,
 extern struct timespec timer_check (void);
 extern void mark_kboards (void);
 
-#ifdef HAVE_NTGUI
+#if defined HAVE_NTGUI || defined HAVE_X_WINDOWS
 extern const char *const lispy_function_keys[];
 #endif
 
diff --git a/src/print.c b/src/print.c
index c13294c8e6..adadb289de 100644
--- a/src/print.c
+++ b/src/print.c
@@ -1521,8 +1521,26 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
       printchar ('>', printcharfun);
       break;
 
-    case PVEC_XWIDGET: case PVEC_XWIDGET_VIEW:
-      print_c_string ("#<xwidget ", printcharfun);
+    case PVEC_XWIDGET:
+#ifdef HAVE_XWIDGETS
+      {
+#ifdef USE_GTK
+	int len = sprintf (buf, "#<xwidget %u %p>",
+			   XXWIDGET (obj)->xwidget_id,
+			   XXWIDGET (obj)->widget_osr);
+#else
+	int len = sprintf (buf, "#<xwidget %u %p>",
+			   XXWIDGET (obj)->xwidget_id,
+			   XXWIDGET (obj)->xwWidget);
+#endif
+	strout (buf, len, len, printcharfun);
+	break;
+      }
+#else
+      emacs_abort ();
+#endif
+    case PVEC_XWIDGET_VIEW:
+      print_c_string ("#<xwidget view", printcharfun);
       printchar ('>', printcharfun);
       break;
 
diff --git a/src/termhooks.h b/src/termhooks.h
index 1d3cdc8fe8..e7539bbce2 100644
--- a/src/termhooks.h
+++ b/src/termhooks.h
@@ -255,6 +255,8 @@ #define EMACS_TERMHOOKS_H
 #ifdef HAVE_XWIDGETS
   /* events generated by xwidgets*/
    , XWIDGET_EVENT
+   /* Event generated when WebKit asks us to display another widget.  */
+   , XWIDGET_DISPLAY_EVENT
 #endif
 
 #ifdef USE_FILE_NOTIFY
diff --git a/src/xdisp.c b/src/xdisp.c
index 86c4e704d5..d7ad548917 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -28429,7 +28429,7 @@ fill_xwidget_glyph_string (struct glyph_string *s)
     }
   s->width = s->first_glyph->pixel_width;
   s->ybase += s->first_glyph->voffset;
-  s->xwidget = s->first_glyph->u.xwidget;
+  s->xwidget = xwidget_from_id (s->first_glyph->u.xwidget);
 }
 #endif
 /* Fill glyph string S from a sequence of stretch glyphs.
@@ -29832,7 +29832,7 @@ produce_xwidget_glyph (struct it *it)
           glyph->padding_p = 0;
 	  glyph->glyph_not_available_p = 0;
 	  glyph->face_id = it->face_id;
-          glyph->u.xwidget = it->xwidget;
+          glyph->u.xwidget = it->xwidget->xwidget_id;
 	  glyph->font_type = FONT_TYPE_UNKNOWN;
 	  if (it->bidi_p)
 	    {
diff --git a/src/xterm.c b/src/xterm.c
index aa1a1a5eed..9b434bffcc 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -4390,6 +4390,86 @@ x_scroll_run (struct window *w, struct run *run)
   /* Cursor off.  Will be switched on again in gui_update_window_end.  */
   gui_clear_cursor (w);
 
+#ifdef HAVE_XWIDGETS
+  /* "Copy" xwidget windows in the area that will be scrolled.  */
+  Display *dpy = FRAME_X_DISPLAY (f);
+  Window window = FRAME_X_WINDOW (f);
+
+  Window root, parent, *children;
+  unsigned int nchildren;
+
+  if (XQueryTree (dpy, window, &root, &parent, &children, &nchildren))
+    {
+      /* Now find xwidget views situated between from_y and to_y, and
+	 attached to w.  */
+      for (unsigned int i = 0; i < nchildren; ++i)
+	{
+	  Window child = children[i];
+	  struct xwidget_view *view = xwidget_view_from_window (child);
+
+	  if (view)
+	    {
+	      int window_y = view->y + view->clip_top;
+	      int window_height = view->clip_bottom - view->clip_top;
+
+	      Emacs_Rectangle r1, r2, result;
+	      r1.x = w->pixel_left;
+	      r1.y = from_y;
+	      r1.width = w->pixel_width;
+	      r1.height = height;
+	      r2 = r1;
+	      r2.y = window_y;
+	      r2.height = window_height;
+
+	      /* The window is offscreen, just unmap it.  */
+	      if (window_height == 0)
+		{
+		  view->hidden = true;
+		  XUnmapWindow (dpy, child);
+		  continue;
+		}
+
+	      bool intersects_p =
+		gui_intersect_rectangles (&r1, &r2, &result);
+
+	      if (XWINDOW (view->w) == w && intersects_p)
+		{
+		  int y = view->y + (to_y - from_y);
+		  int text_area_x, text_area_y, text_area_width, text_area_height;
+		  int clip_top, clip_bottom;
+
+		  window_box (w, TEXT_AREA, &text_area_x, &text_area_y,
+			      &text_area_width, &text_area_height);
+
+		  clip_top = max (0, text_area_y - y);
+		  clip_bottom = max (clip_top,
+				     min (XXWIDGET (view->model)->height,
+					  text_area_y + text_area_height - y));
+
+		  view->y = y;
+		  view->clip_top = clip_top;
+		  view->clip_bottom = clip_bottom;
+
+		  /* This means the view has moved offscreen.  Unmap
+		     it and hide it here.  */
+		  if ((view->clip_top - view->clip_bottom) <= 0)
+		    {
+		      view->hidden = true;
+		      XUnmapWindow (dpy, child);
+		    }
+		  else
+		    XMoveResizeWindow (dpy, child, view->x + view->clip_left,
+				       view->y + view->clip_top,
+				       view->clip_right - view->clip_left,
+				       view->clip_top - view->clip_bottom);
+		  XFlush (dpy);
+		}
+            }
+	}
+      XFree (children);
+    }
+#endif
+
 #ifdef USE_CAIRO
   if (FRAME_CR_CONTEXT (f))
     {
@@ -4563,8 +4643,9 @@ x_focus_changed (int type, int state, struct x_display_info *dpyinfo, struct fra
     }
 }
 
-/* Return the Emacs frame-object corresponding to an X window.
-   It could be the frame's main window or an icon window.  */
+/* Return the Emacs frame-object corresponding to an X window.  It
+   could be the frame's main window, an icon window, or an xwidget
+   window.  */
 
 static struct frame *
 x_window_to_frame (struct x_display_info *dpyinfo, int wdesc)
@@ -4575,6 +4656,13 @@ x_window_to_frame (struct x_display_info *dpyinfo, int wdesc)
   if (wdesc == None)
     return NULL;
 
+#ifdef HAVE_XWIDGETS
+  struct xwidget_view *xvw = xwidget_view_from_window (wdesc);
+
+  if (xvw && xvw->frame)
+    return xvw->frame;
+#endif
+
   FOR_EACH_FRAME (tail, frame)
     {
       f = XFRAME (frame);
@@ -4997,7 +5085,7 @@ x_x_to_emacs_modifiers (struct x_display_info *dpyinfo, int state)
             | ((state & dpyinfo->hyper_mod_mask)	? mod_hyper	: 0));
 }
 
-static int
+int
 x_emacs_to_x_modifiers (struct x_display_info *dpyinfo, intmax_t state)
 {
   EMACS_INT mod_ctrl = ctrl_modifier;
@@ -8211,6 +8299,18 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 
     case Expose:
       f = x_window_to_frame (dpyinfo, event->xexpose.window);
+#ifdef HAVE_XWIDGETS
+      {
+	struct xwidget_view *xv =
+	  xwidget_view_from_window (event->xexpose.window);
+
+	if (xv)
+	  {
+	    xwidget_expose (xv);
+	    goto OTHER;
+	  }
+      }
+#endif
       if (f)
         {
           if (!FRAME_VISIBLE_P (f))
@@ -8791,6 +8891,31 @@ handle_one_xevent (struct x_display_info *dpyinfo,
       x_display_set_last_user_time (dpyinfo, event->xcrossing.time);
       x_detect_focus_change (dpyinfo, any, event, &inev.ie);
 
+#ifdef HAVE_XWIDGETS
+      {
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xcrossing.window);
+	Mouse_HLInfo *hlinfo;
+
+	if (xvw)
+	  {
+	    xwidget_motion_or_crossing (xvw, event);
+	    hlinfo = MOUSE_HL_INFO (xvw->frame);
+
+	    if (xvw->frame == hlinfo->mouse_face_mouse_frame)
+	      {
+		clear_mouse_face (hlinfo);
+		hlinfo->mouse_face_mouse_frame = 0;
+	      }
+
+	    if (any_help_event_p)
+	      {
+		do_help = -1;
+	      }
+	    goto OTHER;
+	  }
+      }
+#endif
+
       f = any;
 
       if (f && x_mouse_click_focus_ignore_position)
@@ -8834,6 +8959,17 @@ handle_one_xevent (struct x_display_info *dpyinfo,
       goto OTHER;
 
     case LeaveNotify:
+#ifdef HAVE_XWIDGETS
+      {
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xcrossing.window);
+
+	if (xvw)
+	  {
+	    xwidget_motion_or_crossing (xvw, event);
+	    goto OTHER;
+	  }
+      }
+#endif
       x_display_set_last_user_time (dpyinfo, event->xcrossing.time);
       x_detect_focus_change (dpyinfo, any, event, &inev.ie);
 
@@ -8883,6 +9019,12 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 #ifdef USE_GTK
         if (f && xg_event_is_for_scrollbar (f, event))
           f = 0;
+#endif
+#ifdef HAVE_XWIDGETS
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xmotion.window);
+
+	if (xvw)
+	  xwidget_motion_or_crossing (xvw, event);
 #endif
         if (f)
           {
@@ -9138,6 +9280,24 @@ handle_one_xevent (struct x_display_info *dpyinfo,
     case ButtonRelease:
     case ButtonPress:
       {
+#ifdef HAVE_XWIDGETS
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xmotion.window);
+
+	if (xvw)
+	  {
+	    xwidget_button (xvw, event->type == ButtonPress,
+			    event->xbutton.x, event->xbutton.y,
+			    event->xbutton.button, event->xbutton.state,
+			    event->xbutton.time);
+
+	    if (!EQ (selected_window, xvw->w))
+	      {
+		inev.ie.kind = SELECT_WINDOW_EVENT;
+		inev.ie.frame_or_window = xvw->w;
+	      }
+	    goto OTHER;
+	  }
+#endif
         /* If we decide we want to generate an event to be seen
            by the rest of Emacs, we put it here.  */
         Lisp_Object tab_bar_arg = Qnil;
@@ -12108,6 +12268,10 @@ x_free_frame_resources (struct frame *f)
 	xfree (f->shell_position);
 #else  /* !USE_X_TOOLKIT */
 
+#ifdef HAVE_XWIDGETS
+      kill_frame_xwidget_views (f);
+#endif
+
 #ifdef USE_GTK
       xg_free_frame_widgets (f);
 #endif /* USE_GTK */
diff --git a/src/xterm.h b/src/xterm.h
index de6ea50385..9d9534dd62 100644
--- a/src/xterm.h
+++ b/src/xterm.h
@@ -1108,6 +1108,7 @@ #define SELECTION_EVENT_TIME(eventp)	\
 extern int x_dispatch_event (XEvent *, Display *);
 #endif
 extern int x_x_to_emacs_modifiers (struct x_display_info *, int);
+extern int x_emacs_to_x_modifiers (struct x_display_info *, intmax_t);
 #ifdef USE_CAIRO
 extern void x_cr_destroy_frame_context (struct frame *);
 extern void x_cr_update_surface_desired_size (struct frame *, int, int);
diff --git a/src/xwidget.c b/src/xwidget.c
index e4b42e6e0c..78a3860490 100644
--- a/src/xwidget.c
+++ b/src/xwidget.c
@@ -19,6 +19,7 @@ Copyright (C) 2011-2021 Free Software Foundation, Inc.
 
 #include <config.h>
 
+#include "buffer.h"
 #include "xwidget.h"
 
 #include "lisp.h"
@@ -35,10 +36,22 @@ Copyright (C) 2011-2021 Free Software Foundation, Inc.
 #ifdef USE_GTK
 #include <webkit2/webkit2.h>
 #include <JavaScriptCore/JavaScript.h>
+#include <cairo.h>
+#include <X11/Xlib.h>
 #elif defined NS_IMPL_COCOA
 #include "nsxwidget.h"
 #endif
 
+static Lisp_Object id_to_xwidget_map;
+static uint32_t xwidget_counter = 0;
+
+#ifdef USE_GTK
+static Lisp_Object x_window_to_xwv_map;
+static gboolean offscreen_damage_event (GtkWidget *, GdkEvent *, gpointer);
+static void synthesize_focus_in_event (GtkWidget *);
+static GdkDevice *find_suitable_keyboard (struct frame *);
+#endif
+
 static struct xwidget *
 allocate_xwidget (void)
 {
@@ -64,18 +77,32 @@ #define XSETXWIDGET_VIEW(a, b) XSETPSEUDOVECTOR (a, b, PVEC_XWIDGET_VIEW)
                                            GAsyncResult *,
                                            gpointer);
 static gboolean webkit_download_cb (WebKitWebContext *, WebKitDownload *, gpointer);
-
+static GtkWidget *webkit_create_cb (WebKitWebView *, WebKitNavigationAction *, gpointer);
 static gboolean
 webkit_decide_policy_cb (WebKitWebView *,
                          WebKitPolicyDecision *,
                          WebKitPolicyDecisionType,
                          gpointer);
+static GtkWidget *find_widget_at_pos (GtkWidget *, int, int, int *, int *);
+
+struct widget_search_data
+{
+  int x;
+  int y;
+  bool foundp;
+  bool first;
+  GtkWidget *data;
+};
+
+static void find_widget (GtkWidget *t, struct widget_search_data *);
+static void mouse_target_changed (WebKitWebView *, WebKitHitTestResult *, guint,
+				  gpointer);
 #endif
 
 
 DEFUN ("make-xwidget",
        Fmake_xwidget, Smake_xwidget,
-       5, 6, 0,
+       5, 7, 0,
        doc: /* Make an xwidget of TYPE.
 If BUFFER is nil, use the current buffer.
 If BUFFER is a string and no such buffer exists, create it.
@@ -83,10 +110,13 @@ DEFUN ("make-xwidget",
 
 - webkit
 
-Returns the newly constructed xwidget, or nil if construction fails.  */)
+RELATED is nil, or an xwidget.  When constructing a WebKit widget, it
+will share the same settings and internal subprocess as RELATED.
+Returns the newly constructed xwidget, or nil if construction
+fails.  */)
   (Lisp_Object type,
    Lisp_Object title, Lisp_Object width, Lisp_Object height,
-   Lisp_Object arguments, Lisp_Object buffer)
+   Lisp_Object arguments, Lisp_Object buffer, Lisp_Object related)
 {
 #ifdef USE_GTK
   if (!xg_gtk_initialized)
@@ -108,13 +138,19 @@ DEFUN ("make-xwidget",
   XSETXWIDGET (val, xw);
   Vxwidget_list = Fcons (val, Vxwidget_list);
   xw->plist = Qnil;
+  xw->xwidget_id = ++xwidget_counter;
+  xw->find_text = NULL;
+
+  Fputhash (make_fixnum (xw->xwidget_id), val, id_to_xwidget_map);
 
 #ifdef USE_GTK
   xw->widgetwindow_osr = NULL;
   xw->widget_osr = NULL;
+  xw->hit_result = 0;
   if (EQ (xw->type, Qwebkit))
     {
       block_input ();
+      WebKitSettings *settings;
       WebKitWebContext *webkit_context = webkit_web_context_get_default ();
 
 # if WEBKIT_CHECK_VERSION (2, 26, 0)
@@ -128,18 +164,34 @@ DEFUN ("make-xwidget",
 
       if (EQ (xw->type, Qwebkit))
         {
-          xw->widget_osr = webkit_web_view_new ();
-
-          /* webkitgtk uses GSubprocess which sets sigaction causing
-             Emacs to not catch SIGCHLD with its usual handle setup in
-             catch_child_signal().  This resets the SIGCHLD
-             sigaction.  */
-          struct sigaction old_action;
-          sigaction (SIGCHLD, NULL, &old_action);
-          webkit_web_view_load_uri(WEBKIT_WEB_VIEW (xw->widget_osr),
-                                   "about:blank");
-          sigaction (SIGCHLD, &old_action, NULL);
-        }
+	  WebKitWebView *related_view;
+
+	  if (NILP (related)
+	      || !XWIDGETP (related)
+	      || !EQ (XXWIDGET (related)->type, Qwebkit))
+	    {
+	      xw->widget_osr = webkit_web_view_new ();
+
+	      /* webkitgtk uses GSubprocess which sets sigaction causing
+		 Emacs to not catch SIGCHLD with its usual handle setup in
+		 catch_child_signal().  This resets the SIGCHLD
+		 sigaction.  */
+	      struct sigaction old_action;
+	      sigaction (SIGCHLD, NULL, &old_action);
+	      webkit_web_view_load_uri (WEBKIT_WEB_VIEW (xw->widget_osr),
+					"about:blank");
+	      sigaction (SIGCHLD, &old_action, NULL);
+	    }
+	  else
+	    {
+	      related_view = WEBKIT_WEB_VIEW (XXWIDGET (related)->widget_osr);
+	      xw->widget_osr = webkit_web_view_new_with_related_view (related_view);
+	    }
+
+	  /* Enable the developer extras */
+	  settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (xw->widget_osr));
+	  g_object_set (G_OBJECT (settings), "enable-developer-extras", TRUE, NULL);
+	}
 
       gtk_widget_set_size_request (GTK_WIDGET (xw->widget_osr), xw->width,
                                    xw->height);
@@ -157,6 +209,7 @@ DEFUN ("make-xwidget",
 
       gtk_widget_show (xw->widget_osr);
       gtk_widget_show (xw->widgetwindow_osr);
+      synthesize_focus_in_event (xw->widgetwindow_osr);
 
       /* Store some xwidget data in the gtk widgets for convenient
          retrieval in the event handlers.  */
@@ -179,8 +232,20 @@ DEFUN ("make-xwidget",
                             G_CALLBACK
                             (webkit_decide_policy_cb),
                             xw);
+
+	  g_signal_connect (G_OBJECT (xw->widget_osr),
+			    "mouse-target-changed",
+			    G_CALLBACK (mouse_target_changed),
+			    xw);
+	  g_signal_connect (G_OBJECT (xw->widget_osr),
+			    "create",
+			    G_CALLBACK (webkit_create_cb),
+			    xw);
         }
 
+      g_signal_connect (G_OBJECT (xw->widgetwindow_osr), "damage-event",
+			G_CALLBACK (offscreen_damage_event), xw);
+
       unblock_input ();
     }
 #elif defined NS_IMPL_COCOA
@@ -190,6 +255,158 @@ DEFUN ("make-xwidget",
   return val;
 }
 
+#ifdef USE_GTK
+static void
+set_widget_if_text_view (GtkWidget *widget, void *data)
+{
+  GtkWidget **pointer = data;
+
+  if (GTK_IS_TEXT_VIEW (widget))
+    {
+      *pointer = widget;
+    }
+}
+#endif
+
+DEFUN ("xwidget-perform-lispy-event",
+       Fxwidget_perform_lispy_event, Sxwidget_perform_lispy_event,
+       2, 3, 0, doc: /* Send a lispy event to XWIDGET.
+EVENT should be the event that will be sent.  FRAME should be the
+frame which generated the event, or nil.  On X11, modifier keys will
+not be processed if FRAME is nil and the selected frame is not an
+X-Windows frame.  */)
+  (Lisp_Object xwidget, Lisp_Object event, Lisp_Object frame)
+{
+  struct xwidget *xw;
+  struct frame *f = NULL;
+  int character = -1, keycode = -1;
+  int modifiers = 0;
+
+#ifdef USE_GTK
+  GdkEvent *xg_event;
+  GtkContainerClass *klass;
+  GtkWidget *widget;
+  GtkWidget *temp = NULL;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!NILP (frame))
+    f = decode_window_system_frame (frame);
+  else if (FRAME_X_P (SELECTED_FRAME ()))
+    f = SELECTED_FRAME ();
+
+#ifdef USE_GTK
+  widget = gtk_window_get_focus (GTK_WINDOW (xw->widgetwindow_osr));
+
+  if (!widget)
+    widget = xw->widget_osr;
+
+  if (RANGED_FIXNUMP (0, event, INT_MAX))
+    {
+      character = XFIXNUM (event);
+
+      if (character < 32)
+	modifiers |= ctrl_modifier;
+
+      modifiers |= character & meta_modifier;
+      modifiers |= character & hyper_modifier;
+      modifiers |= character & super_modifier;
+      modifiers |= character & shift_modifier;
+      modifiers |= character & ctrl_modifier;
+
+      character = character & ~(1 << 21);
+
+      if (character < 32)
+	character += '_';
+
+      if (f)
+	modifiers = x_emacs_to_x_modifiers (FRAME_DISPLAY_INFO (f), modifiers);
+      else
+	modifiers = 0;
+    }
+  else if (SYMBOLP (event))
+    {
+      Lisp_Object decoded = parse_modifiers (event);
+      Lisp_Object decoded_name = SYMBOL_NAME (XCAR (decoded));
+
+      int off = 0;
+      bool found = false;
+
+      while (off < 256)
+	{
+	  if (lispy_function_keys[off]
+	      && !strcmp (lispy_function_keys[off],
+			  SSDATA (decoded_name)))
+	    {
+	      found = true;
+	      break;
+	    }
+	  ++off;
+	}
+
+      if (f)
+	modifiers = x_emacs_to_x_modifiers (FRAME_DISPLAY_INFO (f),
+					    XFIXNUM (XCAR (XCDR (decoded))));
+      else
+	modifiers = 0;
+
+      if (found)
+	keycode = off + 0xff00;
+    }
+
+  if (character == -1 && keycode == -1)
+    return Qnil;
+
+  block_input ();
+  xg_event = gdk_event_new (GDK_KEY_PRESS);
+  xg_event->any.window = gtk_widget_get_window (xw->widget_osr);
+  g_object_ref (xg_event->any.window);
+
+  if (character > -1)
+    keycode = gdk_unicode_to_keyval (character);
+
+  xg_event->key.keyval = keycode;
+  xg_event->key.state = modifiers;
+
+  if (keycode > -1)
+    {
+      /* WebKitGTK internals abuse follows.  */
+      if (WEBKIT_IS_WEB_VIEW (widget))
+	{
+	  /* WebKitGTK relies on an internal GtkTextView object to
+	     "translate" keys such as backspace.  We must find that
+	     widget and activate its binding to this key if any.  */
+	  klass = GTK_CONTAINER_CLASS (G_OBJECT_GET_CLASS (widget));
+
+	  klass->forall (GTK_CONTAINER (xw->widget_osr), TRUE,
+			 set_widget_if_text_view, &temp);
+
+	  if (GTK_IS_WIDGET (temp))
+	    {
+	      if (!gtk_widget_get_realized (temp))
+		gtk_widget_realize (temp);
+
+	      gtk_bindings_activate (G_OBJECT (temp), keycode, modifiers);
+	    }
+	}
+    }
+
+  if (f)
+    gdk_event_set_device (xg_event,
+			  find_suitable_keyboard (SELECTED_FRAME ()));
+
+  gtk_main_do_event (xg_event);
+  xg_event->type = GDK_KEY_RELEASE;
+  gtk_main_do_event (xg_event);
+  gdk_event_free (xg_event);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
 DEFUN ("get-buffer-xwidgets", Fget_buffer_xwidgets, Sget_buffer_xwidgets,
        1, 1, 0,
        doc: /* Return a list of xwidgets associated with BUFFER.
@@ -221,16 +438,397 @@ xwidget_hidden (struct xwidget_view *xv)
   return xv->hidden;
 }
 
+struct xwidget *
+xwidget_from_id (uint32_t id)
+{
+  Lisp_Object key = make_fixnum (id);
+  Lisp_Object xwidget = Fgethash (key, id_to_xwidget_map, Qnil);
+
+  if (NILP (xwidget))
+    emacs_abort ();
+
+  return XXWIDGET (xwidget);
+}
+
 #ifdef USE_GTK
+
+static GdkDevice *
+find_suitable_pointer (struct frame *f)
+{
+  GdkSeat *seat = gdk_display_get_default_seat
+    (gtk_widget_get_display (FRAME_GTK_WIDGET (f)));
+
+  if (!seat)
+    return NULL;
+
+  return gdk_seat_get_pointer (seat);
+}
+
+static GdkDevice *
+find_suitable_keyboard (struct frame *f)
+{
+  GdkSeat *seat = gdk_display_get_default_seat
+    (gtk_widget_get_display (FRAME_GTK_WIDGET (f)));
+
+  if (!seat)
+    return NULL;
+
+  return gdk_seat_get_keyboard (seat);
+}
+
+static void
+find_widget_cb (GtkWidget *widget, void *user)
+{
+  find_widget (widget, user);
+}
+
+static void
+find_widget (GtkWidget *widget,
+	     struct widget_search_data *data)
+{
+  GtkAllocation new_allocation;
+  GdkWindow *window;
+  int x_offset = 0;
+  int y_offset = 0;
+
+  gtk_widget_get_allocation (widget, &new_allocation);
+
+  if (gtk_widget_get_has_window (widget))
+    {
+      new_allocation.x = 0;
+      new_allocation.y = 0;
+    }
+
+  if (gtk_widget_get_parent (widget) && !data->first)
+    {
+      window = gtk_widget_get_window (widget);
+      while (window != gtk_widget_get_window (gtk_widget_get_parent (widget)))
+        {
+          gint tx, ty, twidth, theight;
+
+	  if (!window)
+	    return;
+
+          twidth = gdk_window_get_width (window);
+          theight = gdk_window_get_height (window);
+
+          if (new_allocation.x < 0)
+            {
+              new_allocation.width += new_allocation.x;
+              new_allocation.x = 0;
+            }
+
+          if (new_allocation.y < 0)
+            {
+              new_allocation.height += new_allocation.y;
+              new_allocation.y = 0;
+            }
+
+          if (new_allocation.x + new_allocation.width > twidth)
+            new_allocation.width = twidth - new_allocation.x;
+          if (new_allocation.y + new_allocation.height > theight)
+            new_allocation.height = theight - new_allocation.y;
+
+          gdk_window_get_position (window, &tx, &ty);
+          new_allocation.x += tx;
+          x_offset += tx;
+          new_allocation.y += ty;
+          y_offset += ty;
+
+          window = gdk_window_get_parent (window);
+	}
+    }
+
+  if ((data->x >= new_allocation.x) && (data->y >= new_allocation.y) &&
+      (data->x < new_allocation.x + new_allocation.width) &&
+      (data->y < new_allocation.y + new_allocation.height))
+    {
+      /* First, check if the drag is in a valid drop site in
+       * one of our children
+       */
+      if (GTK_IS_CONTAINER (widget))
+        {
+          struct widget_search_data new_data = *data;
+
+          new_data.x -= x_offset;
+          new_data.y -= y_offset;
+          new_data.foundp = false;
+          new_data.first = false;
+
+          gtk_container_forall (GTK_CONTAINER (widget),
+                                find_widget_cb, &new_data);
+
+          data->foundp = new_data.foundp;
+          if (data->foundp)
+            data->data = new_data.data;
+        }
+
+      /* If not, and this widget is registered as a drop site, check to
+       * emit "drag_motion" to check if we are actually in
+       * a drop site.
+       */
+      if (!data->foundp)
+        {
+          data->foundp = true;
+          data->data = widget;
+        }
+    }
+}
+
+static GtkWidget *
+find_widget_at_pos (GtkWidget *w, int x, int y,
+		    int *new_x, int *new_y)
+{
+  struct widget_search_data data;
+
+  data.x = x;
+  data.y = y;
+  data.foundp = false;
+  data.first = true;
+
+  find_widget (w, &data);
+
+  if (data.foundp)
+    {
+      gtk_widget_translate_coordinates (w, data.data, x,
+					y, new_x, new_y);
+      return data.data;
+    }
+
+  *new_x = x;
+  *new_y = y;
+
+  return NULL;
+}
+
+static Emacs_Cursor
+cursor_for_hit (guint result, struct frame *frame)
+{
+  Emacs_Cursor cursor = FRAME_OUTPUT_DATA (frame)->nontext_cursor;
+
+  if ((result & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)
+      || (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION)
+      || (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT))
+    cursor = FRAME_X_OUTPUT (frame)->text_cursor;
+
+  if (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR)
+    cursor = FRAME_X_OUTPUT (frame)->vertical_drag_cursor;
+
+  if (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK)
+    cursor = FRAME_X_OUTPUT (frame)->hand_cursor;
+
+  return cursor;
+}
+
+static void
+define_cursors (struct xwidget *xw, WebKitHitTestResult *res)
+{
+  struct xwidget_view *xvw;
+
+  xw->hit_result = webkit_hit_test_result_get_context (res);
+
+  for (Lisp_Object tem = Vxwidget_view_list; CONSP (tem);
+       tem = XCDR (tem))
+    {
+      if (XWIDGET_VIEW_P (XCAR (tem)))
+	{
+	  xvw = XXWIDGET_VIEW (XCAR (tem));
+
+	  if (XXWIDGET (xvw->model) == xw)
+	    {
+	      xvw->cursor = cursor_for_hit (xw->hit_result, xvw->frame);
+	      if (xvw->wdesc != None)
+		XDefineCursor (xvw->dpy, xvw->wdesc, xvw->cursor);
+	    }
+	}
+    }
+}
+
+static void
+mouse_target_changed (WebKitWebView *webview,
+		      WebKitHitTestResult *hitresult,
+		      guint modifiers, gpointer xw)
+{
+  define_cursors (xw, hitresult);
+}
+
+
+static void
+xwidget_button_1 (struct xwidget_view *view,
+		  bool down_p, int x, int y, int button,
+		  int modifier_state, Time time)
+{
+  GdkEvent *xg_event = gdk_event_new (down_p ? GDK_BUTTON_PRESS : GDK_BUTTON_RELEASE);
+  struct xwidget *model = XXWIDGET (view->model);
+  GtkWidget *target;
+
+  /* X and Y should be relative to the origin of view->wdesc.  */
+  x += view->clip_left;
+  y += view->clip_top;
+
+  target = find_widget_at_pos (model->widgetwindow_osr, x, y, &x, &y);
+
+  if (!target)
+    target = model->widget_osr;
+
+  xg_event->any.window = gtk_widget_get_window (target);
+  g_object_ref (xg_event->any.window); /* The window will be unrefed
+					  later by gdk_event_free. */
+
+  xg_event->button.x = x;
+  xg_event->button.x_root = x;
+  xg_event->button.y = y;
+  xg_event->button.y_root = y;
+  xg_event->button.button = button;
+  xg_event->button.state = modifier_state;
+  xg_event->button.time = time;
+  xg_event->button.device = find_suitable_pointer (view->frame);
+
+  gtk_main_do_event (xg_event);
+  gdk_event_free (xg_event);
+}
+
+void
+xwidget_button (struct xwidget_view *view,
+		bool down_p, int x, int y, int button,
+		int modifier_state, Time time)
+{
+  if (button < 4 || button > 8)
+    xwidget_button_1 (view, down_p, x, y, button, modifier_state, time);
+  else
+    {
+      GdkEvent *xg_event = gdk_event_new (GDK_SCROLL);
+      struct xwidget *model = XXWIDGET (view->model);
+      GtkWidget *target;
+
+      x += view->clip_left;
+      y += view->clip_top;
+
+      target = find_widget_at_pos (model->widgetwindow_osr, x, y, &x, &y);
+
+      if (!target)
+	target = model->widget_osr;
+
+      xg_event->any.window = gtk_widget_get_window (target);
+      g_object_ref (xg_event->any.window); /* The window will be unrefed
+					      later by gdk_event_free. */
+      if (button == 4)
+	xg_event->scroll.direction = GDK_SCROLL_UP;
+      else if (button == 5)
+	xg_event->scroll.direction = GDK_SCROLL_DOWN;
+      else if (button == 6)
+	xg_event->scroll.direction = GDK_SCROLL_LEFT;
+      else
+	xg_event->scroll.direction = GDK_SCROLL_RIGHT;
+
+      xg_event->scroll.device = find_suitable_pointer (view->frame);
+
+      xg_event->scroll.x = x;
+      xg_event->scroll.x_root = x;
+      xg_event->scroll.y = y;
+      xg_event->scroll.y_root = y;
+      xg_event->scroll.state = modifier_state;
+      xg_event->scroll.time = time;
+
+      xg_event->scroll.delta_x = 0;
+      xg_event->scroll.delta_y = 0;
+
+      gtk_main_do_event (xg_event);
+      gdk_event_free (xg_event);
+    }
+}
+
+void
+xwidget_motion_or_crossing (struct xwidget_view *view, const XEvent *event)
+{
+  GdkEvent *xg_event = gdk_event_new (event->type == MotionNotify ? GDK_MOTION_NOTIFY :
+				      (event->type == LeaveNotify ? GDK_LEAVE_NOTIFY :
+				       GDK_ENTER_NOTIFY));
+  struct xwidget *model = XXWIDGET (view->model);
+  int x;
+  int y;
+  GtkWidget *target = find_widget_at_pos (model->widgetwindow_osr,
+					  (event->type == MotionNotify
+					   ? event->xmotion.x + view->clip_left
+					   : event->xmotion.y + view->clip_top),
+					  (event->type == MotionNotify
+					   ? event->xmotion.y + view->clip_left
+					   : event->xcrossing.y + view->clip_top),
+					  &x, &y);
+
+  if (!target)
+    target = model->widgetwindow_osr;
+
+  xg_event->any.window = gtk_widget_get_window (target);
+  g_object_ref (xg_event->any.window); /* The window will be unrefed
+					  later by gdk_event_free. */
+
+  if (event->type == MotionNotify)
+    {
+      xg_event->motion.x = x;
+      xg_event->motion.y = y;
+      xg_event->motion.x_root = event->xmotion.x_root;
+      xg_event->motion.y_root = event->xmotion.y_root;
+      xg_event->motion.time = event->xmotion.time;
+      xg_event->motion.state = event->xmotion.state;
+      xg_event->motion.device = find_suitable_pointer (view->frame);
+    }
+  else
+    {
+      xg_event->crossing.detail = min (5, event->xcrossing.detail);
+      xg_event->crossing.time = event->xcrossing.time;
+      xg_event->crossing.x = x;
+      xg_event->crossing.y = y;
+      xg_event->crossing.x_root = event->xcrossing.x_root;
+      xg_event->crossing.y_root = event->xcrossing.y_root;
+      gdk_event_set_device (xg_event, find_suitable_pointer (view->frame));
+    }
+
+  gtk_main_do_event (xg_event);
+  gdk_event_free (xg_event);
+}
+
+static void
+synthesize_focus_in_event (GtkWidget *offscreen_window)
+{
+  GdkWindow *wnd;
+  GdkEvent *focus_event;
+
+  if (!gtk_widget_get_realized (offscreen_window))
+    gtk_widget_realize (offscreen_window);
+
+  wnd = gtk_widget_get_window (offscreen_window);
+
+  focus_event = gdk_event_new (GDK_FOCUS_CHANGE);
+  focus_event->any.window = wnd;
+  focus_event->focus_change.in = TRUE;
+  g_object_ref (wnd);
+
+  gtk_main_do_event (focus_event);
+  gdk_event_free (focus_event);
+}
+
+struct xwidget_view *
+xwidget_view_from_window (Window wdesc)
+{
+  Lisp_Object key = make_fixnum (wdesc);
+  Lisp_Object xwv = Fgethash (key, x_window_to_xwv_map, Qnil);
+
+  if (NILP (xwv))
+    return NULL;
+
+  return XXWIDGET_VIEW (xwv);
+}
+
 static void
 xwidget_show_view (struct xwidget_view *xv)
 {
   xv->hidden = false;
-  gtk_widget_show (xv->widgetwindow);
-  gtk_fixed_move (GTK_FIXED (xv->emacswindow),
-                  xv->widgetwindow,
-                  xv->x + xv->clip_left,
-                  xv->y + xv->clip_top);
+  XMoveWindow (xv->dpy, xv->wdesc,
+	       xv->x + xv->clip_left,
+	       xv->y + xv->clip_top);
+  XMapWindow (xv->dpy, xv->wdesc);
+  XFlush (xv->dpy);
 }
 
 /* Hide an xwidget view.  */
@@ -238,28 +836,64 @@ xwidget_show_view (struct xwidget_view *xv)
 xwidget_hide_view (struct xwidget_view *xv)
 {
   xv->hidden = true;
-  gtk_fixed_move (GTK_FIXED (xv->emacswindow), xv->widgetwindow,
-                  10000, 10000);
+  XUnmapWindow (xv->dpy, xv->wdesc);
+  XFlush (xv->dpy);
+}
+
+static void
+xv_do_draw (struct xwidget_view *xw, struct xwidget *w)
+{
+  GtkOffscreenWindow *wnd;
+  cairo_surface_t *surface;
+  block_input ();
+  wnd = GTK_OFFSCREEN_WINDOW (w->widgetwindow_osr);
+  surface = gtk_offscreen_window_get_surface (wnd);
+
+  cairo_save (xw->cr_context);
+  if (surface)
+    {
+      cairo_set_source_surface (xw->cr_context, surface, xw->clip_left,
+				xw->clip_top);
+      cairo_set_operator (xw->cr_context, CAIRO_OPERATOR_SOURCE);
+      cairo_paint (xw->cr_context);
+    }
+  cairo_restore (xw->cr_context);
+
+  unblock_input ();
 }
 
 /* When the off-screen webkit master view changes this signal is called.
    It copies the bitmap from the off-screen instance.  */
 static gboolean
 offscreen_damage_event (GtkWidget *widget, GdkEvent *event,
-                        gpointer xv_widget)
-{
-  /* Queue a redraw of onscreen widget.
-     There is a guard against receiving an invalid widget,
-     which should only happen if we failed to remove the
-     specific signal handler for the damage event.  */
-  if (GTK_IS_WIDGET (xv_widget))
-    gtk_widget_queue_draw (GTK_WIDGET (xv_widget));
-  else
-    message ("Warning, offscreen_damage_event received invalid xv pointer:%p\n",
-             xv_widget);
+                        gpointer xwidget)
+{
+  block_input ();
+
+  for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail);
+       tail = XCDR (tail))
+    {
+      if (XWIDGET_VIEW_P (XCAR (tail)))
+	{
+	  struct xwidget_view *view = XXWIDGET_VIEW (XCAR (tail));
+
+	  if (view->wdesc && XXWIDGET (view->model) == xwidget)
+	    xv_do_draw (view, XXWIDGET (view->model));
+	}
+    }
+
+  unblock_input ();
 
   return FALSE;
 }
+
+void
+xwidget_expose (struct xwidget_view *xv)
+{
+  struct xwidget *xw = XXWIDGET (xv->model);
+
+  xv_do_draw (xv, xw);
+}
 #endif /* USE_GTK */
 
 void
@@ -313,22 +947,108 @@ store_xwidget_js_callback_event (struct xwidget *xw,
 
 
 #ifdef USE_GTK
+static void
+store_xwidget_display_event (struct xwidget *xw)
+{
+  struct input_event evt;
+  Lisp_Object val;
+
+  XSETXWIDGET (val, xw);
+  EVENT_INIT (evt);
+  evt.kind = XWIDGET_DISPLAY_EVENT;
+  evt.frame_or_window = Qnil;
+  evt.arg = val;
+  kbd_buffer_store_event (&evt);
+}
+
+static void
+webkit_ready_to_show (WebKitWebView *new_view,
+		      gpointer user_data)
+{
+  Lisp_Object tem;
+  struct xwidget *xw;
+
+  for (tem = Vxwidget_list; CONSP (tem); tem = XCDR (tem))
+    {
+      if (XWIDGETP (XCAR (tem)))
+	{
+	  xw = XXWIDGET (XCAR (tem));
+
+	  if (EQ (xw->type, Qwebkit)
+	      && WEBKIT_WEB_VIEW (xw->widget_osr) == new_view)
+	    store_xwidget_display_event (xw);
+	}
+    }
+}
+
+static GtkWidget *
+webkit_create_cb_1 (WebKitWebView *webview,
+		    struct xwidget_view *xv)
+{
+  Lisp_Object related;
+  Lisp_Object xwidget;
+  GtkWidget *widget;
+
+  XSETXWIDGET (related, xv);
+  xwidget = Fmake_xwidget (Qwebkit, Qnil, make_fixnum (0),
+			   make_fixnum (0), Qnil,
+			   build_string (" *detached xwidget buffer*"),
+			   related);
+
+  if (NILP (xwidget))
+    return NULL;
+
+  widget = XXWIDGET (xwidget)->widget_osr;
+
+  g_signal_connect (G_OBJECT (widget), "ready-to-show",
+		    G_CALLBACK (webkit_ready_to_show), NULL);
+
+  return widget;
+}
+
+static GtkWidget *
+webkit_create_cb (WebKitWebView *webview,
+		  WebKitNavigationAction *nav_action,
+		  gpointer user_data)
+{
+  switch (webkit_navigation_action_get_navigation_type (nav_action))
+    {
+    case WEBKIT_NAVIGATION_TYPE_OTHER:
+      return webkit_create_cb_1 (webview, user_data);
+
+    case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD:
+    case WEBKIT_NAVIGATION_TYPE_RELOAD:
+    case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED:
+    case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
+    case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED:
+    default:
+      return NULL;
+    }
+}
+
 void
 webkit_view_load_changed_cb (WebKitWebView *webkitwebview,
                              WebKitLoadEvent load_event,
                              gpointer data)
 {
-  switch (load_event) {
-  case WEBKIT_LOAD_FINISHED:
+  struct xwidget *xw = g_object_get_data (G_OBJECT (webkitwebview),
+					  XG_XWIDGET);
+
+  switch (load_event)
     {
-      struct xwidget *xw = g_object_get_data (G_OBJECT (webkitwebview),
-                                              XG_XWIDGET);
-      store_xwidget_event_string (xw, "load-changed", "");
+    case WEBKIT_LOAD_FINISHED:
+      store_xwidget_event_string (xw, "load-changed", "load-finished");
+      break;
+    case WEBKIT_LOAD_STARTED:
+      store_xwidget_event_string (xw, "load-changed", "load-started");
+      break;
+    case WEBKIT_LOAD_REDIRECTED:
+      store_xwidget_event_string (xw, "load-changed", "load-redirected");
+      break;
+    case WEBKIT_LOAD_COMMITTED:
+      store_xwidget_event_string (xw, "load-changed", "load-committed");
       break;
     }
-  default:
-    break;
-  }
 }
 
 /* Recursively convert a JavaScript value to a Lisp value. */
@@ -498,51 +1218,6 @@ webkit_decide_policy_cb (WebKitWebView *webView,
     return FALSE;
   }
 }
-
-
-/* For gtk3 offscreen rendered widgets.  */
-static gboolean
-xwidget_osr_draw_cb (GtkWidget *widget, cairo_t *cr, gpointer data)
-{
-  struct xwidget *xw = g_object_get_data (G_OBJECT (widget), XG_XWIDGET);
-  struct xwidget_view *xv = g_object_get_data (G_OBJECT (widget),
-                                               XG_XWIDGET_VIEW);
-
-  cairo_rectangle (cr, 0, 0, xv->clip_right, xv->clip_bottom);
-  cairo_clip (cr);
-
-  gtk_widget_draw (xw->widget_osr, cr);
-  return FALSE;
-}
-
-static gboolean
-xwidget_osr_event_forward (GtkWidget *widget, GdkEvent *event,
-			   gpointer user_data)
-{
-  /* Copy events that arrive at the outer widget to the offscreen widget.  */
-  struct xwidget *xw = g_object_get_data (G_OBJECT (widget), XG_XWIDGET);
-  GdkEvent *eventcopy = gdk_event_copy (event);
-  eventcopy->any.window = gtk_widget_get_window (xw->widget_osr);
-
-  /* TODO: This might leak events.  They should be deallocated later,
-     perhaps in xwgir_event_cb.  */
-  gtk_main_do_event (eventcopy);
-
-  /* Don't propagate this event further.  */
-  return TRUE;
-}
-
-static gboolean
-xwidget_osr_event_set_embedder (GtkWidget *widget, GdkEvent *event,
-				gpointer data)
-{
-  struct xwidget_view *xv = data;
-  struct xwidget *xww = XXWIDGET (xv->model);
-  gdk_offscreen_window_set_embedder (gtk_widget_get_window
-				     (xww->widgetwindow_osr),
-                                     gtk_widget_get_window (xv->widget));
-  return FALSE;
-}
 #endif /* USE_GTK */
 
 
@@ -568,63 +1243,19 @@ xwidget_init_view (struct xwidget *xww,
   XSETXWIDGET (xv->model, xww);
 
 #ifdef USE_GTK
-  if (EQ (xww->type, Qwebkit))
-    {
-      xv->widget = gtk_drawing_area_new ();
-      /* Expose event handling.  */
-      gtk_widget_set_app_paintable (xv->widget, TRUE);
-      gtk_widget_add_events (xv->widget, GDK_ALL_EVENTS_MASK);
-
-      /* Draw the view on damage-event.  */
-      g_signal_connect (G_OBJECT (xww->widgetwindow_osr), "damage-event",
-                        G_CALLBACK (offscreen_damage_event), xv->widget);
+  xv->dpy = FRAME_X_DISPLAY (s->f);
 
-      if (EQ (xww->type, Qwebkit))
-        {
-          g_signal_connect (G_OBJECT (xv->widget), "button-press-event",
-                            G_CALLBACK (xwidget_osr_event_forward), NULL);
-          g_signal_connect (G_OBJECT (xv->widget), "button-release-event",
-                            G_CALLBACK (xwidget_osr_event_forward), NULL);
-          g_signal_connect (G_OBJECT (xv->widget), "motion-notify-event",
-                            G_CALLBACK (xwidget_osr_event_forward), NULL);
-        }
-      else
-        {
-          /* xwgir debug, orthogonal to forwarding.  */
-          g_signal_connect (G_OBJECT (xv->widget), "enter-notify-event",
-                            G_CALLBACK (xwidget_osr_event_set_embedder), xv);
-        }
-      g_signal_connect (G_OBJECT (xv->widget), "draw",
-                        G_CALLBACK (xwidget_osr_draw_cb), NULL);
-    }
-
-  /* Widget realization.
-
-     Make container widget first, and put the actual widget inside the
-     container later.  Drawing should crop container window if necessary
-     to handle case where xwidget is partially obscured by other Emacs
-     windows.  Other containers than gtk_fixed where explored, but
-     gtk_fixed had the most predictable behavior so far.  */
-
-  xv->emacswindow = FRAME_GTK_WIDGET (s->f);
-  xv->widgetwindow = gtk_fixed_new ();
-  gtk_widget_set_has_window (xv->widgetwindow, TRUE);
-  gtk_container_add (GTK_CONTAINER (xv->widgetwindow), xv->widget);
-
-  /* Store some xwidget data in the gtk widgets.  */
-  g_object_set_data (G_OBJECT (xv->widget), XG_FRAME_DATA, s->f);
-  g_object_set_data (G_OBJECT (xv->widget), XG_XWIDGET, xww);
-  g_object_set_data (G_OBJECT (xv->widget), XG_XWIDGET_VIEW, xv);
-  g_object_set_data (G_OBJECT (xv->widgetwindow), XG_XWIDGET, xww);
-  g_object_set_data (G_OBJECT (xv->widgetwindow), XG_XWIDGET_VIEW, xv);
-
-  gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xww->width,
-                               xww->height);
-  gtk_widget_set_size_request (xv->widgetwindow, xww->width, xww->height);
-  gtk_fixed_put (GTK_FIXED (FRAME_GTK_WIDGET (s->f)), xv->widgetwindow, x, y);
   xv->x = x;
   xv->y = y;
-  gtk_widget_show_all (xv->widgetwindow);
+
+  xv->clip_left = 0;
+  xv->clip_right = xww->width;
+  xv->clip_top = 0;
+  xv->clip_bottom = xww->height;
+
+  xv->wdesc = None;
+  xv->frame = s->f;
+  xv->cursor = cursor_for_hit (xww->hit_result, s->f);
 #elif defined NS_IMPL_COCOA
   nsxwidget_init_view (xv, xww, s, x, y);
   nsxwidget_resize_view(xv, xww->width, xww->height);
@@ -681,6 +1312,8 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
   window_box (s->w, TEXT_AREA, &text_area_x, &text_area_y,
               &text_area_width, &text_area_height);
 
+  /* On X11, this keeps generating expose events.  */
+#ifndef USE_GTK
   /* Resize xwidget webkit if its container window size is changed in
      some ways, for example, a buffer became hidden in small split
      window, then it can appear front in merged whole window.  */
@@ -693,6 +1326,7 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
                        make_int (text_area_width),
                        make_int (text_area_height));
     }
+#endif
 
   clip_left = max (0, text_area_x - x);
   clip_right = max (clip_left,
@@ -711,15 +1345,51 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
      later.  */
   bool moved = (xv->x + xv->clip_left != x + clip_left
 		|| xv->y + xv->clip_top != y + clip_top);
+
+#ifdef USE_GTK
+  bool wdesc_was_none = xv->wdesc == None;
+#endif
   xv->x = x;
   xv->y = y;
 
+#ifdef USE_GTK
+  block_input ();
+  if (xv->wdesc == None)
+    {
+      Lisp_Object xvw;
+      XSETXWIDGET_VIEW (xvw, xv);
+      XSetWindowAttributes a;
+      a.event_mask = (ExposureMask | ButtonPressMask | ButtonReleaseMask
+		      | PointerMotionMask | EnterWindowMask | LeaveWindowMask);
+
+      xv->wdesc = XCreateWindow (xv->dpy, FRAME_X_WINDOW (s->f),
+				 x + clip_left, y + clip_top,
+				 clip_right - clip_left,
+				 clip_bottom - clip_top, 0,
+				 CopyFromParent, CopyFromParent,
+				 CopyFromParent, CWEventMask, &a);
+      XDefineCursor (xv->dpy, xv->wdesc, xv->cursor);
+      xv->cr_surface = cairo_xlib_surface_create (xv->dpy,
+						  xv->wdesc,
+						  FRAME_DISPLAY_INFO (s->f)->visual,
+						  clip_right - clip_left,
+						  clip_bottom - clip_top);
+      xv->cr_context = cairo_create (xv->cr_surface);
+      Fputhash (make_fixnum (xv->wdesc), xvw, x_window_to_xwv_map);
+
+      moved = false;
+    }
+#endif
+
   /* Has it moved?  */
   if (moved)
     {
 #ifdef USE_GTK
-      gtk_fixed_move (GTK_FIXED (FRAME_GTK_WIDGET (s->f)),
-                      xv->widgetwindow, x + clip_left, y + clip_top);
+      XMoveResizeWindow (xv->dpy, xv->wdesc, x + clip_left, y + clip_top,
+			 clip_right - clip_left, clip_bottom - clip_top);
+      XFlush (xv->dpy);
+      cairo_xlib_surface_set_size (xv->cr_surface, clip_right - clip_left,
+				   clip_bottom - clip_top);
 #elif defined NS_IMPL_COCOA
       nsxwidget_move_view (xv, x + clip_left, y + clip_top);
 #endif
@@ -735,10 +1405,14 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
       || xv->clip_top != clip_top || xv->clip_left != clip_left)
     {
 #ifdef USE_GTK
-      gtk_widget_set_size_request (xv->widgetwindow, clip_right - clip_left,
-                                   clip_bottom - clip_top);
-      gtk_fixed_move (GTK_FIXED (xv->widgetwindow), xv->widget, -clip_left,
-                      -clip_top);
+      if (!wdesc_was_none && !moved)
+	{
+	  XResizeWindow (xv->dpy, xv->wdesc, clip_right - clip_left,
+			 clip_bottom - clip_top);
+	  XFlush (xv->dpy);
+	  cairo_xlib_surface_set_size (xv->cr_surface, clip_right - clip_left,
+				       clip_bottom - clip_top);
+	}
 #elif defined NS_IMPL_COCOA
       nsxwidget_resize_view (xv, clip_right - clip_left,
                              clip_bottom - clip_top);
@@ -758,12 +1432,15 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
   if (!xwidget_hidden (xv))
     {
 #ifdef USE_GTK
-      gtk_widget_queue_draw (xv->widgetwindow);
-      gtk_widget_queue_draw (xv->widget);
+      gtk_widget_queue_draw (xww->widget_osr);
 #elif defined NS_IMPL_COCOA
       nsxwidget_set_needsdisplay (xv);
 #endif
     }
+
+#ifdef USE_GTK
+  unblock_input ();
+#endif
 }
 
 static bool
@@ -975,16 +1652,13 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
           struct xwidget_view *xv = XXWIDGET_VIEW (XCAR (tail));
           if (XXWIDGET (xv->model) == xw)
             {
-#ifdef USE_GTK
-              gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xw->width,
-                                           xw->height);
-#elif defined NS_IMPL_COCOA
-              nsxwidget_resize_view(xv, xw->width, xw->height);
-#endif
+	      wset_redisplay (XWINDOW (xv->w));
             }
         }
     }
 
+  redisplay ();
+
   return Qnil;
 }
 
@@ -1084,13 +1758,15 @@ DEFUN ("delete-xwidget-view",
   CHECK_XWIDGET_VIEW (xwidget_view);
   struct xwidget_view *xv = XXWIDGET_VIEW (xwidget_view);
 #ifdef USE_GTK
-  gtk_widget_destroy (xv->widgetwindow);
-  /* xv->model still has signals pointing to the view.  There can be
-     several views.  Find the matching signals and delete them all.  */
-  g_signal_handlers_disconnect_matched  (XXWIDGET (xv->model)->widgetwindow_osr,
-                                         G_SIGNAL_MATCH_DATA,
-                                         0, 0, 0, 0,
-                                         xv->widget);
+  if (xv->wdesc != None)
+    {
+      block_input ();
+      cairo_destroy (xv->cr_context);
+      cairo_surface_destroy (xv->cr_surface);
+      XDestroyWindow (xv->dpy, xv->wdesc);
+      Fremhash (make_fixnum (xv->wdesc), x_window_to_xwv_map);
+      unblock_input ();
+    }
 #elif defined NS_IMPL_COCOA
   nsxwidget_delete_view (xv);
 #endif
@@ -1145,6 +1821,19 @@ DEFUN ("xwidget-buffer",
   return XXWIDGET (xwidget)->buffer;
 }
 
+DEFUN ("set-xwidget-buffer",
+       Fset_xwidget_buffer, Sset_xwidget_buffer,
+       2, 2, 0,
+       doc: /* Set XWIDGET's buffer to BUFFER.  */)
+  (Lisp_Object xwidget, Lisp_Object buffer)
+{
+  CHECK_XWIDGET (xwidget);
+  CHECK_BUFFER (buffer);
+
+  XXWIDGET (xwidget)->buffer = buffer;
+  return Qnil;
+}
+
 DEFUN ("set-xwidget-plist",
        Fset_xwidget_plist, Sset_xwidget_plist,
        2, 2, 0,
@@ -1183,6 +1872,166 @@ DEFUN ("xwidget-query-on-exit-flag",
   return (XXWIDGET (xwidget)->kill_without_query ? Qnil : Qt);
 }
 
+DEFUN ("xwidget-webkit-search", Fxwidget_webkit_search, Sxwidget_webkit_search,
+       2, 5, 0,
+       doc: /* Begin an incremental search operation in an xwidget.
+QUERY should be a string containing the text to search for.  XWIDGET
+should be a WebKit xwidget where the search will take place.  When the
+search operation is complete, callers should also call
+`xwidget-webkit-finish-search' to complete the search operation.
+
+CASE-INSENSITIVE, when non-nil, will cause the search to ignore the
+case of characters inside QUERY.  BACKWARDS, when non-nil, will cause
+the search to proceed towards the beginning of the widget's contents.
+WRAP-AROUND, when nil, will cause the search to stop upon hitting the
+end of the widget's contents.
+
+It is OK to call this function even when a search is already in
+progress.  In that case, the previous search query will be replaced
+with QUERY.  */)
+  (Lisp_Object query, Lisp_Object xwidget, Lisp_Object case_insensitive,
+   Lisp_Object backwards, Lisp_Object wrap_around)
+{
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+  WebKitFindOptions opt;
+  struct xwidget *xw;
+  gchar *g_query;
+#endif
+
+  CHECK_STRING (query);
+  CHECK_XWIDGET (xwidget);
+
+#ifdef USE_GTK
+  xw = XXWIDGET (xwidget);
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  query = ENCODE_UTF_8 (query);
+  opt = WEBKIT_FIND_OPTIONS_NONE;
+  g_query = xstrdup (SSDATA (query));
+
+  if (!NILP (case_insensitive))
+    opt |= WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE;
+  if (!NILP (backwards))
+    opt |= WEBKIT_FIND_OPTIONS_BACKWARDS;
+  if (!NILP (wrap_around))
+    opt |= WEBKIT_FIND_OPTIONS_WRAP_AROUND;
+
+  if (xw->find_text)
+    xfree (xw->find_text);
+  xw->find_text = g_query;
+
+  block_input ();
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search (controller, g_query, opt, G_MAXUINT);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
+DEFUN ("xwidget-webkit-next-result", Fxwidget_webkit_next_result,
+       Sxwidget_webkit_next_result, 1, 1, 0,
+       doc: /* Show the next result matching the current search query.
+
+XWIDGET should be an xwidget that currently has a search query.
+Before calling this function, you should start a search operation
+using `xwidget-webkit-search'.  */)
+  (Lisp_Object xwidget)
+{
+  struct xwidget *xw;
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!xw->find_text)
+    error ("Widget has no ongoing search operation");
+
+#ifdef USE_GTK
+  block_input ();
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search_next (controller);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
+DEFUN ("xwidget-webkit-previous-result", Fxwidget_webkit_previous_result,
+       Sxwidget_webkit_previous_result, 1, 1, 0,
+       doc: /* Show the previous result matching the current search query.
+
+XWIDGET should be an xwidget that currently has a search query.
+Before calling this function, you should start a search operation
+using `xwidget-webkit-search'.  */)
+  (Lisp_Object xwidget)
+{
+  struct xwidget *xw;
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!xw->find_text)
+    error ("Widget has no ongoing search operation");
+
+#ifdef USE_GTK
+  block_input ();
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search_previous (controller);
+
+  if (xw->find_text)
+    {
+      xfree (xw->find_text);
+      xw->find_text = NULL;
+    }
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
+DEFUN ("xwidget-webkit-finish-search", Fxwidget_webkit_finish_search,
+       Sxwidget_webkit_finish_search, 1, 1, 0,
+       doc: /* Finish XWIDGET's search operation.
+
+XWIDGET should be an xwidget that currently has a search query.
+Before calling this function, you should start a search operation
+using `xwidget-webkit-search'.  */)
+  (Lisp_Object xwidget)
+{
+  struct xwidget *xw;
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!xw->find_text)
+    error ("Widget has no ongoing search operation");
+
+#ifdef USE_GTK
+  block_input ();
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search_finish (controller);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
 void
 syms_of_xwidget (void)
 {
@@ -1215,6 +2064,12 @@ syms_of_xwidget (void)
   defsubr (&Sxwidget_plist);
   defsubr (&Sxwidget_buffer);
   defsubr (&Sset_xwidget_plist);
+  defsubr (&Sxwidget_perform_lispy_event);
+  defsubr (&Sxwidget_webkit_search);
+  defsubr (&Sxwidget_webkit_finish_search);
+  defsubr (&Sxwidget_webkit_next_result);
+  defsubr (&Sxwidget_webkit_previous_result);
+  defsubr (&Sset_xwidget_buffer);
 
   DEFSYM (QCxwidget, ":xwidget");
   DEFSYM (QCtitle, ":title");
@@ -1236,6 +2091,15 @@ syms_of_xwidget (void)
   Vxwidget_view_list = Qnil;
 
   Fprovide (intern ("xwidget-internal"), Qnil);
+
+  id_to_xwidget_map = CALLN (Fmake_hash_table, QCtest, Qeq);
+  staticpro (&id_to_xwidget_map);
+
+#ifdef USE_GTK
+  x_window_to_xwv_map = CALLN (Fmake_hash_table, QCtest, Qeq);
+
+  staticpro (&x_window_to_xwv_map);
+#endif
 }
 
 
@@ -1374,7 +2238,7 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
 		  /* The only call to xwidget_end_redisplay is in dispnew.
 		     xwidget_end_redisplay (w->current_matrix);  */
 		  struct xwidget_view *xv
-		    = xwidget_view_lookup (glyph->u.xwidget, w);
+		    = xwidget_view_lookup (xwidget_from_id (glyph->u.xwidget), w);
 #ifdef USE_GTK
 		  /* FIXME: Is it safe to assume xwidget_view_lookup
 		     always succeeds here?  If so, this comment can be removed.
@@ -1424,6 +2288,26 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
     }
 }
 
+#ifdef USE_GTK
+void
+kill_frame_xwidget_views (struct frame *f)
+{
+  Lisp_Object rem = Qnil;
+
+  for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail);
+       tail = XCDR (tail))
+    {
+      if (XXWIDGET_VIEW (XCAR (tail))->frame == f)
+	rem = Fcons (XCAR (tail), rem);
+    }
+
+  for (; CONSP (rem); rem = XCDR (rem))
+    {
+      Fdelete_xwidget_view (XCAR (rem));
+    }
+}
+#endif
+
 /* Kill all xwidget in BUFFER.  */
 void
 kill_buffer_xwidgets (Lisp_Object buffer)
@@ -1437,12 +2321,15 @@ kill_buffer_xwidgets (Lisp_Object buffer)
       {
         CHECK_XWIDGET (xwidget);
         struct xwidget *xw = XXWIDGET (xwidget);
+	Fremhash (make_fixnum (xw->xwidget_id), id_to_xwidget_map);
 #ifdef USE_GTK
         if (xw->widget_osr && xw->widgetwindow_osr)
           {
             gtk_widget_destroy (xw->widget_osr);
             gtk_widget_destroy (xw->widgetwindow_osr);
           }
+	if (xw->find_text)
+	  xfree (xw->find_text);
 	if (!NILP (xw->script_callbacks))
 	  for (ptrdiff_t idx = 0; idx < ASIZE (xw->script_callbacks); idx++)
 	    {
diff --git a/src/xwidget.h b/src/xwidget.h
index 591f23489d..35b9e8b493 100644
--- a/src/xwidget.h
+++ b/src/xwidget.h
@@ -32,6 +32,8 @@ #define XWIDGET_H_INCLUDED
 
 #if defined (USE_GTK)
 #include <gtk/gtk.h>
+#include <X11/Xlib.h>
+#include "xterm.h"
 #elif defined (NS_IMPL_COCOA) && defined (__OBJC__)
 #import <AppKit/NSView.h>
 #import "nsxwidget.h"
@@ -59,11 +61,14 @@ #define XWIDGET_H_INCLUDED
 
   int height;
   int width;
+  uint32_t xwidget_id;
+  gchar *find_text;
 
 #if defined (USE_GTK)
   /* For offscreen widgets, unused if not osr.  */
   GtkWidget *widget_osr;
   GtkWidget *widgetwindow_osr;
+  guint hit_result;
 #elif defined (NS_IMPL_COCOA)
 # ifdef __OBJC__
   /* For offscreen widgets, unused if not osr.  */
@@ -98,9 +103,13 @@ #define XWIDGET_H_INCLUDED
   bool hidden;
 
 #if defined (USE_GTK)
-  GtkWidget *widget;
-  GtkWidget *widgetwindow;
-  GtkWidget *emacswindow;
+  Display *dpy;
+  Window wdesc;
+  Emacs_Cursor cursor;
+  struct frame *frame;
+
+  cairo_surface_t *cr_surface;
+  cairo_t *cr_context;
 #elif defined (NS_IMPL_COCOA)
 # ifdef __OBJC__
   XvWindow *xvWindow;
@@ -162,6 +171,18 @@ #define XG_XWIDGET_VIEW "emacs_xwidget_view"
 void store_xwidget_js_callback_event (struct xwidget *xw,
                                       Lisp_Object proc,
                                       Lisp_Object argument);
+
+extern struct xwidget *xwidget_from_id (uint32_t id);
+
+#ifdef HAVE_X_WINDOWS
+struct xwidget_view *xwidget_view_from_window (Window wdesc);
+void xwidget_expose (struct xwidget_view *xv);
+extern void kill_frame_xwidget_views (struct frame *f);
+extern void xwidget_button (struct xwidget_view *, bool, int,
+			    int, int, int, Time);
+extern void xwidget_motion_or_crossing (struct xwidget_view *,
+					const XEvent *);
+#endif
 #else
 INLINE_HEADER_BEGIN
 INLINE void syms_of_xwidget (void) {}

^ permalink raw reply related	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07  1:26                                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-07  1:29                                   ` Lars Ingebrigtsen
  2021-11-07  1:34                                     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 39+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-07  1:29 UTC (permalink / raw)
  To: Po Lu; +Cc: 51473

Po Lu <luangruo@yahoo.com> writes:

> Thanks, does this work instead?

Now it's:

In file included from window.c:41:
./xwidget.h:65:3: error: unknown type name 'gchar'; did you mean 'char'?
  gchar *find_text;
  ^~~~~
  char
In file included from xdisp.c:465:
./xwidget.h:65:3: error: unknown type name 'gchar'; did you mean 'char'?
  gchar *find_text;
  ^~~~~
  char
In file included from dispnew.c:43:
./xwidget.h:65:3: error: unknown type name 'gchar'; did you mean 'char'?
  gchar *find_text;

repeated a whole bunch more times, but it's just that error, I think.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07  1:29                                   ` Lars Ingebrigtsen
@ 2021-11-07  1:34                                     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-07  1:39                                       ` Lars Ingebrigtsen
  0 siblings, 1 reply; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-07  1:34 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: Eli Zaretskii, 51473

[-- Attachment #1: Type: text/plain, Size: 612 bytes --]

Lars Ingebrigtsen <larsi@gnus.org> writes:

> Now it's:
>
> In file included from window.c:41:
> ./xwidget.h:65:3: error: unknown type name 'gchar'; did you mean 'char'?
>   gchar *find_text;
>   ^~~~~
>   char
> In file included from xdisp.c:465:
> ./xwidget.h:65:3: error: unknown type name 'gchar'; did you mean 'char'?
>   gchar *find_text;
>   ^~~~~
>   char
> In file included from dispnew.c:43:
> ./xwidget.h:65:3: error: unknown type name 'gchar'; did you mean 'char'?
>   gchar *find_text;
>
> repeated a whole bunch more times, but it's just that error, I think.

Good catch, thanks!  Does this work?


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: test.diff --]
[-- Type: text/x-patch, Size: 94401 bytes --]

diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi
index 7f91e1c188..1207ab5e9a 100644
--- a/doc/emacs/misc.texi
+++ b/doc/emacs/misc.texi
@@ -2953,6 +2953,34 @@ Embedded WebKit Widgets
 reloading it.  Type @w{@kbd{C-h b}} in that buffer to see the key
 bindings.
 
+@findex xwidget-webkit-edit-mode
+@cindex xwidget-webkit-edit-mode
+  By default, typing a self-inserting character inside an xwidget
+webkit buffer will do nothing, or trigger some special action.  To
+make those characters and other common editing keys insert themselves
+when pressed, you can enable @code{xwidget-webkit-edit-mode}, which
+redefines them to be passed through to the WebKit xwidget.
+
+You can also enable @code{xwidget-webkit-edit-mode} by typing @kbd{e}
+inside the xwidget webkit buffer.
+
+@findex xwidget-webkit-isearch-mode
+@cindex xwidget-webkit-isearch-mode
+@cindex searching in webkit buffers
+  @code{xwidget-webkit-isearch-mode} is a minor mode that behaves
+similarly to incremental search (@pxref{Incremental Search}), but
+operates on the contents of a WebKit widget instead of the current
+buffer.  It is bound to @kbd{C-s} and @kbd{C-r} inside xwidget-webkit
+buffers.  When it is enabled through @kbd{C-r}, the initial search
+will be performed in reverse direction.
+
+Typing any self-inserting character will cause the character to be
+inserted into the current search query.  Typing @kbd{C-s} will cause
+the WebKit widget to display the next search result, while typing
+@kbd{C-r} will cause it to display the last.
+
+To leave incremental search, you can type @kbd{C-g}.
+
 @node Browse-URL
 @subsection  Following URLs
 @cindex World Wide Web
diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi
index b38a83b4fe..832b570b6a 100644
--- a/doc/lispref/commands.texi
+++ b/doc/lispref/commands.texi
@@ -1176,6 +1176,7 @@ Input Events
 * Repeat Events::               Double and triple click (or drag, or down).
 * Motion Events::               Just moving the mouse, not pushing a button.
 * Focus Events::                Moving the mouse between frames.
+* Xwidget Events::              Events generated by xwidgets.
 * Misc Events::                 Other events the system can generate.
 * Event Examples::              Examples of the lists for mouse events.
 * Classifying Events::          Finding the modifier keys in an event symbol.
@@ -1871,6 +1872,76 @@ Focus Events
 so that the focus event comes either before or after the multi-event key
 sequence, and not within it.
 
+@node Xwidget Events
+@subsection Xwidget events
+
+Xwidgets (@pxref{Xwidgets}) can send events to update Lisp programs on
+their status.  These events are dubbed @code{xwidget-events}, and
+contain various data describing the nature of the change.
+
+@table @code
+@cindex @code{xwidget-event} event
+@item (xwidget-event @var{kind} @var{xwidget} @var{arg})
+This event is sent whenever some kind of update occurs in
+@var{xwidget}.  There are several types of updates, which are
+identified by @var{kind}.
+
+@cindex @code{load-changed} xwidget events
+An xwidget event with @var{kind} set to @code{load-changed} indicates
+that the @var{xwidget} has reached a particular point of the
+page-loading process.  When these events are sent, @var{arg} will
+contain a string that futher describes the status of the widget.
+
+@cindex @samp{"load-finished"} in xwidgets
+When @var{arg} is @samp{"load-finished"}, it means the xwidget has
+finished processing whatever page-loading operation that it was
+previously performing.
+
+@cindex @samp{"load-started"} in xwidgets
+Otherwise, if it is @samp{"load-started"}, then the widget has begun a
+page-loading operation.
+
+@cindex @samp{"load-redirected"} in xwidgets
+If @var{arg} is @samp{"load-redirected"}, it means the widget has
+encountered and followed a redirect during the page-loading operation.
+
+@cindex @samp{"load-committed"} in xwidgets
+If @var{arg} is @samp{"load-committed"}, then the widget has committed
+to a given URL during the page-loading operation.  This means that the
+URL is the final URL that will be rendered by @var{xwidget} during the
+current page-loading operation.
+
+@cindex @code{download-callback} xwidget events
+An event with @var{kind} set to @code{download-callback} indicates
+that a download of some kind has been completed.
+
+In these events, there can be arguments after @var{arg}, which itself
+indicates the URL that the download file was retrieved from: the first
+argument after @var{arg} indicates the MIME type of the download, as a
+string, while the second such argument contains the full file path to
+the downloaded file.
+
+@cindex @code{download-started} xwidget events
+An event with @var{kind} set to @code{download-started} indicates that
+a download has been started.  In these events, @var{arg} contains the
+URL of the file that is currently being downloaded.
+
+@cindex @code{javascript-callback} xwidget events
+An event with @var{kind} set to @code{javascript-callback} contains
+JavaScript callback data.  These events are used internally by
+@code{xwidget-webkit-execute-script}.
+
+@cindex @code{xwidget-display-event} event
+@item (xwidget-display-event @var{xwidget})
+This event is sent whenever an xwidget requests that another xwidget
+be displayed.  @var{xwidget} is the xwidget that should be displayed.
+
+@var{xwidget}'s buffer will be set to a temporary buffer.  When
+displaying the widget, care should be taken to replace the buffer with
+the buffer in which the xwidget will be displayed, using
+@code{set-xwidget-buffer}  (@pxref{Xwidgets}).
+@end table
+
 @node Misc Events
 @subsection Miscellaneous System Events
 
diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi
index 22528a1b0f..60bca15eb2 100644
--- a/doc/lispref/display.texi
+++ b/doc/lispref/display.texi
@@ -6784,7 +6784,10 @@ Xwidgets
 in a @code{display} text or overlay property (@pxref{Display
 Property}).
 
-@defun make-xwidget type title width height arguments &optional buffer
+  Embedded widgets can send events notifying Lisp code about changes
+occurring within them.  (@pxref{Xwidget Events}).
+
+@defun make-xwidget type title width height arguments &optional buffer related
 This creates and returns an xwidget object.  If
 @var{buffer} is omitted or @code{nil}, it defaults to the current
 buffer.  If @var{buffer} names a buffer that doesn't exist, it will be
@@ -6797,7 +6800,10 @@ Xwidgets
 @end table
 
 The @var{width} and @var{height} arguments specify the widget size in
-pixels, and @var{title}, a string, specifies its title.
+pixels, and @var{title}, a string, specifies its title.  @var{related}
+is used internally by the WebKit widget, and specifies another WebKit
+widget that the newly created widget should share settings and
+subprocesses with.
 @end defun
 
 @defun xwidgetp object
@@ -6818,6 +6824,10 @@ Xwidgets
 This function returns the buffer of @var{xwidget}.
 @end defun
 
+@defun set-xwidget-buffer xwidget buffer
+This function sets the buffer of @var{xwidget} to @var{buffer}.
+@end defun
+
 @defun get-buffer-xwidgets buffer
 This function returns a list of xwidget objects associated with the
 @var{buffer}, which can be specified as a buffer object or a name of
@@ -6878,6 +6888,61 @@ Xwidgets
 query-on-exit flag, either @code{t} or @code{nil}.
 @end defun
 
+@defun xwidget-perform-lispy-event xwidget event frame
+Send an input event @var{event} to @var{xwidget}.  The precise action
+performed is platform-specific.  See @ref{Input Events}.
+
+You can optionally pass the frame the event was generated from via
+@var{frame}.  On X11, modifier keys in key events will not be
+considered if @var{frame} is @code{nil}, and the selected frame is not
+an X-Windows frame.
+
+On GTK, only keyboard and function key events are implemented.  Mouse,
+motion, and click events are dispatched to the xwidget without going
+through Lisp code, and as such shouldn't require this function to be
+sent.
+@end defun
+
+@defun xwidget-webkit-search query xwidget &optional case-insensitive backwards wrap-around
+Start an incremental search on the WebKit widget @var{xwidget} with
+the string @var{query} as a query.  @var{case-insensitive} denotes
+whether or not the search is case-insensitive, @var{backwards}
+determines if the search is performed backwards towards the start of
+the document, and @var{wrap-around} determines whether or not the
+search terminates at the end of the document.
+
+If the function is called while a search query is already present,
+then the query specified here will replace the existing query.
+
+To stop a search query, use @code{xwidget-webkit-finish-search}.
+@end defun
+
+@defun xwidget-webkit-next-result xwidget
+Display the next search result in @var{xwidget}.  This function will
+error unless a search query has already been started in @var{xwidget}
+through @code{xwidget-webkit-search}.
+
+If @code{wrap-around} was non-nil when @code{xwidget-webkit-search}
+was called, then the search will restart from the beginning of the
+document if the end is reached.
+@end defun
+
+@defun xwidget-webkit-previous-result xwidget
+Display the previous search result in @var{xwidget}.  This function
+will error unless a search query has already been started in
+@var{xwidget} through @code{xwidget-webkit-search}.
+
+If @code{wrap-around} was non-nil when @code{xwidget-webkit-search}
+was called, then the search will restart from the end of the
+document if the beginning is reached.
+@end defun
+
+@defun xwidget-webkit-finish-search xwidget
+Finish a search operation started with @code{xwidget-webkit-search} in
+@var{xwidget}.  If there is no query currently ongoing, then this
+function will error.
+@end defun
+
 @node Buttons
 @section Buttons
 @cindex buttons in buffers
diff --git a/etc/NEWS b/etc/NEWS
index a50229916f..b14f9a2549 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -495,6 +495,31 @@ the buffer will take you to that directory.
 This is a convenience function to extract the field data from
 'exif-parse-file' and 'exif-parse-buffer'.
 
+** Xwidgets
+
++++
+*** New minor mode `xwidget-webkit-edit-mode'.
+When this mode is enabled, self-inserting characters and other common
+web browser shotcut keys are redefined to send themselves to the
+WebKit widget.
+
++++
+*** New minor mode `xwidget-webkit-isearch-mode'.
+This mode acts similarly to incremental search, and allows to search
+the contents of a WebKit widget.  In xwidget-webkit mode, it is bound
+to `C-s' and `C-r'.
+
+---
+*** On X11, the WebKit inspector is now available inside xwidgets.
+To access the inspector, right click on the widget and select "Inspect
+Element".
+
+---
+*** "Open in New Window" in a WebKit widget's context menu now works.
+The newly created buffer will be displayed via display-buffer, which
+can be customized through the usual mechanism of display-buffer-alist
+and friends.
+
 \f
 * New Modes and Packages in Emacs 29.1
 
@@ -719,6 +744,39 @@ an exact match, then the lowercased '[menu-bar foo\ bar]' and finally
 '[menu-bar foo-bar]'.  This further improves backwards-compatibility
 when converting menus to use 'easy-menu-define'.
 
++++
+** The function `make-xwidget' now accepts an optional RELATED argument.
+This argument is used as another widget for the newly created WebKit
+widget to share settings and subprocesses with.  It must be another
+WebKit widget.
+
++++
+** New function `xwidget-perform-lispy-event'.
+This function allows you to send events to xwidgets.  Usually, some
+equivalent of the event will be sent, but there is no guarantee of
+what the widget will actually receive.
+
+On GTK+, only key and function key events are implemented.
+
++++
+** New functions for performing searches on WebKit xwidgets.
+Some new functions, such as `xwidget-webkit-search', have been added
+for performing searches on WebKit xwidgets.
+
++++
+** `load-changed' xwidget events are now more detailed.
+In particular, they can now have different arguments based on the
+state of the WebKit widget.  `load-finished' is sent when a load has
+completed, `load-started' when a load first starts, `load-redirected'
+after a redirect, and `load-committed' when the WebKit widget first
+commits to the load.
+
++++
+** New event type `xwidget-display-event'.
+These events are sent whenever an xwidget requests that Emacs display
+another.  The only argument to this event is the xwidget that should
+be displayed.
+
 \f
 * Changes in Emacs 29.1 on Non-Free Operating Systems
 
diff --git a/etc/images/README b/etc/images/README
index 9bbe796cc9..561cfff765 100644
--- a/etc/images/README
+++ b/etc/images/README
@@ -68,6 +68,7 @@ Emacs images and their source in the GNOME icons stock/ directory:
   bookmark_add.xpm          actions/bookmark_add
   cancel.xpm                slightly modified generic/stock_stop
   connect.xpm               net/stock_connect
+  connect-to-url.xpm        net/stock_connect-to-url
   contact.xpm               net/stock_contact
   data-save.xpm             data/stock_data-save
   delete.xpm                generic/stock_delete
diff --git a/etc/images/connect-to-url.pbm b/etc/images/connect-to-url.pbm
new file mode 100644
index 0000000000..f142349f4a
Binary files /dev/null and b/etc/images/connect-to-url.pbm differ
diff --git a/etc/images/connect-to-url.xpm b/etc/images/connect-to-url.xpm
new file mode 100644
index 0000000000..38fefeaf61
--- /dev/null
+++ b/etc/images/connect-to-url.xpm
@@ -0,0 +1,281 @@
+/* XPM */
+static char *connect_to_url[] = {
+/* columns rows colors chars-per-pixel */
+"24 24 251 2 ",
+"   c black",
+".  c #010101",
+"X  c #000103",
+"o  c #010204",
+"O  c #010305",
+"+  c #020407",
+"@  c #020609",
+"#  c #03070C",
+"$  c #04080D",
+"%  c #0F0F0D",
+"&  c #030A10",
+"*  c #050B10",
+"=  c #060C11",
+"-  c #070D13",
+";  c #070D14",
+":  c #060C15",
+">  c #070E14",
+",  c #0B1824",
+"<  c #0A1B2B",
+"1  c #0A1C2E",
+"2  c #141A20",
+"3  c #161E25",
+"4  c #181E23",
+"5  c #0D2032",
+"6  c #142534",
+"7  c #1F2830",
+"8  c #1D2933",
+"9  c #102438",
+"0  c #272622",
+"q  c #21292F",
+"w  c #272F36",
+"e  c #282F33",
+"r  c #222F3A",
+"t  c #2E3337",
+"y  c #2D373E",
+"u  c #32383C",
+"i  c #33383C",
+"p  c #343A3E",
+"a  c #43423C",
+"s  c #112941",
+"d  c #102A44",
+"f  c #132D47",
+"g  c #192F46",
+"h  c #17314B",
+"j  c #15314F",
+"k  c #163351",
+"l  c #163554",
+"z  c #173554",
+"x  c #1F3A53",
+"c  c #1D3955",
+"v  c #1A3958",
+"b  c #1C3B5B",
+"n  c #1F3C58",
+"m  c #1D3C5C",
+"M  c #1E3E5D",
+"N  c #1F3F5F",
+"B  c #303B44",
+"V  c #313C44",
+"C  c #313D47",
+"Z  c #213C56",
+"A  c #233E57",
+"S  c #1F405F",
+"D  c #374148",
+"F  c #2D4050",
+"G  c #25405B",
+"H  c #25425E",
+"J  c #214262",
+"K  c #244565",
+"L  c #264665",
+"P  c #254666",
+"I  c #2A4967",
+"U  c #284969",
+"Y  c #2A4C6C",
+"T  c #2C4F6F",
+"R  c #33526E",
+"E  c #385269",
+"W  c #2D5070",
+"Q  c #2E5172",
+"!  c #335473",
+"~  c #3F5B75",
+"^  c #3D5F7D",
+"/  c #41494F",
+"(  c #646056",
+")  c #6C685E",
+"_  c #505F6C",
+"`  c #48657C",
+"'  c #556A7A",
+"]  c #5B6C78",
+"[  c #5F6F7B",
+"{  c #5D6F7D",
+"}  c #706C62",
+"|  c #726D63",
+" . c #78756B",
+".. c #7D786E",
+"X. c #60727F",
+"o. c #807D74",
+"O. c #8A857B",
+"+. c #8B877E",
+"@. c #4E6A83",
+"#. c #4A6A86",
+"$. c #4A7090",
+"%. c #587790",
+"&. c #5F7E95",
+"*. c #587B98",
+"=. c #6F7980",
+"-. c #697F8F",
+";. c #66839B",
+":. c #6A879F",
+">. c #708391",
+",. c #728A9A",
+"<. c #748898",
+"1. c #758A99",
+"2. c #7B8F9F",
+"3. c #708DA4",
+"4. c #7990A1",
+"5. c #7292AB",
+"6. c #7691A8",
+"7. c #7693AB",
+"8. c #7B98AE",
+"9. c #7E98AD",
+"0. c #7E9DB3",
+"q. c #7F9EB4",
+"w. c #8C8981",
+"e. c #989389",
+"r. c #A6A29B",
+"t. c #8093A1",
+"y. c #8598A3",
+"u. c #8498A7",
+"i. c #809AAD",
+"p. c #8F9FAA",
+"a. c #899FAE",
+"s. c #819FB5",
+"d. c #86A2B8",
+"f. c #87A5BB",
+"g. c #88A3B8",
+"h. c #89A5BA",
+"j. c #8FABBF",
+"k. c #97A7B1",
+"l. c #90AABE",
+"z. c #91ABBF",
+"x. c #98ACB9",
+"c. c #AAA7A0",
+"v. c #B1ADA4",
+"b. c #B3B1AA",
+"n. c #B7B3AA",
+"m. c #A3B1BC",
+"M. c #A5B1BC",
+"N. c #A9B6BF",
+"B. c #BEBBB5",
+"V. c #C4C2BD",
+"C. c #94AEC1",
+"Z. c #96AEC1",
+"A. c #94AFC2",
+"S. c #95AFC2",
+"D. c #96B0C3",
+"F. c #98B0C3",
+"G. c #9FB5C3",
+"H. c #99B3C6",
+"J. c #98B3C7",
+"K. c #9AB3C6",
+"L. c #9BB4C7",
+"P. c #9FB8CA",
+"I. c #9FB8CB",
+"U. c #A2B8C9",
+"Y. c #A3B9C9",
+"T. c #A0B9CB",
+"R. c #A3BACB",
+"E. c #A0B9CC",
+"W. c #A2BACC",
+"Q. c #A4BDCE",
+"!. c #A6BECF",
+"~. c #B8BEC2",
+"^. c #B8C3CA",
+"/. c #BCC5CB",
+"(. c #BDC8CE",
+"). c #A8C0D1",
+"_. c #AAC0D0",
+"`. c #ABC1D1",
+"'. c #ACC2D3",
+"]. c #AAC5D7",
+"[. c #B4C8D6",
+"{. c #BDCBD5",
+"}. c #B4C9D8",
+"|. c #B6CAD8",
+" X c #B8CBD9",
+".X c #BBCDDB",
+"XX c #B7D0E0",
+"oX c #BDD3E2",
+"OX c #BCD5E5",
+"+X c #CECAC3",
+"@X c #C5D2C8",
+"#X c #C0D2DE",
+"$X c #C4D3DF",
+"%X c #CCD7DE",
+"&X c #D2D8DC",
+"*X c #E1DFDB",
+"=X c #E2E1DD",
+"-X c #C2D3E0",
+";X c #C2D4E1",
+":X c #C5D5E1",
+">X c #C6D6E1",
+",X c #C4D6E2",
+"<X c #C5D6E3",
+"1X c #C6D7E3",
+"2X c #C3D7E4",
+"3X c #C1D7E6",
+"4X c #C7D8E3",
+"5X c #C5D8E5",
+"6X c #C7D9E5",
+"7X c #CBD9E4",
+"8X c #CBDAE5",
+"9X c #CDDAE4",
+"0X c #CCDBE5",
+"qX c #CFDBE5",
+"wX c #CBDCE7",
+"eX c #C0D9E8",
+"rX c #C2DBEA",
+"tX c #C4DAE8",
+"yX c #D0DEE7",
+"uX c #D1DFE8",
+"iX c #D0DFE9",
+"pX c #D0E0EA",
+"aX c #D1E1EB",
+"sX c #D3E1EA",
+"dX c #D4E1E9",
+"fX c #D4E1EA",
+"gX c #D5E2EA",
+"hX c #D4E2EB",
+"jX c #D6E2EB",
+"kX c #D3E2EC",
+"lX c #D8E3EA",
+"zX c #DFE6EB",
+"xX c #D9E4EC",
+"cX c #D9E5ED",
+"vX c #DAE5ED",
+"bX c #DAE6ED",
+"nX c #DCE7EE",
+"mX c #DBE8EF",
+"MX c #DDE8EF",
+"NX c #DFE8EF",
+"BX c #EAE8E3",
+"VX c #EBEAE6",
+"CX c #ECEBE8",
+"ZX c #E9EEEA",
+"AX c #F0EFEC",
+"SX c #F2F0ED",
+"DX c #E1ECF3",
+"FX c #E4EDF3",
+"GX c #E8EFF4",
+"HX c #F0F3F1",
+"JX c None",
+/* pixels */
+"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJXu D p t i V w JXJXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXC X./.&XDXGX%X{.m._ r JXJXJXJXJXJXJX",
+"JXJXJXJXJXi /.DXnXnXFXuX7X$X$XjXM.w JXJXJXJXJXJX",
+"JXJXJXJX/ ^.qXbX1XkX5X5X-X;XsXqXjXN.B JXJXJXJXJX",
+"JXJXJXe (.bXMXDXaXtXtX3XoXbXjXsXyX7Xx.q JXJXJXJX",
+"JXJX7 k.jXbXbX5X3XeXrXOXXX1XsXyXwX$X|.4.3 JXJXJX",
+"JXJXX.:XuXjX'.]._.y.    G.sXW.|..X$X[.H.' JXJXJX",
+"JXJXu.$XqXT.H.>.    e.o.  sXwX}.R.R.`.H.1.- JXJX",
+"JX4 a.9.C.h.] a n.V.BXo.        p.!.T.l.4.- JXJX",
+"JX2 F.d.5.7.  =XAXc.BXo.  @X@XZX  !.C.F.@.> JXJX",
+"            o.=XAXc.BXo.        t.U.z.3.Y $ JXJX",
+"BXBXBXBXVXBXBXAXVXO.CXo.  P.C.!.I.J.C.;.L * JXJX",
+"o.o.o.o.o. . .B.b...*X .  $.*.T.J.A.h.Y c @ JXJX",
+"             .w.r.| +X .        1.C.3.L h   JXJX",
+"JXJX6 Q ^ 1.% w.r.| +X .  @X@XHX  h.:.M ,   JXJX",
+"JXJXO x T #.] 0 +.} v.)         -.s.H 9 O JXJXJX",
+"JXJXJX+ n ! i.X.% % e.(   Q Y %.0.&.f O   JXJXJX",
+"JXJXJXJX& A s.8.E A % % A K J R ` g @   JXJXJXJX",
+"JXJXJXJXJX@ C ~ m M J N M b v l < O   JXJXJXJXJX",
+"JXJXJXJXJXJX  : 5 d k z k d 1 &     JXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJX                JXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX",
+"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX"
+};
diff --git a/lisp/xwidget.el b/lisp/xwidget.el
index 8c593abea8..4046140895 100644
--- a/lisp/xwidget.el
+++ b/lisp/xwidget.el
@@ -35,8 +35,9 @@
 (require 'bookmark)
 
 (declare-function make-xwidget "xwidget.c"
-                  (type title width height arguments &optional buffer))
+                  (type title width height arguments &optional buffer related))
 (declare-function xwidget-buffer "xwidget.c" (xwidget))
+(declare-function set-xwidget-buffer "xwidget.c" (xwidget buffer))
 (declare-function xwidget-size-request "xwidget.c" (xwidget))
 (declare-function xwidget-resize "xwidget.c" (xwidget new-width new-height))
 (declare-function xwidget-webkit-execute-script "xwidget.c"
@@ -58,14 +59,14 @@ xwidget
   "Displaying native widgets in Emacs buffers."
   :group 'widgets)
 
-(defun xwidget-insert (pos type title width height &optional args)
+(defun xwidget-insert (pos type title width height &optional args related)
   "Insert an xwidget at position POS.
-Supply the xwidget's TYPE, TITLE, WIDTH, and HEIGHT.
+Supply the xwidget's TYPE, TITLE, WIDTH, HEIGHT, and RELATED.
 See `make-xwidget' for the possible TYPE values.
 The usage of optional argument ARGS depends on the xwidget.
 This returns the result of `make-xwidget'."
   (goto-char pos)
-  (let ((id (make-xwidget type title width height args)))
+  (let ((id (make-xwidget type title width height args nil related)))
     (put-text-property (point) (+ 1 (point))
                        'display (list 'xwidget ':xwidget id))
     id))
@@ -88,6 +89,9 @@ xwidget-at
 (require 'seq)
 (require 'url-handlers)
 
+(defvar-local xwidget-webkit--title ""
+  "The title of the WebKit widget, used for the header line.")
+
 ;;;###autoload
 (defun xwidget-webkit-browse-url (url &optional new-session)
   "Ask xwidget-webkit to browse URL.
@@ -124,6 +128,14 @@ xwidget-webkit-clone-and-split-right
     (with-selected-window (split-window-right)
       (xwidget-webkit-new-session url))))
 
+(declare-function xwidget-perform-lispy-event "xwidget.c")
+
+(defun xwidget-webkit-pass-command-event ()
+  "Pass `last-command-event' to the current buffer's WebKit widget."
+  (interactive)
+  (xwidget-perform-lispy-event (xwidget-webkit-current-session)
+                               last-command-event))
+
 ;;todo.
 ;; - check that the webkit support is compiled in
 (defvar xwidget-webkit-mode-map
@@ -138,6 +150,9 @@ xwidget-webkit-mode-map
     (define-key map "w" 'xwidget-webkit-current-url)
     (define-key map "+" 'xwidget-webkit-zoom-in)
     (define-key map "-" 'xwidget-webkit-zoom-out)
+    (define-key map "e" 'xwidget-webkit-edit-mode)
+    (define-key map "\C-r" 'xwidget-webkit-isearch-mode)
+    (define-key map "\C-s" 'xwidget-webkit-isearch-mode)
 
     ;;similar to image mode bindings
     (define-key map (kbd "SPC")                 'xwidget-webkit-scroll-up)
@@ -164,6 +179,63 @@ xwidget-webkit-mode-map
     map)
   "Keymap for `xwidget-webkit-mode'.")
 
+(easy-menu-define nil xwidget-webkit-mode-map "Xwidget WebKit menu."
+  (list "Xwidget WebKit"
+        ["Browse URL" xwidget-webkit-browse-url
+         :active t
+         :help "Prompt for a URL, then instruct WebKit to browse it"]
+        ["Back" xwidget-webkit-back t]
+        ["Forward" xwidget-webkit-forward t]
+        ["Reload" xwidget-webkit-reload t]
+        ["Insert String" xwidget-webkit-insert-string
+         :active t
+         :help "Insert a string into the currently active field"]
+        ["Zoom In" xwidget-webkit-zoom-in t]
+        ["Zoom Out" xwidget-webkit-zoom-out t]
+        ["Edit Mode" xwidget-webkit-edit-mode
+         :active t
+         :style toggle
+         :selected xwidget-webkit-edit-mode
+         :help "Send self inserting characters to the WebKit widget"]
+        ["Save Selection" xwidget-webkit-copy-selection-as-kill
+         :active t
+         :help "Save the browser's selection in the kill ring"]
+        ["Incremental Search" xwidget-webkit-isearch-mode
+         :active (not xwidget-webkit-isearch-mode)
+         :help "Perform incremental search inside the WebKit widget"]))
+
+(defvar xwidget-webkit-tool-bar-map
+  (let ((map (make-sparse-keymap)))
+    (prog1 map
+      (tool-bar-local-item-from-menu 'xwidget-webkit-back
+                                     "left-arrow"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-forward
+                                     "right-arrow"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-reload
+                                     "refresh"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-zoom-in
+                                     "zoom-in"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-zoom-out
+                                     "zoom-out"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-browse-url
+                                     "connect-to-url"
+                                     map
+                                     xwidget-webkit-mode-map)
+      (tool-bar-local-item-from-menu 'xwidget-webkit-isearch-mode
+                                     "search"
+                                     map
+                                     xwidget-webkit-mode-map))))
+
 (defun xwidget-webkit-zoom-in ()
   "Increase webkit view zoom factor."
   (interactive nil xwidget-webkit-mode)
@@ -276,6 +348,8 @@ xwidget-webkit-callback
     (with-current-buffer (xwidget-buffer xwidget)
       (cond ((eq xwidget-event-type 'load-changed)
              (let ((title (xwidget-webkit-title xwidget)))
+               (setq xwidget-webkit--title title)
+               (force-mode-line-update)
                (xwidget-log "webkit finished loading: %s" title)
                ;; Do not adjust webkit size to window here, the selected window
                ;; can be the mini-buffer window unwantedly.
@@ -309,8 +383,10 @@ bookmark-make-record-function
 (define-derived-mode xwidget-webkit-mode special-mode "xwidget-webkit"
   "Xwidget webkit view mode."
   (setq buffer-read-only t)
+  (setq-local tool-bar-map xwidget-webkit-tool-bar-map)
   (setq-local bookmark-make-record-function
               #'xwidget-webkit-bookmark-make-record)
+  (setq-local header-line-format 'xwidget-webkit--title)
   ;; Keep track of [vh]scroll when switching buffers
   (image-mode-setup-winprops))
 
@@ -609,6 +685,7 @@ xwidget-webkit-new-session
   (let*
       ((bufname (generate-new-buffer-name "*xwidget-webkit*"))
        (callback (or callback #'xwidget-webkit-callback))
+       (current-session (xwidget-webkit-current-session))
        xw)
     (setq xwidget-webkit-last-session-buffer (switch-to-buffer
                                               (get-buffer-create bufname)))
@@ -621,11 +698,35 @@ xwidget-webkit-new-session
       (setq xw (xwidget-insert
                 start 'webkit bufname
                 (xwidget-window-inside-pixel-width (selected-window))
-                (xwidget-window-inside-pixel-height (selected-window)))))
+                (xwidget-window-inside-pixel-height (selected-window))
+                nil current-session)))
     (xwidget-put xw 'callback callback)
     (xwidget-webkit-mode)
     (xwidget-webkit-goto-uri (xwidget-webkit-last-session) url)))
 
+(defun xwidget-webkit-import-widget (xwidget)
+  "Create a new webkit session buffer from XWIDGET, an existing xwidget.
+Return the buffer."
+  (let* ((bufname (generate-new-buffer-name "*xwidget-webkit*"))
+         (callback #'xwidget-webkit-callback)
+         (buffer (get-buffer-create bufname)))
+    (with-current-buffer buffer
+      (save-excursion
+        (erase-buffer)
+        (insert ".")
+        (put-text-property (point-min) (point-max)
+                           'display (list 'xwidget :xwidget xwidget)))
+      (xwidget-put xwidget 'callback callback)
+      (set-xwidget-buffer xwidget buffer)
+      (xwidget-webkit-mode))
+    buffer))
+
+(defun xwidget-webkit-display-event (event)
+  "Import the xwidget inside EVENT and display it."
+  (interactive "e")
+  (display-buffer (xwidget-webkit-import-widget (nth 1 event))))
+
+(global-set-key [xwidget-display-event] 'xwidget-webkit-display-event)
 
 (defun xwidget-webkit-goto-url (url)
   "Goto URL with xwidget webkit."
@@ -684,6 +785,165 @@ xwidget-put
   (set-xwidget-plist xwidget
                      (plist-put (xwidget-plist xwidget) propname value)))
 
+(defvar xwidget-webkit-edit-mode-map (make-keymap))
+
+(define-key xwidget-webkit-edit-mode-map [backspace] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [tab] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [return] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [C-return] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [S-return] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-left] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-right] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-up] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-down] 'xwidget-webkit-pass-command-event)
+(define-key xwidget-webkit-edit-mode-map [M-return] 'xwidget-webkit-pass-command-event)
+
+(define-minor-mode xwidget-webkit-edit-mode
+  "Minor mode for editing the content of WebKit buffers.
+
+This defines most self-inserting characters and some common
+keyboard shortcuts to `xwidget-webkit-pass-command-event', which
+will pass the key events corresponding to these characters to the
+WebKit widget."
+  :keymap xwidget-webkit-edit-mode-map)
+
+(substitute-key-definition 'self-insert-command
+                           'xwidget-webkit-pass-command-event
+                           xwidget-webkit-edit-mode-map
+                           global-map)
+
+(declare-function xwidget-webkit-search "xwidget.c")
+(declare-function xwidget-webkit-next-result "xwidget.c")
+(declare-function xwidget-webkit-previous-result "xwidget.c")
+(declare-function xwidget-webkit-finish-search "xwidget.c")
+
+(defvar-local xwidget-webkit-isearch--string ""
+  "The current search query.")
+(defvar-local xwidget-webkit-isearch--is-reverse nil
+  "Whether or not the current isearch should be reverse.")
+
+(defun xwidget-webkit-isearch--update (&optional only-message)
+  "Update the current buffer's WebKit widget's search query.
+If ONLY-MESSAGE is non-nil, the query will not be sent to the
+WebKit widget.  The query will be set to the contents of
+`xwidget-webkit-isearch--string'."
+  (unless only-message
+    (xwidget-webkit-search xwidget-webkit-isearch--string
+                           (xwidget-webkit-current-session)
+                           t xwidget-webkit-isearch--is-reverse t))
+  (message (concat (propertize "Search contents: " 'face 'minibuffer-prompt)
+                   xwidget-webkit-isearch--string)))
+
+(defun xwidget-webkit-isearch-erasing-char (count)
+  "Erase the last COUNT characters of the current query."
+  (interactive (list (prefix-numeric-value current-prefix-arg)))
+  (when (> (length xwidget-webkit-isearch--string) 0)
+    (setq xwidget-webkit-isearch--string
+          (substring xwidget-webkit-isearch--string 0
+                     (- (length xwidget-webkit-isearch--string) count))))
+  (xwidget-webkit-isearch--update))
+
+(defun xwidget-webkit-isearch-printing-char (char &optional count)
+  "Add ordinary character CHAR to the search string and search.
+With argument, add COUNT copies of CHAR."
+  (interactive (list last-command-event
+                     (prefix-numeric-value current-prefix-arg)))
+  (setq xwidget-webkit-isearch--string (concat xwidget-webkit-isearch--string
+                                               (make-string (or count 1) char)))
+  (xwidget-webkit-isearch--update))
+
+(defun xwidget-webkit-isearch-forward (count)
+  "Move to the next search result COUNT times."
+  (interactive (list (prefix-numeric-value current-prefix-arg)))
+  (let ((was-reverse xwidget-webkit-isearch--is-reverse))
+    (setq xwidget-webkit-isearch--is-reverse nil)
+    (when was-reverse
+      (xwidget-webkit-isearch--update)))
+  (let ((i 0))
+    (while (< i count)
+      (xwidget-webkit-next-result (xwidget-webkit-current-session))
+      (cl-incf i)))
+  (xwidget-webkit-isearch--update t))
+
+(defun xwidget-webkit-isearch-backward (count)
+  "Move to the previous search result COUNT times."
+  (interactive (list (prefix-numeric-value current-prefix-arg)))
+  (let ((was-reverse xwidget-webkit-isearch--is-reverse))
+    (setq xwidget-webkit-isearch--is-reverse t)
+    (unless was-reverse
+      (xwidget-webkit-isearch--update)))
+  (let ((i 0))
+    (while (< i count)
+      (xwidget-webkit-next-result (xwidget-webkit-current-session))
+      (cl-incf i)))
+  (xwidget-webkit-isearch--update t))
+
+(defun xwidget-webkit-isearch-exit ()
+  "Exit incremental search of a WebKit buffer."
+  (interactive)
+  (xwidget-webkit-isearch-mode 0))
+
+(defvar xwidget-webkit-isearch-mode-map (make-keymap)
+  "The keymap used inside xwidget-webkit-isearch-mode.")
+
+(set-char-table-range (nth 1 xwidget-webkit-isearch-mode-map)
+                      (cons 0 (max-char))
+                      'xwidget-webkit-isearch-exit)
+
+(substitute-key-definition 'self-insert-command
+                           'xwidget-webkit-isearch-printing-char
+                           xwidget-webkit-isearch-mode-map
+                           global-map)
+
+(define-key xwidget-webkit-isearch-mode-map (kbd "DEL")
+  'xwidget-webkit-isearch-erasing-char)
+(define-key xwidget-webkit-isearch-mode-map [return] 'xwidget-webkit-isearch-exit)
+(define-key xwidget-webkit-isearch-mode-map "\r" 'xwidget-webkit-isearch-exit)
+(define-key xwidget-webkit-isearch-mode-map "\C-g" 'xwidget-webkit-isearch-exit)
+(define-key xwidget-webkit-isearch-mode-map "\C-r" 'xwidget-webkit-isearch-backward)
+(define-key xwidget-webkit-isearch-mode-map "\C-s" 'xwidget-webkit-isearch-forward)
+(define-key xwidget-webkit-isearch-mode-map "\t" 'xwidget-webkit-isearch-printing-char)
+
+(let ((meta-map (make-keymap)))
+  (set-char-table-range (nth 1 meta-map)
+                        (cons 0 (max-char))
+                        'xwidget-webkit-isearch-exit)
+  (define-key xwidget-webkit-isearch-mode-map (char-to-string meta-prefix-char) meta-map))
+
+(define-minor-mode xwidget-webkit-isearch-mode
+  "Minor mode for performing incremental search inside WebKit buffers.
+
+An attempt was made for this to resemble regular incremental
+search, but it suffers from several limitations, such as not
+supporting recursive edits.
+
+If this mode is enabled with `C-r', then the search will default
+to being performed in reverse direction.
+
+To navigate around the search results, type
+\\[xwidget-webkit-isearch-forward] to move forward, and
+\\[xwidget-webkit-isearch-backward] to move backward.
+
+Press \\[xwidget-webkit-isearch-exit] to exit incremental search."
+  :keymap xwidget-webkit-isearch-mode-map
+  (if xwidget-webkit-isearch-mode
+      (progn
+        (setq xwidget-webkit-isearch--string "")
+        (setq xwidget-webkit-isearch--is-reverse (eq last-command-event ?\C-r))
+        (xwidget-webkit-isearch--update))
+    (xwidget-webkit-finish-search (xwidget-webkit-current-session))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
diff --git a/src/dispextern.h b/src/dispextern.h
index 5b28fe7666..f17f095e0d 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -536,8 +536,8 @@ #define FACE_ID_BITS	20
     int img_id;
 
 #ifdef HAVE_XWIDGETS
-    /* Xwidget reference (type == XWIDGET_GLYPH).  */
-    struct xwidget *xwidget;
+    /* Xwidget ID.  */
+    uint32_t xwidget;
 #endif
 
     /* Sub-structure for type == STRETCH_GLYPH.  */
diff --git a/src/dispnew.c b/src/dispnew.c
index 4a73244c89..632eec2f03 100644
--- a/src/dispnew.c
+++ b/src/dispnew.c
@@ -4449,16 +4449,6 @@ scrolling_window (struct window *w, int tab_line_p)
 	break;
     }
 
-#ifdef HAVE_XWIDGETS
-  /* Currently this seems needed to detect xwidget movement reliably.
-     This is most probably because an xwidget glyph is represented in
-     struct glyph's 'union u' by a pointer to a struct, which takes 8
-     bytes in 64-bit builds, and thus the comparison of u.val values
-     done by GLYPH_EQUAL_P doesn't work reliably, since it assumes the
-     size of the union is 4 bytes.  FIXME.  */
-    return 0;
-#endif
-
   /* Can't scroll the display of w32 GUI frames when position of point
      is indicated by the system caret, because scrolling the display
      will then "copy" the pixels used by the caret.  */
diff --git a/src/keyboard.c b/src/keyboard.c
index aa6a4b9e97..c4a5671b10 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -3993,6 +3993,7 @@ kbd_buffer_get_event (KBOARD **kbp,
 #endif
 #ifdef HAVE_XWIDGETS
       case XWIDGET_EVENT:
+      case XWIDGET_DISPLAY_EVENT:
 #endif
       case SAVE_SESSION_EVENT:
       case NO_EVENT:
@@ -4897,7 +4898,7 @@ #define FUNCTION_KEY_OFFSET 0xff00
 
 /* You'll notice that this table is arranged to be conveniently
    indexed by X Windows keysym values.  */
-static const char *const lispy_function_keys[] =
+const char *const lispy_function_keys[] =
   {
     /* X Keysym value */
 
@@ -6139,6 +6140,11 @@ make_lispy_event (struct input_event *event)
       {
         return Fcons (Qxwidget_event, event->arg);
       }
+
+    case XWIDGET_DISPLAY_EVENT:
+      {
+	return list2 (Qxwidget_display_event, event->arg);
+      }
 #endif
 
 #ifdef USE_FILE_NOTIFY
@@ -11732,6 +11738,7 @@ syms_of_keyboard (void)
 
 #ifdef HAVE_XWIDGETS
   DEFSYM (Qxwidget_event, "xwidget-event");
+  DEFSYM (Qxwidget_display_event, "xwidget-display-event");
 #endif
 
 #ifdef USE_FILE_NOTIFY
diff --git a/src/keyboard.h b/src/keyboard.h
index 8bdffaa2bf..21c51ec386 100644
--- a/src/keyboard.h
+++ b/src/keyboard.h
@@ -491,7 +491,7 @@ kbd_buffer_store_event_hold (struct input_event *event,
 extern struct timespec timer_check (void);
 extern void mark_kboards (void);
 
-#ifdef HAVE_NTGUI
+#if defined HAVE_NTGUI || defined HAVE_X_WINDOWS
 extern const char *const lispy_function_keys[];
 #endif
 
diff --git a/src/print.c b/src/print.c
index c13294c8e6..adadb289de 100644
--- a/src/print.c
+++ b/src/print.c
@@ -1521,8 +1521,26 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
       printchar ('>', printcharfun);
       break;
 
-    case PVEC_XWIDGET: case PVEC_XWIDGET_VIEW:
-      print_c_string ("#<xwidget ", printcharfun);
+    case PVEC_XWIDGET:
+#ifdef HAVE_XWIDGETS
+      {
+#ifdef USE_GTK
+	int len = sprintf (buf, "#<xwidget %u %p>",
+			   XXWIDGET (obj)->xwidget_id,
+			   XXWIDGET (obj)->widget_osr);
+#else
+	int len = sprintf (buf, "#<xwidget %u %p>",
+			   XXWIDGET (obj)->xwidget_id,
+			   XXWIDGET (obj)->xwWidget);
+#endif
+	strout (buf, len, len, printcharfun);
+	break;
+      }
+#else
+      emacs_abort ();
+#endif
+    case PVEC_XWIDGET_VIEW:
+      print_c_string ("#<xwidget view", printcharfun);
       printchar ('>', printcharfun);
       break;
 
diff --git a/src/termhooks.h b/src/termhooks.h
index 1d3cdc8fe8..e7539bbce2 100644
--- a/src/termhooks.h
+++ b/src/termhooks.h
@@ -255,6 +255,8 @@ #define EMACS_TERMHOOKS_H
 #ifdef HAVE_XWIDGETS
   /* events generated by xwidgets*/
    , XWIDGET_EVENT
+   /* Event generated when WebKit asks us to display another widget.  */
+   , XWIDGET_DISPLAY_EVENT
 #endif
 
 #ifdef USE_FILE_NOTIFY
diff --git a/src/xdisp.c b/src/xdisp.c
index 86c4e704d5..d7ad548917 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -28429,7 +28429,7 @@ fill_xwidget_glyph_string (struct glyph_string *s)
     }
   s->width = s->first_glyph->pixel_width;
   s->ybase += s->first_glyph->voffset;
-  s->xwidget = s->first_glyph->u.xwidget;
+  s->xwidget = xwidget_from_id (s->first_glyph->u.xwidget);
 }
 #endif
 /* Fill glyph string S from a sequence of stretch glyphs.
@@ -29832,7 +29832,7 @@ produce_xwidget_glyph (struct it *it)
           glyph->padding_p = 0;
 	  glyph->glyph_not_available_p = 0;
 	  glyph->face_id = it->face_id;
-          glyph->u.xwidget = it->xwidget;
+          glyph->u.xwidget = it->xwidget->xwidget_id;
 	  glyph->font_type = FONT_TYPE_UNKNOWN;
 	  if (it->bidi_p)
 	    {
diff --git a/src/xterm.c b/src/xterm.c
index aa1a1a5eed..9b434bffcc 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -4390,6 +4390,86 @@ x_scroll_run (struct window *w, struct run *run)
   /* Cursor off.  Will be switched on again in gui_update_window_end.  */
   gui_clear_cursor (w);
 
+#ifdef HAVE_XWIDGETS
+  /* "Copy" xwidget windows in the area that will be scrolled.  */
+  Display *dpy = FRAME_X_DISPLAY (f);
+  Window window = FRAME_X_WINDOW (f);
+
+  Window root, parent, *children;
+  unsigned int nchildren;
+
+  if (XQueryTree (dpy, window, &root, &parent, &children, &nchildren))
+    {
+      /* Now find xwidget views situated between from_y and to_y, and
+	 attached to w.  */
+      for (unsigned int i = 0; i < nchildren; ++i)
+	{
+	  Window child = children[i];
+	  struct xwidget_view *view = xwidget_view_from_window (child);
+
+	  if (view)
+	    {
+	      int window_y = view->y + view->clip_top;
+	      int window_height = view->clip_bottom - view->clip_top;
+
+	      Emacs_Rectangle r1, r2, result;
+	      r1.x = w->pixel_left;
+	      r1.y = from_y;
+	      r1.width = w->pixel_width;
+	      r1.height = height;
+	      r2 = r1;
+	      r2.y = window_y;
+	      r2.height = window_height;
+
+	      /* The window is offscreen, just unmap it.  */
+	      if (window_height == 0)
+		{
+		  view->hidden = true;
+		  XUnmapWindow (dpy, child);
+		  continue;
+		}
+
+	      bool intersects_p =
+		gui_intersect_rectangles (&r1, &r2, &result);
+
+	      if (XWINDOW (view->w) == w && intersects_p)
+		{
+		  int y = view->y + (to_y - from_y);
+		  int text_area_x, text_area_y, text_area_width, text_area_height;
+		  int clip_top, clip_bottom;
+
+		  window_box (w, TEXT_AREA, &text_area_x, &text_area_y,
+			      &text_area_width, &text_area_height);
+
+		  clip_top = max (0, text_area_y - y);
+		  clip_bottom = max (clip_top,
+				     min (XXWIDGET (view->model)->height,
+					  text_area_y + text_area_height - y));
+
+		  view->y = y;
+		  view->clip_top = clip_top;
+		  view->clip_bottom = clip_bottom;
+
+		  /* This means the view has moved offscreen.  Unmap
+		     it and hide it here.  */
+		  if ((view->clip_top - view->clip_bottom) <= 0)
+		    {
+		      view->hidden = true;
+		      XUnmapWindow (dpy, child);
+		    }
+		  else
+		    XMoveResizeWindow (dpy, child, view->x + view->clip_left,
+				       view->y + view->clip_top,
+				       view->clip_right - view->clip_left,
+				       view->clip_top - view->clip_bottom);
+		  XFlush (dpy);
+		}
+            }
+	}
+      XFree (children);
+    }
+#endif
+
 #ifdef USE_CAIRO
   if (FRAME_CR_CONTEXT (f))
     {
@@ -4563,8 +4643,9 @@ x_focus_changed (int type, int state, struct x_display_info *dpyinfo, struct fra
     }
 }
 
-/* Return the Emacs frame-object corresponding to an X window.
-   It could be the frame's main window or an icon window.  */
+/* Return the Emacs frame-object corresponding to an X window.  It
+   could be the frame's main window, an icon window, or an xwidget
+   window.  */
 
 static struct frame *
 x_window_to_frame (struct x_display_info *dpyinfo, int wdesc)
@@ -4575,6 +4656,13 @@ x_window_to_frame (struct x_display_info *dpyinfo, int wdesc)
   if (wdesc == None)
     return NULL;
 
+#ifdef HAVE_XWIDGETS
+  struct xwidget_view *xvw = xwidget_view_from_window (wdesc);
+
+  if (xvw && xvw->frame)
+    return xvw->frame;
+#endif
+
   FOR_EACH_FRAME (tail, frame)
     {
       f = XFRAME (frame);
@@ -4997,7 +5085,7 @@ x_x_to_emacs_modifiers (struct x_display_info *dpyinfo, int state)
             | ((state & dpyinfo->hyper_mod_mask)	? mod_hyper	: 0));
 }
 
-static int
+int
 x_emacs_to_x_modifiers (struct x_display_info *dpyinfo, intmax_t state)
 {
   EMACS_INT mod_ctrl = ctrl_modifier;
@@ -8211,6 +8299,18 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 
     case Expose:
       f = x_window_to_frame (dpyinfo, event->xexpose.window);
+#ifdef HAVE_XWIDGETS
+      {
+	struct xwidget_view *xv =
+	  xwidget_view_from_window (event->xexpose.window);
+
+	if (xv)
+	  {
+	    xwidget_expose (xv);
+	    goto OTHER;
+	  }
+      }
+#endif
       if (f)
         {
           if (!FRAME_VISIBLE_P (f))
@@ -8791,6 +8891,31 @@ handle_one_xevent (struct x_display_info *dpyinfo,
       x_display_set_last_user_time (dpyinfo, event->xcrossing.time);
       x_detect_focus_change (dpyinfo, any, event, &inev.ie);
 
+#ifdef HAVE_XWIDGETS
+      {
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xcrossing.window);
+	Mouse_HLInfo *hlinfo;
+
+	if (xvw)
+	  {
+	    xwidget_motion_or_crossing (xvw, event);
+	    hlinfo = MOUSE_HL_INFO (xvw->frame);
+
+	    if (xvw->frame == hlinfo->mouse_face_mouse_frame)
+	      {
+		clear_mouse_face (hlinfo);
+		hlinfo->mouse_face_mouse_frame = 0;
+	      }
+
+	    if (any_help_event_p)
+	      {
+		do_help = -1;
+	      }
+	    goto OTHER;
+	  }
+      }
+#endif
+
       f = any;
 
       if (f && x_mouse_click_focus_ignore_position)
@@ -8834,6 +8959,17 @@ handle_one_xevent (struct x_display_info *dpyinfo,
       goto OTHER;
 
     case LeaveNotify:
+#ifdef HAVE_XWIDGETS
+      {
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xcrossing.window);
+
+	if (xvw)
+	  {
+	    xwidget_motion_or_crossing (xvw, event);
+	    goto OTHER;
+	  }
+      }
+#endif
       x_display_set_last_user_time (dpyinfo, event->xcrossing.time);
       x_detect_focus_change (dpyinfo, any, event, &inev.ie);
 
@@ -8883,6 +9019,12 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 #ifdef USE_GTK
         if (f && xg_event_is_for_scrollbar (f, event))
           f = 0;
+#endif
+#ifdef HAVE_XWIDGETS
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xmotion.window);
+
+	if (xvw)
+	  xwidget_motion_or_crossing (xvw, event);
 #endif
         if (f)
           {
@@ -9138,6 +9280,24 @@ handle_one_xevent (struct x_display_info *dpyinfo,
     case ButtonRelease:
     case ButtonPress:
       {
+#ifdef HAVE_XWIDGETS
+	struct xwidget_view *xvw = xwidget_view_from_window (event->xmotion.window);
+
+	if (xvw)
+	  {
+	    xwidget_button (xvw, event->type == ButtonPress,
+			    event->xbutton.x, event->xbutton.y,
+			    event->xbutton.button, event->xbutton.state,
+			    event->xbutton.time);
+
+	    if (!EQ (selected_window, xvw->w))
+	      {
+		inev.ie.kind = SELECT_WINDOW_EVENT;
+		inev.ie.frame_or_window = xvw->w;
+	      }
+	    goto OTHER;
+	  }
+#endif
         /* If we decide we want to generate an event to be seen
            by the rest of Emacs, we put it here.  */
         Lisp_Object tab_bar_arg = Qnil;
@@ -12108,6 +12268,10 @@ x_free_frame_resources (struct frame *f)
 	xfree (f->shell_position);
 #else  /* !USE_X_TOOLKIT */
 
+#ifdef HAVE_XWIDGETS
+      kill_frame_xwidget_views (f);
+#endif
+
 #ifdef USE_GTK
       xg_free_frame_widgets (f);
 #endif /* USE_GTK */
diff --git a/src/xterm.h b/src/xterm.h
index de6ea50385..9d9534dd62 100644
--- a/src/xterm.h
+++ b/src/xterm.h
@@ -1108,6 +1108,7 @@ #define SELECTION_EVENT_TIME(eventp)	\
 extern int x_dispatch_event (XEvent *, Display *);
 #endif
 extern int x_x_to_emacs_modifiers (struct x_display_info *, int);
+extern int x_emacs_to_x_modifiers (struct x_display_info *, intmax_t);
 #ifdef USE_CAIRO
 extern void x_cr_destroy_frame_context (struct frame *);
 extern void x_cr_update_surface_desired_size (struct frame *, int, int);
diff --git a/src/xwidget.c b/src/xwidget.c
index e4b42e6e0c..78a3860490 100644
--- a/src/xwidget.c
+++ b/src/xwidget.c
@@ -19,6 +19,7 @@ Copyright (C) 2011-2021 Free Software Foundation, Inc.
 
 #include <config.h>
 
+#include "buffer.h"
 #include "xwidget.h"
 
 #include "lisp.h"
@@ -35,10 +36,22 @@ Copyright (C) 2011-2021 Free Software Foundation, Inc.
 #ifdef USE_GTK
 #include <webkit2/webkit2.h>
 #include <JavaScriptCore/JavaScript.h>
+#include <cairo.h>
+#include <X11/Xlib.h>
 #elif defined NS_IMPL_COCOA
 #include "nsxwidget.h"
 #endif
 
+static Lisp_Object id_to_xwidget_map;
+static uint32_t xwidget_counter = 0;
+
+#ifdef USE_GTK
+static Lisp_Object x_window_to_xwv_map;
+static gboolean offscreen_damage_event (GtkWidget *, GdkEvent *, gpointer);
+static void synthesize_focus_in_event (GtkWidget *);
+static GdkDevice *find_suitable_keyboard (struct frame *);
+#endif
+
 static struct xwidget *
 allocate_xwidget (void)
 {
@@ -64,18 +77,32 @@ #define XSETXWIDGET_VIEW(a, b) XSETPSEUDOVECTOR (a, b, PVEC_XWIDGET_VIEW)
                                            GAsyncResult *,
                                            gpointer);
 static gboolean webkit_download_cb (WebKitWebContext *, WebKitDownload *, gpointer);
-
+static GtkWidget *webkit_create_cb (WebKitWebView *, WebKitNavigationAction *, gpointer);
 static gboolean
 webkit_decide_policy_cb (WebKitWebView *,
                          WebKitPolicyDecision *,
                          WebKitPolicyDecisionType,
                          gpointer);
+static GtkWidget *find_widget_at_pos (GtkWidget *, int, int, int *, int *);
+
+struct widget_search_data
+{
+  int x;
+  int y;
+  bool foundp;
+  bool first;
+  GtkWidget *data;
+};
+
+static void find_widget (GtkWidget *t, struct widget_search_data *);
+static void mouse_target_changed (WebKitWebView *, WebKitHitTestResult *, guint,
+				  gpointer);
 #endif
 
 
 DEFUN ("make-xwidget",
        Fmake_xwidget, Smake_xwidget,
-       5, 6, 0,
+       5, 7, 0,
        doc: /* Make an xwidget of TYPE.
 If BUFFER is nil, use the current buffer.
 If BUFFER is a string and no such buffer exists, create it.
@@ -83,10 +110,13 @@ DEFUN ("make-xwidget",
 
 - webkit
 
-Returns the newly constructed xwidget, or nil if construction fails.  */)
+RELATED is nil, or an xwidget.  When constructing a WebKit widget, it
+will share the same settings and internal subprocess as RELATED.
+Returns the newly constructed xwidget, or nil if construction
+fails.  */)
   (Lisp_Object type,
    Lisp_Object title, Lisp_Object width, Lisp_Object height,
-   Lisp_Object arguments, Lisp_Object buffer)
+   Lisp_Object arguments, Lisp_Object buffer, Lisp_Object related)
 {
 #ifdef USE_GTK
   if (!xg_gtk_initialized)
@@ -108,13 +138,19 @@ DEFUN ("make-xwidget",
   XSETXWIDGET (val, xw);
   Vxwidget_list = Fcons (val, Vxwidget_list);
   xw->plist = Qnil;
+  xw->xwidget_id = ++xwidget_counter;
+  xw->find_text = NULL;
+
+  Fputhash (make_fixnum (xw->xwidget_id), val, id_to_xwidget_map);
 
 #ifdef USE_GTK
   xw->widgetwindow_osr = NULL;
   xw->widget_osr = NULL;
+  xw->hit_result = 0;
   if (EQ (xw->type, Qwebkit))
     {
       block_input ();
+      WebKitSettings *settings;
       WebKitWebContext *webkit_context = webkit_web_context_get_default ();
 
 # if WEBKIT_CHECK_VERSION (2, 26, 0)
@@ -128,18 +164,34 @@ DEFUN ("make-xwidget",
 
       if (EQ (xw->type, Qwebkit))
         {
-          xw->widget_osr = webkit_web_view_new ();
-
-          /* webkitgtk uses GSubprocess which sets sigaction causing
-             Emacs to not catch SIGCHLD with its usual handle setup in
-             catch_child_signal().  This resets the SIGCHLD
-             sigaction.  */
-          struct sigaction old_action;
-          sigaction (SIGCHLD, NULL, &old_action);
-          webkit_web_view_load_uri(WEBKIT_WEB_VIEW (xw->widget_osr),
-                                   "about:blank");
-          sigaction (SIGCHLD, &old_action, NULL);
-        }
+	  WebKitWebView *related_view;
+
+	  if (NILP (related)
+	      || !XWIDGETP (related)
+	      || !EQ (XXWIDGET (related)->type, Qwebkit))
+	    {
+	      xw->widget_osr = webkit_web_view_new ();
+
+	      /* webkitgtk uses GSubprocess which sets sigaction causing
+		 Emacs to not catch SIGCHLD with its usual handle setup in
+		 catch_child_signal().  This resets the SIGCHLD
+		 sigaction.  */
+	      struct sigaction old_action;
+	      sigaction (SIGCHLD, NULL, &old_action);
+	      webkit_web_view_load_uri (WEBKIT_WEB_VIEW (xw->widget_osr),
+					"about:blank");
+	      sigaction (SIGCHLD, &old_action, NULL);
+	    }
+	  else
+	    {
+	      related_view = WEBKIT_WEB_VIEW (XXWIDGET (related)->widget_osr);
+	      xw->widget_osr = webkit_web_view_new_with_related_view (related_view);
+	    }
+
+	  /* Enable the developer extras */
+	  settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (xw->widget_osr));
+	  g_object_set (G_OBJECT (settings), "enable-developer-extras", TRUE, NULL);
+	}
 
       gtk_widget_set_size_request (GTK_WIDGET (xw->widget_osr), xw->width,
                                    xw->height);
@@ -157,6 +209,7 @@ DEFUN ("make-xwidget",
 
       gtk_widget_show (xw->widget_osr);
       gtk_widget_show (xw->widgetwindow_osr);
+      synthesize_focus_in_event (xw->widgetwindow_osr);
 
       /* Store some xwidget data in the gtk widgets for convenient
          retrieval in the event handlers.  */
@@ -179,8 +232,20 @@ DEFUN ("make-xwidget",
                             G_CALLBACK
                             (webkit_decide_policy_cb),
                             xw);
+
+	  g_signal_connect (G_OBJECT (xw->widget_osr),
+			    "mouse-target-changed",
+			    G_CALLBACK (mouse_target_changed),
+			    xw);
+	  g_signal_connect (G_OBJECT (xw->widget_osr),
+			    "create",
+			    G_CALLBACK (webkit_create_cb),
+			    xw);
         }
 
+      g_signal_connect (G_OBJECT (xw->widgetwindow_osr), "damage-event",
+			G_CALLBACK (offscreen_damage_event), xw);
+
       unblock_input ();
     }
 #elif defined NS_IMPL_COCOA
@@ -190,6 +255,158 @@ DEFUN ("make-xwidget",
   return val;
 }
 
+#ifdef USE_GTK
+static void
+set_widget_if_text_view (GtkWidget *widget, void *data)
+{
+  GtkWidget **pointer = data;
+
+  if (GTK_IS_TEXT_VIEW (widget))
+    {
+      *pointer = widget;
+    }
+}
+#endif
+
+DEFUN ("xwidget-perform-lispy-event",
+       Fxwidget_perform_lispy_event, Sxwidget_perform_lispy_event,
+       2, 3, 0, doc: /* Send a lispy event to XWIDGET.
+EVENT should be the event that will be sent.  FRAME should be the
+frame which generated the event, or nil.  On X11, modifier keys will
+not be processed if FRAME is nil and the selected frame is not an
+X-Windows frame.  */)
+  (Lisp_Object xwidget, Lisp_Object event, Lisp_Object frame)
+{
+  struct xwidget *xw;
+  struct frame *f = NULL;
+  int character = -1, keycode = -1;
+  int modifiers = 0;
+
+#ifdef USE_GTK
+  GdkEvent *xg_event;
+  GtkContainerClass *klass;
+  GtkWidget *widget;
+  GtkWidget *temp = NULL;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!NILP (frame))
+    f = decode_window_system_frame (frame);
+  else if (FRAME_X_P (SELECTED_FRAME ()))
+    f = SELECTED_FRAME ();
+
+#ifdef USE_GTK
+  widget = gtk_window_get_focus (GTK_WINDOW (xw->widgetwindow_osr));
+
+  if (!widget)
+    widget = xw->widget_osr;
+
+  if (RANGED_FIXNUMP (0, event, INT_MAX))
+    {
+      character = XFIXNUM (event);
+
+      if (character < 32)
+	modifiers |= ctrl_modifier;
+
+      modifiers |= character & meta_modifier;
+      modifiers |= character & hyper_modifier;
+      modifiers |= character & super_modifier;
+      modifiers |= character & shift_modifier;
+      modifiers |= character & ctrl_modifier;
+
+      character = character & ~(1 << 21);
+
+      if (character < 32)
+	character += '_';
+
+      if (f)
+	modifiers = x_emacs_to_x_modifiers (FRAME_DISPLAY_INFO (f), modifiers);
+      else
+	modifiers = 0;
+    }
+  else if (SYMBOLP (event))
+    {
+      Lisp_Object decoded = parse_modifiers (event);
+      Lisp_Object decoded_name = SYMBOL_NAME (XCAR (decoded));
+
+      int off = 0;
+      bool found = false;
+
+      while (off < 256)
+	{
+	  if (lispy_function_keys[off]
+	      && !strcmp (lispy_function_keys[off],
+			  SSDATA (decoded_name)))
+	    {
+	      found = true;
+	      break;
+	    }
+	  ++off;
+	}
+
+      if (f)
+	modifiers = x_emacs_to_x_modifiers (FRAME_DISPLAY_INFO (f),
+					    XFIXNUM (XCAR (XCDR (decoded))));
+      else
+	modifiers = 0;
+
+      if (found)
+	keycode = off + 0xff00;
+    }
+
+  if (character == -1 && keycode == -1)
+    return Qnil;
+
+  block_input ();
+  xg_event = gdk_event_new (GDK_KEY_PRESS);
+  xg_event->any.window = gtk_widget_get_window (xw->widget_osr);
+  g_object_ref (xg_event->any.window);
+
+  if (character > -1)
+    keycode = gdk_unicode_to_keyval (character);
+
+  xg_event->key.keyval = keycode;
+  xg_event->key.state = modifiers;
+
+  if (keycode > -1)
+    {
+      /* WebKitGTK internals abuse follows.  */
+      if (WEBKIT_IS_WEB_VIEW (widget))
+	{
+	  /* WebKitGTK relies on an internal GtkTextView object to
+	     "translate" keys such as backspace.  We must find that
+	     widget and activate its binding to this key if any.  */
+	  klass = GTK_CONTAINER_CLASS (G_OBJECT_GET_CLASS (widget));
+
+	  klass->forall (GTK_CONTAINER (xw->widget_osr), TRUE,
+			 set_widget_if_text_view, &temp);
+
+	  if (GTK_IS_WIDGET (temp))
+	    {
+	      if (!gtk_widget_get_realized (temp))
+		gtk_widget_realize (temp);
+
+	      gtk_bindings_activate (G_OBJECT (temp), keycode, modifiers);
+	    }
+	}
+    }
+
+  if (f)
+    gdk_event_set_device (xg_event,
+			  find_suitable_keyboard (SELECTED_FRAME ()));
+
+  gtk_main_do_event (xg_event);
+  xg_event->type = GDK_KEY_RELEASE;
+  gtk_main_do_event (xg_event);
+  gdk_event_free (xg_event);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
 DEFUN ("get-buffer-xwidgets", Fget_buffer_xwidgets, Sget_buffer_xwidgets,
        1, 1, 0,
        doc: /* Return a list of xwidgets associated with BUFFER.
@@ -221,16 +438,397 @@ xwidget_hidden (struct xwidget_view *xv)
   return xv->hidden;
 }
 
+struct xwidget *
+xwidget_from_id (uint32_t id)
+{
+  Lisp_Object key = make_fixnum (id);
+  Lisp_Object xwidget = Fgethash (key, id_to_xwidget_map, Qnil);
+
+  if (NILP (xwidget))
+    emacs_abort ();
+
+  return XXWIDGET (xwidget);
+}
+
 #ifdef USE_GTK
+
+static GdkDevice *
+find_suitable_pointer (struct frame *f)
+{
+  GdkSeat *seat = gdk_display_get_default_seat
+    (gtk_widget_get_display (FRAME_GTK_WIDGET (f)));
+
+  if (!seat)
+    return NULL;
+
+  return gdk_seat_get_pointer (seat);
+}
+
+static GdkDevice *
+find_suitable_keyboard (struct frame *f)
+{
+  GdkSeat *seat = gdk_display_get_default_seat
+    (gtk_widget_get_display (FRAME_GTK_WIDGET (f)));
+
+  if (!seat)
+    return NULL;
+
+  return gdk_seat_get_keyboard (seat);
+}
+
+static void
+find_widget_cb (GtkWidget *widget, void *user)
+{
+  find_widget (widget, user);
+}
+
+static void
+find_widget (GtkWidget *widget,
+	     struct widget_search_data *data)
+{
+  GtkAllocation new_allocation;
+  GdkWindow *window;
+  int x_offset = 0;
+  int y_offset = 0;
+
+  gtk_widget_get_allocation (widget, &new_allocation);
+
+  if (gtk_widget_get_has_window (widget))
+    {
+      new_allocation.x = 0;
+      new_allocation.y = 0;
+    }
+
+  if (gtk_widget_get_parent (widget) && !data->first)
+    {
+      window = gtk_widget_get_window (widget);
+      while (window != gtk_widget_get_window (gtk_widget_get_parent (widget)))
+        {
+          gint tx, ty, twidth, theight;
+
+	  if (!window)
+	    return;
+
+          twidth = gdk_window_get_width (window);
+          theight = gdk_window_get_height (window);
+
+          if (new_allocation.x < 0)
+            {
+              new_allocation.width += new_allocation.x;
+              new_allocation.x = 0;
+            }
+
+          if (new_allocation.y < 0)
+            {
+              new_allocation.height += new_allocation.y;
+              new_allocation.y = 0;
+            }
+
+          if (new_allocation.x + new_allocation.width > twidth)
+            new_allocation.width = twidth - new_allocation.x;
+          if (new_allocation.y + new_allocation.height > theight)
+            new_allocation.height = theight - new_allocation.y;
+
+          gdk_window_get_position (window, &tx, &ty);
+          new_allocation.x += tx;
+          x_offset += tx;
+          new_allocation.y += ty;
+          y_offset += ty;
+
+          window = gdk_window_get_parent (window);
+	}
+    }
+
+  if ((data->x >= new_allocation.x) && (data->y >= new_allocation.y) &&
+      (data->x < new_allocation.x + new_allocation.width) &&
+      (data->y < new_allocation.y + new_allocation.height))
+    {
+      /* First, check if the drag is in a valid drop site in
+       * one of our children
+       */
+      if (GTK_IS_CONTAINER (widget))
+        {
+          struct widget_search_data new_data = *data;
+
+          new_data.x -= x_offset;
+          new_data.y -= y_offset;
+          new_data.foundp = false;
+          new_data.first = false;
+
+          gtk_container_forall (GTK_CONTAINER (widget),
+                                find_widget_cb, &new_data);
+
+          data->foundp = new_data.foundp;
+          if (data->foundp)
+            data->data = new_data.data;
+        }
+
+      /* If not, and this widget is registered as a drop site, check to
+       * emit "drag_motion" to check if we are actually in
+       * a drop site.
+       */
+      if (!data->foundp)
+        {
+          data->foundp = true;
+          data->data = widget;
+        }
+    }
+}
+
+static GtkWidget *
+find_widget_at_pos (GtkWidget *w, int x, int y,
+		    int *new_x, int *new_y)
+{
+  struct widget_search_data data;
+
+  data.x = x;
+  data.y = y;
+  data.foundp = false;
+  data.first = true;
+
+  find_widget (w, &data);
+
+  if (data.foundp)
+    {
+      gtk_widget_translate_coordinates (w, data.data, x,
+					y, new_x, new_y);
+      return data.data;
+    }
+
+  *new_x = x;
+  *new_y = y;
+
+  return NULL;
+}
+
+static Emacs_Cursor
+cursor_for_hit (guint result, struct frame *frame)
+{
+  Emacs_Cursor cursor = FRAME_OUTPUT_DATA (frame)->nontext_cursor;
+
+  if ((result & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)
+      || (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION)
+      || (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT))
+    cursor = FRAME_X_OUTPUT (frame)->text_cursor;
+
+  if (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR)
+    cursor = FRAME_X_OUTPUT (frame)->vertical_drag_cursor;
+
+  if (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK)
+    cursor = FRAME_X_OUTPUT (frame)->hand_cursor;
+
+  return cursor;
+}
+
+static void
+define_cursors (struct xwidget *xw, WebKitHitTestResult *res)
+{
+  struct xwidget_view *xvw;
+
+  xw->hit_result = webkit_hit_test_result_get_context (res);
+
+  for (Lisp_Object tem = Vxwidget_view_list; CONSP (tem);
+       tem = XCDR (tem))
+    {
+      if (XWIDGET_VIEW_P (XCAR (tem)))
+	{
+	  xvw = XXWIDGET_VIEW (XCAR (tem));
+
+	  if (XXWIDGET (xvw->model) == xw)
+	    {
+	      xvw->cursor = cursor_for_hit (xw->hit_result, xvw->frame);
+	      if (xvw->wdesc != None)
+		XDefineCursor (xvw->dpy, xvw->wdesc, xvw->cursor);
+	    }
+	}
+    }
+}
+
+static void
+mouse_target_changed (WebKitWebView *webview,
+		      WebKitHitTestResult *hitresult,
+		      guint modifiers, gpointer xw)
+{
+  define_cursors (xw, hitresult);
+}
+
+
+static void
+xwidget_button_1 (struct xwidget_view *view,
+		  bool down_p, int x, int y, int button,
+		  int modifier_state, Time time)
+{
+  GdkEvent *xg_event = gdk_event_new (down_p ? GDK_BUTTON_PRESS : GDK_BUTTON_RELEASE);
+  struct xwidget *model = XXWIDGET (view->model);
+  GtkWidget *target;
+
+  /* X and Y should be relative to the origin of view->wdesc.  */
+  x += view->clip_left;
+  y += view->clip_top;
+
+  target = find_widget_at_pos (model->widgetwindow_osr, x, y, &x, &y);
+
+  if (!target)
+    target = model->widget_osr;
+
+  xg_event->any.window = gtk_widget_get_window (target);
+  g_object_ref (xg_event->any.window); /* The window will be unrefed
+					  later by gdk_event_free. */
+
+  xg_event->button.x = x;
+  xg_event->button.x_root = x;
+  xg_event->button.y = y;
+  xg_event->button.y_root = y;
+  xg_event->button.button = button;
+  xg_event->button.state = modifier_state;
+  xg_event->button.time = time;
+  xg_event->button.device = find_suitable_pointer (view->frame);
+
+  gtk_main_do_event (xg_event);
+  gdk_event_free (xg_event);
+}
+
+void
+xwidget_button (struct xwidget_view *view,
+		bool down_p, int x, int y, int button,
+		int modifier_state, Time time)
+{
+  if (button < 4 || button > 8)
+    xwidget_button_1 (view, down_p, x, y, button, modifier_state, time);
+  else
+    {
+      GdkEvent *xg_event = gdk_event_new (GDK_SCROLL);
+      struct xwidget *model = XXWIDGET (view->model);
+      GtkWidget *target;
+
+      x += view->clip_left;
+      y += view->clip_top;
+
+      target = find_widget_at_pos (model->widgetwindow_osr, x, y, &x, &y);
+
+      if (!target)
+	target = model->widget_osr;
+
+      xg_event->any.window = gtk_widget_get_window (target);
+      g_object_ref (xg_event->any.window); /* The window will be unrefed
+					      later by gdk_event_free. */
+      if (button == 4)
+	xg_event->scroll.direction = GDK_SCROLL_UP;
+      else if (button == 5)
+	xg_event->scroll.direction = GDK_SCROLL_DOWN;
+      else if (button == 6)
+	xg_event->scroll.direction = GDK_SCROLL_LEFT;
+      else
+	xg_event->scroll.direction = GDK_SCROLL_RIGHT;
+
+      xg_event->scroll.device = find_suitable_pointer (view->frame);
+
+      xg_event->scroll.x = x;
+      xg_event->scroll.x_root = x;
+      xg_event->scroll.y = y;
+      xg_event->scroll.y_root = y;
+      xg_event->scroll.state = modifier_state;
+      xg_event->scroll.time = time;
+
+      xg_event->scroll.delta_x = 0;
+      xg_event->scroll.delta_y = 0;
+
+      gtk_main_do_event (xg_event);
+      gdk_event_free (xg_event);
+    }
+}
+
+void
+xwidget_motion_or_crossing (struct xwidget_view *view, const XEvent *event)
+{
+  GdkEvent *xg_event = gdk_event_new (event->type == MotionNotify ? GDK_MOTION_NOTIFY :
+				      (event->type == LeaveNotify ? GDK_LEAVE_NOTIFY :
+				       GDK_ENTER_NOTIFY));
+  struct xwidget *model = XXWIDGET (view->model);
+  int x;
+  int y;
+  GtkWidget *target = find_widget_at_pos (model->widgetwindow_osr,
+					  (event->type == MotionNotify
+					   ? event->xmotion.x + view->clip_left
+					   : event->xmotion.y + view->clip_top),
+					  (event->type == MotionNotify
+					   ? event->xmotion.y + view->clip_left
+					   : event->xcrossing.y + view->clip_top),
+					  &x, &y);
+
+  if (!target)
+    target = model->widgetwindow_osr;
+
+  xg_event->any.window = gtk_widget_get_window (target);
+  g_object_ref (xg_event->any.window); /* The window will be unrefed
+					  later by gdk_event_free. */
+
+  if (event->type == MotionNotify)
+    {
+      xg_event->motion.x = x;
+      xg_event->motion.y = y;
+      xg_event->motion.x_root = event->xmotion.x_root;
+      xg_event->motion.y_root = event->xmotion.y_root;
+      xg_event->motion.time = event->xmotion.time;
+      xg_event->motion.state = event->xmotion.state;
+      xg_event->motion.device = find_suitable_pointer (view->frame);
+    }
+  else
+    {
+      xg_event->crossing.detail = min (5, event->xcrossing.detail);
+      xg_event->crossing.time = event->xcrossing.time;
+      xg_event->crossing.x = x;
+      xg_event->crossing.y = y;
+      xg_event->crossing.x_root = event->xcrossing.x_root;
+      xg_event->crossing.y_root = event->xcrossing.y_root;
+      gdk_event_set_device (xg_event, find_suitable_pointer (view->frame));
+    }
+
+  gtk_main_do_event (xg_event);
+  gdk_event_free (xg_event);
+}
+
+static void
+synthesize_focus_in_event (GtkWidget *offscreen_window)
+{
+  GdkWindow *wnd;
+  GdkEvent *focus_event;
+
+  if (!gtk_widget_get_realized (offscreen_window))
+    gtk_widget_realize (offscreen_window);
+
+  wnd = gtk_widget_get_window (offscreen_window);
+
+  focus_event = gdk_event_new (GDK_FOCUS_CHANGE);
+  focus_event->any.window = wnd;
+  focus_event->focus_change.in = TRUE;
+  g_object_ref (wnd);
+
+  gtk_main_do_event (focus_event);
+  gdk_event_free (focus_event);
+}
+
+struct xwidget_view *
+xwidget_view_from_window (Window wdesc)
+{
+  Lisp_Object key = make_fixnum (wdesc);
+  Lisp_Object xwv = Fgethash (key, x_window_to_xwv_map, Qnil);
+
+  if (NILP (xwv))
+    return NULL;
+
+  return XXWIDGET_VIEW (xwv);
+}
+
 static void
 xwidget_show_view (struct xwidget_view *xv)
 {
   xv->hidden = false;
-  gtk_widget_show (xv->widgetwindow);
-  gtk_fixed_move (GTK_FIXED (xv->emacswindow),
-                  xv->widgetwindow,
-                  xv->x + xv->clip_left,
-                  xv->y + xv->clip_top);
+  XMoveWindow (xv->dpy, xv->wdesc,
+	       xv->x + xv->clip_left,
+	       xv->y + xv->clip_top);
+  XMapWindow (xv->dpy, xv->wdesc);
+  XFlush (xv->dpy);
 }
 
 /* Hide an xwidget view.  */
@@ -238,28 +836,64 @@ xwidget_show_view (struct xwidget_view *xv)
 xwidget_hide_view (struct xwidget_view *xv)
 {
   xv->hidden = true;
-  gtk_fixed_move (GTK_FIXED (xv->emacswindow), xv->widgetwindow,
-                  10000, 10000);
+  XUnmapWindow (xv->dpy, xv->wdesc);
+  XFlush (xv->dpy);
+}
+
+static void
+xv_do_draw (struct xwidget_view *xw, struct xwidget *w)
+{
+  GtkOffscreenWindow *wnd;
+  cairo_surface_t *surface;
+  block_input ();
+  wnd = GTK_OFFSCREEN_WINDOW (w->widgetwindow_osr);
+  surface = gtk_offscreen_window_get_surface (wnd);
+
+  cairo_save (xw->cr_context);
+  if (surface)
+    {
+      cairo_set_source_surface (xw->cr_context, surface, xw->clip_left,
+				xw->clip_top);
+      cairo_set_operator (xw->cr_context, CAIRO_OPERATOR_SOURCE);
+      cairo_paint (xw->cr_context);
+    }
+  cairo_restore (xw->cr_context);
+
+  unblock_input ();
 }
 
 /* When the off-screen webkit master view changes this signal is called.
    It copies the bitmap from the off-screen instance.  */
 static gboolean
 offscreen_damage_event (GtkWidget *widget, GdkEvent *event,
-                        gpointer xv_widget)
-{
-  /* Queue a redraw of onscreen widget.
-     There is a guard against receiving an invalid widget,
-     which should only happen if we failed to remove the
-     specific signal handler for the damage event.  */
-  if (GTK_IS_WIDGET (xv_widget))
-    gtk_widget_queue_draw (GTK_WIDGET (xv_widget));
-  else
-    message ("Warning, offscreen_damage_event received invalid xv pointer:%p\n",
-             xv_widget);
+                        gpointer xwidget)
+{
+  block_input ();
+
+  for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail);
+       tail = XCDR (tail))
+    {
+      if (XWIDGET_VIEW_P (XCAR (tail)))
+	{
+	  struct xwidget_view *view = XXWIDGET_VIEW (XCAR (tail));
+
+	  if (view->wdesc && XXWIDGET (view->model) == xwidget)
+	    xv_do_draw (view, XXWIDGET (view->model));
+	}
+    }
+
+  unblock_input ();
 
   return FALSE;
 }
+
+void
+xwidget_expose (struct xwidget_view *xv)
+{
+  struct xwidget *xw = XXWIDGET (xv->model);
+
+  xv_do_draw (xv, xw);
+}
 #endif /* USE_GTK */
 
 void
@@ -313,22 +947,108 @@ store_xwidget_js_callback_event (struct xwidget *xw,
 
 
 #ifdef USE_GTK
+static void
+store_xwidget_display_event (struct xwidget *xw)
+{
+  struct input_event evt;
+  Lisp_Object val;
+
+  XSETXWIDGET (val, xw);
+  EVENT_INIT (evt);
+  evt.kind = XWIDGET_DISPLAY_EVENT;
+  evt.frame_or_window = Qnil;
+  evt.arg = val;
+  kbd_buffer_store_event (&evt);
+}
+
+static void
+webkit_ready_to_show (WebKitWebView *new_view,
+		      gpointer user_data)
+{
+  Lisp_Object tem;
+  struct xwidget *xw;
+
+  for (tem = Vxwidget_list; CONSP (tem); tem = XCDR (tem))
+    {
+      if (XWIDGETP (XCAR (tem)))
+	{
+	  xw = XXWIDGET (XCAR (tem));
+
+	  if (EQ (xw->type, Qwebkit)
+	      && WEBKIT_WEB_VIEW (xw->widget_osr) == new_view)
+	    store_xwidget_display_event (xw);
+	}
+    }
+}
+
+static GtkWidget *
+webkit_create_cb_1 (WebKitWebView *webview,
+		    struct xwidget_view *xv)
+{
+  Lisp_Object related;
+  Lisp_Object xwidget;
+  GtkWidget *widget;
+
+  XSETXWIDGET (related, xv);
+  xwidget = Fmake_xwidget (Qwebkit, Qnil, make_fixnum (0),
+			   make_fixnum (0), Qnil,
+			   build_string (" *detached xwidget buffer*"),
+			   related);
+
+  if (NILP (xwidget))
+    return NULL;
+
+  widget = XXWIDGET (xwidget)->widget_osr;
+
+  g_signal_connect (G_OBJECT (widget), "ready-to-show",
+		    G_CALLBACK (webkit_ready_to_show), NULL);
+
+  return widget;
+}
+
+static GtkWidget *
+webkit_create_cb (WebKitWebView *webview,
+		  WebKitNavigationAction *nav_action,
+		  gpointer user_data)
+{
+  switch (webkit_navigation_action_get_navigation_type (nav_action))
+    {
+    case WEBKIT_NAVIGATION_TYPE_OTHER:
+      return webkit_create_cb_1 (webview, user_data);
+
+    case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD:
+    case WEBKIT_NAVIGATION_TYPE_RELOAD:
+    case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED:
+    case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
+    case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED:
+    default:
+      return NULL;
+    }
+}
+
 void
 webkit_view_load_changed_cb (WebKitWebView *webkitwebview,
                              WebKitLoadEvent load_event,
                              gpointer data)
 {
-  switch (load_event) {
-  case WEBKIT_LOAD_FINISHED:
+  struct xwidget *xw = g_object_get_data (G_OBJECT (webkitwebview),
+					  XG_XWIDGET);
+
+  switch (load_event)
     {
-      struct xwidget *xw = g_object_get_data (G_OBJECT (webkitwebview),
-                                              XG_XWIDGET);
-      store_xwidget_event_string (xw, "load-changed", "");
+    case WEBKIT_LOAD_FINISHED:
+      store_xwidget_event_string (xw, "load-changed", "load-finished");
+      break;
+    case WEBKIT_LOAD_STARTED:
+      store_xwidget_event_string (xw, "load-changed", "load-started");
+      break;
+    case WEBKIT_LOAD_REDIRECTED:
+      store_xwidget_event_string (xw, "load-changed", "load-redirected");
+      break;
+    case WEBKIT_LOAD_COMMITTED:
+      store_xwidget_event_string (xw, "load-changed", "load-committed");
       break;
     }
-  default:
-    break;
-  }
 }
 
 /* Recursively convert a JavaScript value to a Lisp value. */
@@ -498,51 +1218,6 @@ webkit_decide_policy_cb (WebKitWebView *webView,
     return FALSE;
   }
 }
-
-
-/* For gtk3 offscreen rendered widgets.  */
-static gboolean
-xwidget_osr_draw_cb (GtkWidget *widget, cairo_t *cr, gpointer data)
-{
-  struct xwidget *xw = g_object_get_data (G_OBJECT (widget), XG_XWIDGET);
-  struct xwidget_view *xv = g_object_get_data (G_OBJECT (widget),
-                                               XG_XWIDGET_VIEW);
-
-  cairo_rectangle (cr, 0, 0, xv->clip_right, xv->clip_bottom);
-  cairo_clip (cr);
-
-  gtk_widget_draw (xw->widget_osr, cr);
-  return FALSE;
-}
-
-static gboolean
-xwidget_osr_event_forward (GtkWidget *widget, GdkEvent *event,
-			   gpointer user_data)
-{
-  /* Copy events that arrive at the outer widget to the offscreen widget.  */
-  struct xwidget *xw = g_object_get_data (G_OBJECT (widget), XG_XWIDGET);
-  GdkEvent *eventcopy = gdk_event_copy (event);
-  eventcopy->any.window = gtk_widget_get_window (xw->widget_osr);
-
-  /* TODO: This might leak events.  They should be deallocated later,
-     perhaps in xwgir_event_cb.  */
-  gtk_main_do_event (eventcopy);
-
-  /* Don't propagate this event further.  */
-  return TRUE;
-}
-
-static gboolean
-xwidget_osr_event_set_embedder (GtkWidget *widget, GdkEvent *event,
-				gpointer data)
-{
-  struct xwidget_view *xv = data;
-  struct xwidget *xww = XXWIDGET (xv->model);
-  gdk_offscreen_window_set_embedder (gtk_widget_get_window
-				     (xww->widgetwindow_osr),
-                                     gtk_widget_get_window (xv->widget));
-  return FALSE;
-}
 #endif /* USE_GTK */
 
 
@@ -568,63 +1243,19 @@ xwidget_init_view (struct xwidget *xww,
   XSETXWIDGET (xv->model, xww);
 
 #ifdef USE_GTK
-  if (EQ (xww->type, Qwebkit))
-    {
-      xv->widget = gtk_drawing_area_new ();
-      /* Expose event handling.  */
-      gtk_widget_set_app_paintable (xv->widget, TRUE);
-      gtk_widget_add_events (xv->widget, GDK_ALL_EVENTS_MASK);
-
-      /* Draw the view on damage-event.  */
-      g_signal_connect (G_OBJECT (xww->widgetwindow_osr), "damage-event",
-                        G_CALLBACK (offscreen_damage_event), xv->widget);
+  xv->dpy = FRAME_X_DISPLAY (s->f);
 
-      if (EQ (xww->type, Qwebkit))
-        {
-          g_signal_connect (G_OBJECT (xv->widget), "button-press-event",
-                            G_CALLBACK (xwidget_osr_event_forward), NULL);
-          g_signal_connect (G_OBJECT (xv->widget), "button-release-event",
-                            G_CALLBACK (xwidget_osr_event_forward), NULL);
-          g_signal_connect (G_OBJECT (xv->widget), "motion-notify-event",
-                            G_CALLBACK (xwidget_osr_event_forward), NULL);
-        }
-      else
-        {
-          /* xwgir debug, orthogonal to forwarding.  */
-          g_signal_connect (G_OBJECT (xv->widget), "enter-notify-event",
-                            G_CALLBACK (xwidget_osr_event_set_embedder), xv);
-        }
-      g_signal_connect (G_OBJECT (xv->widget), "draw",
-                        G_CALLBACK (xwidget_osr_draw_cb), NULL);
-    }
-
-  /* Widget realization.
-
-     Make container widget first, and put the actual widget inside the
-     container later.  Drawing should crop container window if necessary
-     to handle case where xwidget is partially obscured by other Emacs
-     windows.  Other containers than gtk_fixed where explored, but
-     gtk_fixed had the most predictable behavior so far.  */
-
-  xv->emacswindow = FRAME_GTK_WIDGET (s->f);
-  xv->widgetwindow = gtk_fixed_new ();
-  gtk_widget_set_has_window (xv->widgetwindow, TRUE);
-  gtk_container_add (GTK_CONTAINER (xv->widgetwindow), xv->widget);
-
-  /* Store some xwidget data in the gtk widgets.  */
-  g_object_set_data (G_OBJECT (xv->widget), XG_FRAME_DATA, s->f);
-  g_object_set_data (G_OBJECT (xv->widget), XG_XWIDGET, xww);
-  g_object_set_data (G_OBJECT (xv->widget), XG_XWIDGET_VIEW, xv);
-  g_object_set_data (G_OBJECT (xv->widgetwindow), XG_XWIDGET, xww);
-  g_object_set_data (G_OBJECT (xv->widgetwindow), XG_XWIDGET_VIEW, xv);
-
-  gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xww->width,
-                               xww->height);
-  gtk_widget_set_size_request (xv->widgetwindow, xww->width, xww->height);
-  gtk_fixed_put (GTK_FIXED (FRAME_GTK_WIDGET (s->f)), xv->widgetwindow, x, y);
   xv->x = x;
   xv->y = y;
-  gtk_widget_show_all (xv->widgetwindow);
+
+  xv->clip_left = 0;
+  xv->clip_right = xww->width;
+  xv->clip_top = 0;
+  xv->clip_bottom = xww->height;
+
+  xv->wdesc = None;
+  xv->frame = s->f;
+  xv->cursor = cursor_for_hit (xww->hit_result, s->f);
 #elif defined NS_IMPL_COCOA
   nsxwidget_init_view (xv, xww, s, x, y);
   nsxwidget_resize_view(xv, xww->width, xww->height);
@@ -681,6 +1312,8 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
   window_box (s->w, TEXT_AREA, &text_area_x, &text_area_y,
               &text_area_width, &text_area_height);
 
+  /* On X11, this keeps generating expose events.  */
+#ifndef USE_GTK
   /* Resize xwidget webkit if its container window size is changed in
      some ways, for example, a buffer became hidden in small split
      window, then it can appear front in merged whole window.  */
@@ -693,6 +1326,7 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
                        make_int (text_area_width),
                        make_int (text_area_height));
     }
+#endif
 
   clip_left = max (0, text_area_x - x);
   clip_right = max (clip_left,
@@ -711,15 +1345,51 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
      later.  */
   bool moved = (xv->x + xv->clip_left != x + clip_left
 		|| xv->y + xv->clip_top != y + clip_top);
+
+#ifdef USE_GTK
+  bool wdesc_was_none = xv->wdesc == None;
+#endif
   xv->x = x;
   xv->y = y;
 
+#ifdef USE_GTK
+  block_input ();
+  if (xv->wdesc == None)
+    {
+      Lisp_Object xvw;
+      XSETXWIDGET_VIEW (xvw, xv);
+      XSetWindowAttributes a;
+      a.event_mask = (ExposureMask | ButtonPressMask | ButtonReleaseMask
+		      | PointerMotionMask | EnterWindowMask | LeaveWindowMask);
+
+      xv->wdesc = XCreateWindow (xv->dpy, FRAME_X_WINDOW (s->f),
+				 x + clip_left, y + clip_top,
+				 clip_right - clip_left,
+				 clip_bottom - clip_top, 0,
+				 CopyFromParent, CopyFromParent,
+				 CopyFromParent, CWEventMask, &a);
+      XDefineCursor (xv->dpy, xv->wdesc, xv->cursor);
+      xv->cr_surface = cairo_xlib_surface_create (xv->dpy,
+						  xv->wdesc,
+						  FRAME_DISPLAY_INFO (s->f)->visual,
+						  clip_right - clip_left,
+						  clip_bottom - clip_top);
+      xv->cr_context = cairo_create (xv->cr_surface);
+      Fputhash (make_fixnum (xv->wdesc), xvw, x_window_to_xwv_map);
+
+      moved = false;
+    }
+#endif
+
   /* Has it moved?  */
   if (moved)
     {
 #ifdef USE_GTK
-      gtk_fixed_move (GTK_FIXED (FRAME_GTK_WIDGET (s->f)),
-                      xv->widgetwindow, x + clip_left, y + clip_top);
+      XMoveResizeWindow (xv->dpy, xv->wdesc, x + clip_left, y + clip_top,
+			 clip_right - clip_left, clip_bottom - clip_top);
+      XFlush (xv->dpy);
+      cairo_xlib_surface_set_size (xv->cr_surface, clip_right - clip_left,
+				   clip_bottom - clip_top);
 #elif defined NS_IMPL_COCOA
       nsxwidget_move_view (xv, x + clip_left, y + clip_top);
 #endif
@@ -735,10 +1405,14 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
       || xv->clip_top != clip_top || xv->clip_left != clip_left)
     {
 #ifdef USE_GTK
-      gtk_widget_set_size_request (xv->widgetwindow, clip_right - clip_left,
-                                   clip_bottom - clip_top);
-      gtk_fixed_move (GTK_FIXED (xv->widgetwindow), xv->widget, -clip_left,
-                      -clip_top);
+      if (!wdesc_was_none && !moved)
+	{
+	  XResizeWindow (xv->dpy, xv->wdesc, clip_right - clip_left,
+			 clip_bottom - clip_top);
+	  XFlush (xv->dpy);
+	  cairo_xlib_surface_set_size (xv->cr_surface, clip_right - clip_left,
+				       clip_bottom - clip_top);
+	}
 #elif defined NS_IMPL_COCOA
       nsxwidget_resize_view (xv, clip_right - clip_left,
                              clip_bottom - clip_top);
@@ -758,12 +1432,15 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
   if (!xwidget_hidden (xv))
     {
 #ifdef USE_GTK
-      gtk_widget_queue_draw (xv->widgetwindow);
-      gtk_widget_queue_draw (xv->widget);
+      gtk_widget_queue_draw (xww->widget_osr);
 #elif defined NS_IMPL_COCOA
       nsxwidget_set_needsdisplay (xv);
 #endif
     }
+
+#ifdef USE_GTK
+  unblock_input ();
+#endif
 }
 
 static bool
@@ -975,16 +1652,13 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
           struct xwidget_view *xv = XXWIDGET_VIEW (XCAR (tail));
           if (XXWIDGET (xv->model) == xw)
             {
-#ifdef USE_GTK
-              gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xw->width,
-                                           xw->height);
-#elif defined NS_IMPL_COCOA
-              nsxwidget_resize_view(xv, xw->width, xw->height);
-#endif
+	      wset_redisplay (XWINDOW (xv->w));
             }
         }
     }
 
+  redisplay ();
+
   return Qnil;
 }
 
@@ -1084,13 +1758,15 @@ DEFUN ("delete-xwidget-view",
   CHECK_XWIDGET_VIEW (xwidget_view);
   struct xwidget_view *xv = XXWIDGET_VIEW (xwidget_view);
 #ifdef USE_GTK
-  gtk_widget_destroy (xv->widgetwindow);
-  /* xv->model still has signals pointing to the view.  There can be
-     several views.  Find the matching signals and delete them all.  */
-  g_signal_handlers_disconnect_matched  (XXWIDGET (xv->model)->widgetwindow_osr,
-                                         G_SIGNAL_MATCH_DATA,
-                                         0, 0, 0, 0,
-                                         xv->widget);
+  if (xv->wdesc != None)
+    {
+      block_input ();
+      cairo_destroy (xv->cr_context);
+      cairo_surface_destroy (xv->cr_surface);
+      XDestroyWindow (xv->dpy, xv->wdesc);
+      Fremhash (make_fixnum (xv->wdesc), x_window_to_xwv_map);
+      unblock_input ();
+    }
 #elif defined NS_IMPL_COCOA
   nsxwidget_delete_view (xv);
 #endif
@@ -1145,6 +1821,19 @@ DEFUN ("xwidget-buffer",
   return XXWIDGET (xwidget)->buffer;
 }
 
+DEFUN ("set-xwidget-buffer",
+       Fset_xwidget_buffer, Sset_xwidget_buffer,
+       2, 2, 0,
+       doc: /* Set XWIDGET's buffer to BUFFER.  */)
+  (Lisp_Object xwidget, Lisp_Object buffer)
+{
+  CHECK_XWIDGET (xwidget);
+  CHECK_BUFFER (buffer);
+
+  XXWIDGET (xwidget)->buffer = buffer;
+  return Qnil;
+}
+
 DEFUN ("set-xwidget-plist",
        Fset_xwidget_plist, Sset_xwidget_plist,
        2, 2, 0,
@@ -1183,6 +1872,166 @@ DEFUN ("xwidget-query-on-exit-flag",
   return (XXWIDGET (xwidget)->kill_without_query ? Qnil : Qt);
 }
 
+DEFUN ("xwidget-webkit-search", Fxwidget_webkit_search, Sxwidget_webkit_search,
+       2, 5, 0,
+       doc: /* Begin an incremental search operation in an xwidget.
+QUERY should be a string containing the text to search for.  XWIDGET
+should be a WebKit xwidget where the search will take place.  When the
+search operation is complete, callers should also call
+`xwidget-webkit-finish-search' to complete the search operation.
+
+CASE-INSENSITIVE, when non-nil, will cause the search to ignore the
+case of characters inside QUERY.  BACKWARDS, when non-nil, will cause
+the search to proceed towards the beginning of the widget's contents.
+WRAP-AROUND, when nil, will cause the search to stop upon hitting the
+end of the widget's contents.
+
+It is OK to call this function even when a search is already in
+progress.  In that case, the previous search query will be replaced
+with QUERY.  */)
+  (Lisp_Object query, Lisp_Object xwidget, Lisp_Object case_insensitive,
+   Lisp_Object backwards, Lisp_Object wrap_around)
+{
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+  WebKitFindOptions opt;
+  struct xwidget *xw;
+  gchar *g_query;
+#endif
+
+  CHECK_STRING (query);
+  CHECK_XWIDGET (xwidget);
+
+#ifdef USE_GTK
+  xw = XXWIDGET (xwidget);
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  query = ENCODE_UTF_8 (query);
+  opt = WEBKIT_FIND_OPTIONS_NONE;
+  g_query = xstrdup (SSDATA (query));
+
+  if (!NILP (case_insensitive))
+    opt |= WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE;
+  if (!NILP (backwards))
+    opt |= WEBKIT_FIND_OPTIONS_BACKWARDS;
+  if (!NILP (wrap_around))
+    opt |= WEBKIT_FIND_OPTIONS_WRAP_AROUND;
+
+  if (xw->find_text)
+    xfree (xw->find_text);
+  xw->find_text = g_query;
+
+  block_input ();
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search (controller, g_query, opt, G_MAXUINT);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
+DEFUN ("xwidget-webkit-next-result", Fxwidget_webkit_next_result,
+       Sxwidget_webkit_next_result, 1, 1, 0,
+       doc: /* Show the next result matching the current search query.
+
+XWIDGET should be an xwidget that currently has a search query.
+Before calling this function, you should start a search operation
+using `xwidget-webkit-search'.  */)
+  (Lisp_Object xwidget)
+{
+  struct xwidget *xw;
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!xw->find_text)
+    error ("Widget has no ongoing search operation");
+
+#ifdef USE_GTK
+  block_input ();
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search_next (controller);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
+DEFUN ("xwidget-webkit-previous-result", Fxwidget_webkit_previous_result,
+       Sxwidget_webkit_previous_result, 1, 1, 0,
+       doc: /* Show the previous result matching the current search query.
+
+XWIDGET should be an xwidget that currently has a search query.
+Before calling this function, you should start a search operation
+using `xwidget-webkit-search'.  */)
+  (Lisp_Object xwidget)
+{
+  struct xwidget *xw;
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!xw->find_text)
+    error ("Widget has no ongoing search operation");
+
+#ifdef USE_GTK
+  block_input ();
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search_previous (controller);
+
+  if (xw->find_text)
+    {
+      xfree (xw->find_text);
+      xw->find_text = NULL;
+    }
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
+DEFUN ("xwidget-webkit-finish-search", Fxwidget_webkit_finish_search,
+       Sxwidget_webkit_finish_search, 1, 1, 0,
+       doc: /* Finish XWIDGET's search operation.
+
+XWIDGET should be an xwidget that currently has a search query.
+Before calling this function, you should start a search operation
+using `xwidget-webkit-search'.  */)
+  (Lisp_Object xwidget)
+{
+  struct xwidget *xw;
+#ifdef USE_GTK
+  WebKitWebView *webview;
+  WebKitFindController *controller;
+#endif
+
+  CHECK_XWIDGET (xwidget);
+  xw = XXWIDGET (xwidget);
+
+  if (!xw->find_text)
+    error ("Widget has no ongoing search operation");
+
+#ifdef USE_GTK
+  block_input ();
+  webview = WEBKIT_WEB_VIEW (xw->widget_osr);
+  controller = webkit_web_view_get_find_controller (webview);
+  webkit_find_controller_search_finish (controller);
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
 void
 syms_of_xwidget (void)
 {
@@ -1215,6 +2064,12 @@ syms_of_xwidget (void)
   defsubr (&Sxwidget_plist);
   defsubr (&Sxwidget_buffer);
   defsubr (&Sset_xwidget_plist);
+  defsubr (&Sxwidget_perform_lispy_event);
+  defsubr (&Sxwidget_webkit_search);
+  defsubr (&Sxwidget_webkit_finish_search);
+  defsubr (&Sxwidget_webkit_next_result);
+  defsubr (&Sxwidget_webkit_previous_result);
+  defsubr (&Sset_xwidget_buffer);
 
   DEFSYM (QCxwidget, ":xwidget");
   DEFSYM (QCtitle, ":title");
@@ -1236,6 +2091,15 @@ syms_of_xwidget (void)
   Vxwidget_view_list = Qnil;
 
   Fprovide (intern ("xwidget-internal"), Qnil);
+
+  id_to_xwidget_map = CALLN (Fmake_hash_table, QCtest, Qeq);
+  staticpro (&id_to_xwidget_map);
+
+#ifdef USE_GTK
+  x_window_to_xwv_map = CALLN (Fmake_hash_table, QCtest, Qeq);
+
+  staticpro (&x_window_to_xwv_map);
+#endif
 }
 
 
@@ -1374,7 +2238,7 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
 		  /* The only call to xwidget_end_redisplay is in dispnew.
 		     xwidget_end_redisplay (w->current_matrix);  */
 		  struct xwidget_view *xv
-		    = xwidget_view_lookup (glyph->u.xwidget, w);
+		    = xwidget_view_lookup (xwidget_from_id (glyph->u.xwidget), w);
 #ifdef USE_GTK
 		  /* FIXME: Is it safe to assume xwidget_view_lookup
 		     always succeeds here?  If so, this comment can be removed.
@@ -1424,6 +2288,26 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
     }
 }
 
+#ifdef USE_GTK
+void
+kill_frame_xwidget_views (struct frame *f)
+{
+  Lisp_Object rem = Qnil;
+
+  for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail);
+       tail = XCDR (tail))
+    {
+      if (XXWIDGET_VIEW (XCAR (tail))->frame == f)
+	rem = Fcons (XCAR (tail), rem);
+    }
+
+  for (; CONSP (rem); rem = XCDR (rem))
+    {
+      Fdelete_xwidget_view (XCAR (rem));
+    }
+}
+#endif
+
 /* Kill all xwidget in BUFFER.  */
 void
 kill_buffer_xwidgets (Lisp_Object buffer)
@@ -1437,12 +2321,15 @@ kill_buffer_xwidgets (Lisp_Object buffer)
       {
         CHECK_XWIDGET (xwidget);
         struct xwidget *xw = XXWIDGET (xwidget);
+	Fremhash (make_fixnum (xw->xwidget_id), id_to_xwidget_map);
 #ifdef USE_GTK
         if (xw->widget_osr && xw->widgetwindow_osr)
           {
             gtk_widget_destroy (xw->widget_osr);
             gtk_widget_destroy (xw->widgetwindow_osr);
           }
+	if (xw->find_text)
+	  xfree (xw->find_text);
 	if (!NILP (xw->script_callbacks))
 	  for (ptrdiff_t idx = 0; idx < ASIZE (xw->script_callbacks); idx++)
 	    {
diff --git a/src/xwidget.h b/src/xwidget.h
index 591f23489d..ad8b7c039c 100644
--- a/src/xwidget.h
+++ b/src/xwidget.h
@@ -32,6 +32,8 @@ #define XWIDGET_H_INCLUDED
 
 #if defined (USE_GTK)
 #include <gtk/gtk.h>
+#include <X11/Xlib.h>
+#include "xterm.h"
 #elif defined (NS_IMPL_COCOA) && defined (__OBJC__)
 #import <AppKit/NSView.h>
 #import "nsxwidget.h"
@@ -59,11 +61,14 @@ #define XWIDGET_H_INCLUDED
 
   int height;
   int width;
+  uint32_t xwidget_id;
+  char *find_text;
 
 #if defined (USE_GTK)
   /* For offscreen widgets, unused if not osr.  */
   GtkWidget *widget_osr;
   GtkWidget *widgetwindow_osr;
+  guint hit_result;
 #elif defined (NS_IMPL_COCOA)
 # ifdef __OBJC__
   /* For offscreen widgets, unused if not osr.  */
@@ -98,9 +103,13 @@ #define XWIDGET_H_INCLUDED
   bool hidden;
 
 #if defined (USE_GTK)
-  GtkWidget *widget;
-  GtkWidget *widgetwindow;
-  GtkWidget *emacswindow;
+  Display *dpy;
+  Window wdesc;
+  Emacs_Cursor cursor;
+  struct frame *frame;
+
+  cairo_surface_t *cr_surface;
+  cairo_t *cr_context;
 #elif defined (NS_IMPL_COCOA)
 # ifdef __OBJC__
   XvWindow *xvWindow;
@@ -162,6 +171,18 @@ #define XG_XWIDGET_VIEW "emacs_xwidget_view"
 void store_xwidget_js_callback_event (struct xwidget *xw,
                                       Lisp_Object proc,
                                       Lisp_Object argument);
+
+extern struct xwidget *xwidget_from_id (uint32_t id);
+
+#ifdef HAVE_X_WINDOWS
+struct xwidget_view *xwidget_view_from_window (Window wdesc);
+void xwidget_expose (struct xwidget_view *xv);
+extern void kill_frame_xwidget_views (struct frame *f);
+extern void xwidget_button (struct xwidget_view *, bool, int,
+			    int, int, int, Time);
+extern void xwidget_motion_or_crossing (struct xwidget_view *,
+					const XEvent *);
+#endif
 #else
 INLINE_HEADER_BEGIN
 INLINE void syms_of_xwidget (void) {}

^ permalink raw reply related	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07  1:34                                     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-07  1:39                                       ` Lars Ingebrigtsen
  2021-11-07  1:55                                         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 39+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-07  1:39 UTC (permalink / raw)
  To: Po Lu; +Cc: 51473

Po Lu <luangruo@yahoo.com> writes:

> Good catch, thanks!  Does this work?

It builds!  ✨

And it seems to work fine, too after doing some cursory browsing to a
handful of sites.  So if you send me a new tar ball of patches to apply,
I'll get them pushed.

(They're to be applied in the sequence they're numbered?)

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07  1:39                                       ` Lars Ingebrigtsen
@ 2021-11-07  1:55                                         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-07  2:04                                           ` Lars Ingebrigtsen
  0 siblings, 1 reply; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-07  1:55 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: Eli Zaretskii, 51473

[-- Attachment #1: Type: text/plain, Size: 366 bytes --]

Lars Ingebrigtsen <larsi@gnus.org> writes:

> It builds!  ✨
>
> And it seems to work fine, too after doing some cursory browsing to a
> handful of sites.  So if you send me a new tar ball of patches to apply,
> I'll get them pushed.

Thanks :)

> (They're to be applied in the sequence they're numbered?)

Yes, I created them with git format-patch.


[-- Attachment #2: patches.tar.gz --]
[-- Type: application/gzip, Size: 43658 bytes --]

^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07  1:55                                         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-07  2:04                                           ` Lars Ingebrigtsen
  0 siblings, 0 replies; 39+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-07  2:04 UTC (permalink / raw)
  To: Po Lu; +Cc: 51473

Po Lu <luangruo@yahoo.com> writes:

>> (They're to be applied in the sequence they're numbered?)
>
> Yes, I created them with git format-patch.

Great!  Now applied and pushed to trunk.  😀

(So I'm closing this bug report.)

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07  0:41                         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-07  0:55                           ` Lars Ingebrigtsen
  2021-11-07  1:04                           ` Lars Ingebrigtsen
@ 2021-11-07 10:09                           ` Eli Zaretskii
  2021-11-07 12:03                             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2 siblings, 1 reply; 39+ messages in thread
From: Eli Zaretskii @ 2021-11-07 10:09 UTC (permalink / raw)
  To: Po Lu; +Cc: larsi, 51473

> From: Po Lu <luangruo@yahoo.com>
> Cc: Eli Zaretskii <eliz@gnu.org>,  51473@debbugs.gnu.org
> Date: Sun, 07 Nov 2021 08:41:15 +0800
> 
> Lars Ingebrigtsen <larsi@gnus.org> writes:
> 
> > I can test both, but it'd be easier to test if it was just one huge
> > patch.  (When applying for real, we can apply the patch series.)  So can
> > you post this series as one patch?
> 
> Thanks, here you go.

> --- a/src/print.c
> +++ b/src/print.c
> @@ -1521,8 +1521,20 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
>        printchar ('>', printcharfun);
>        break;
>  
> -    case PVEC_XWIDGET: case PVEC_XWIDGET_VIEW:
> -      print_c_string ("#<xwidget ", printcharfun);
> +    case PVEC_XWIDGET:
> +#ifdef HAVE_XWIDGETS
> +      {
> +	int len = sprintf (buf, "#<xwidget %u %p>",
> +			   XXWIDGET (obj)->xwidget_id,
> +			   XXWIDGET (obj)->widget_osr);
> +	strout (buf, len, len, printcharfun);
> +	break;
> +      }

The printed representation of xwidget objects should be documented in
the ELisp manual, under the "Editing Types" section.  In fact, we lack
a subsection there for xwidgets; it should be added.

Thanks.





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07 10:09                           ` Eli Zaretskii
@ 2021-11-07 12:03                             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-07 12:33                               ` Eli Zaretskii
  0 siblings, 1 reply; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-07 12:03 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, 51473

[-- Attachment #1: Type: text/plain, Size: 268 bytes --]

Eli Zaretskii <eliz@gnu.org> writes:

> The printed representation of xwidget objects should be documented in
> the ELisp manual, under the "Editing Types" section.  In fact, we lack
> a subsection there for xwidgets; it should be added.
>
> Thanks.

Will this work?


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Document-the-xwidget-type.patch --]
[-- Type: text/x-patch, Size: 2188 bytes --]

From e90ed4c7d3e92ce6a3fd8d89b450c9b660ff2e08 Mon Sep 17 00:00:00 2001
From: Po Lu <luangruo@yahoo.com>
Date: Sun, 7 Nov 2021 20:02:06 +0800
Subject: [PATCH] Document the xwidget type

* doc/lispref/elisp.texi: Add Xwidget Type to the menu.
* doc/lispref/objects.texi (Editing Types): Add Xwidget Type to the
menu.
(Xwidget Type): New node.
---
 doc/lispref/elisp.texi   |  1 +
 doc/lispref/objects.texi | 14 ++++++++++++++
 2 files changed, 15 insertions(+)

diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi
index d0bfd8c901..1c0b0fa1b5 100644
--- a/doc/lispref/elisp.texi
+++ b/doc/lispref/elisp.texi
@@ -365,6 +365,7 @@ Top
 * Keymap Type::             What function a keystroke invokes.
 * Overlay Type::            How an overlay is represented.
 * Font Type::               Fonts for displaying text.
+* Xwidget Type::            Embeddable widgets.
 
 Numbers
 
diff --git a/doc/lispref/objects.texi b/doc/lispref/objects.texi
index 0551bb5673..1c1f463af2 100644
--- a/doc/lispref/objects.texi
+++ b/doc/lispref/objects.texi
@@ -1535,6 +1535,7 @@ Editing Types
 * Keymap Type::         What function a keystroke invokes.
 * Overlay Type::        How an overlay is represented.
 * Font Type::           Fonts for displaying text.
+* Xwidget Type::        Embeddable widgets.
 @end menu
 
 @node Buffer Type
@@ -1860,6 +1861,19 @@ Font Type
 @samp{#<font-entity>} respectively.  @xref{Low-Level Font}, for a
 description of these Lisp objects.
 
+@node Xwidget Type
+@subsection Xwidget Type
+
+  An @dfn{xwidget} is a special display element, such as a web
+browser, that can be embedded inside a buffer.  Each window such an
+xwidget is be displayed in will also have an xwidget view, which on
+X-Windows corresponds to a single X window used to display the widget.
+
+Neither of these objects are readable; their print syntaxes look like
+@samp{#<xwidget>} and @samp{#<xwidget-view>}, respectively.  Lisp
+programmers should not be interested in the functionality of xwidget
+views.  @xref{Xwidgets}, for a more detailed description of xwidgets.
+
 @node Circular Objects
 @section Read Syntax for Circular Objects
 @cindex circular structure, read syntax
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07 12:03                             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-07 12:33                               ` Eli Zaretskii
  2021-11-07 12:40                                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 39+ messages in thread
From: Eli Zaretskii @ 2021-11-07 12:33 UTC (permalink / raw)
  To: Po Lu; +Cc: larsi, 51473

> From: Po Lu <luangruo@yahoo.com>
> Cc: larsi@gnus.org,  51473@debbugs.gnu.org
> Date: Sun, 07 Nov 2021 20:03:04 +0800
> 
> > The printed representation of xwidget objects should be documented in
> > the ELisp manual, under the "Editing Types" section.  In fact, we lack
> > a subsection there for xwidgets; it should be added.
> >
> > Thanks.
> 
> Will this work?

With some minor changes, yes.

> +@node Xwidget Type
> +@subsection Xwidget Type

It is generally a good idea to have a @cindex entry with the name of
the section (but with lower-case letters), so that the section could
be easily found via Info-index.

> +                                                Each window such an
> +xwidget is be displayed in

This style is better avoided: it produces long sentences that are hard
to grasp for non-native speakers, because the important part ("in") is
at the end.  It is better to say it simply:

  Each window that displays an xwidget will also have a @dfn{xwidget
  view}, ...

And add a @cindex for "xwidget-view", again generally a good practice
of indexing each place that introduces new terminology (indicated by
@dfn).

>                                                           , which on
> +X-Windows corresponds to a single X window used to display the widget.

What about NS?  And what about Wayland?

> +Neither of these objects are readable; their print syntaxes look like
> +@samp{#<xwidget>} and @samp{#<xwidget-view>}, respectively.  Lisp
> +programmers should not be interested in the functionality of xwidget
> +views.

I'd drop the second of these sentences.  It doesn't add anything to
the description.

Thanks.





^ permalink raw reply	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07 12:33                               ` Eli Zaretskii
@ 2021-11-07 12:40                                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-07 12:52                                   ` Eli Zaretskii
  0 siblings, 1 reply; 39+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-07 12:40 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, 51473

[-- Attachment #1: Type: text/plain, Size: 1047 bytes --]

Eli Zaretskii <eliz@gnu.org> writes:

> It is generally a good idea to have a @cindex entry with the name of
> the section (but with lower-case letters), so that the section could
> be easily found via Info-index.

Thanks, done.

> This style is better avoided: it produces long sentences that are hard
> to grasp for non-native speakers, because the important part ("in") is
> at the end.  It is better to say it simply:
>
>   Each window that displays an xwidget will also have a @dfn{xwidget
>   view}, ...
>
> And add a @cindex for "xwidget-view", again generally a good practice
> of indexing each place that introduces new terminology (indicated by
> @dfn).

Likewise.

> What about NS?  And what about Wayland?

I don't know exactly how it is handled on NS.  On Wayland, it's still an
X window that runs through Wayland's compatibility layer for X
applications.  (Like the rest of Emacs, at present.)

> I'd drop the second of these sentences.  It doesn't add anything to
> the description.

Done as well, please check the attached patch.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Document-the-xwidget-type.patch --]
[-- Type: text/x-patch, Size: 2150 bytes --]

From 728f07e20bfb8e16182ef7dae816e3512665d64b Mon Sep 17 00:00:00 2001
From: Po Lu <luangruo@yahoo.com>
Date: Sun, 7 Nov 2021 20:02:06 +0800
Subject: [PATCH] Document the xwidget type

* doc/lispref/elisp.texi: Add Xwidget Type to the menu.
* doc/lispref/objects.texi (Editing Types): Add Xwidget Type to the
menu.
(Xwidget Type): New node.
---
 doc/lispref/elisp.texi   |  1 +
 doc/lispref/objects.texi | 15 +++++++++++++++
 2 files changed, 16 insertions(+)

diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi
index d0bfd8c901..1c0b0fa1b5 100644
--- a/doc/lispref/elisp.texi
+++ b/doc/lispref/elisp.texi
@@ -365,6 +365,7 @@ Top
 * Keymap Type::             What function a keystroke invokes.
 * Overlay Type::            How an overlay is represented.
 * Font Type::               Fonts for displaying text.
+* Xwidget Type::            Embeddable widgets.
 
 Numbers
 
diff --git a/doc/lispref/objects.texi b/doc/lispref/objects.texi
index 0551bb5673..bbd3973f61 100644
--- a/doc/lispref/objects.texi
+++ b/doc/lispref/objects.texi
@@ -1535,6 +1535,7 @@ Editing Types
 * Keymap Type::         What function a keystroke invokes.
 * Overlay Type::        How an overlay is represented.
 * Font Type::           Fonts for displaying text.
+* Xwidget Type::        Embeddable widgets.
 @end menu
 
 @node Buffer Type
@@ -1860,6 +1861,20 @@ Font Type
 @samp{#<font-entity>} respectively.  @xref{Low-Level Font}, for a
 description of these Lisp objects.
 
+@node Xwidget Type
+@subsection Xwidget Type
+@cindex xwidget type
+@cindex xwidget-view type
+
+  An @dfn{xwidget} is a special display element, such as a web
+browser, that can be embedded inside a buffer.  Each window that
+displays an xwidget will also have an @dfn{xwidget view}, which on
+X-Windows corresponds to a single X window used to display the widget.
+
+Neither of these objects are readable; their print syntaxes look like
+@samp{#<xwidget>} and @samp{#<xwidget-view>}, respectively.
+@xref{Xwidgets}, for a more detailed description of xwidgets.
+
 @node Circular Objects
 @section Read Syntax for Circular Objects
 @cindex circular structure, read syntax
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements
  2021-11-07 12:40                                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-07 12:52                                   ` Eli Zaretskii
  0 siblings, 0 replies; 39+ messages in thread
From: Eli Zaretskii @ 2021-11-07 12:52 UTC (permalink / raw)
  To: Po Lu; +Cc: larsi, 51473

> From: Po Lu <luangruo@yahoo.com>
> Cc: larsi@gnus.org,  51473@debbugs.gnu.org
> Date: Sun, 07 Nov 2021 20:40:49 +0800
> 
> Done as well, please check the attached patch.

Thanks, installed.





^ permalink raw reply	[flat|nested] 39+ messages in thread

end of thread, other threads:[~2021-11-07 12:52 UTC | newest]

Thread overview: 39+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <87zgqslafe.fsf.ref@yahoo.com>
2021-10-29  4:30 ` bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-10-29 13:18   ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-04 23:11   ` Lars Ingebrigtsen
2021-11-05  7:20     ` Eli Zaretskii
2021-11-05  7:29       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-05  7:33         ` Lars Ingebrigtsen
2021-11-05  7:47           ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-05 13:35       ` Lars Ingebrigtsen
2021-11-05  7:34     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-05 13:10       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-05 13:32         ` Lars Ingebrigtsen
2021-11-05 13:28       ` Lars Ingebrigtsen
2021-11-05 13:40         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-05 13:48           ` Lars Ingebrigtsen
2021-11-06  0:00             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-06  0:22             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-06  0:25               ` Lars Ingebrigtsen
2021-11-06  2:06                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-06  5:39                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-06  8:58                   ` Eli Zaretskii
2021-11-06 10:41                     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-06 17:42                       ` Lars Ingebrigtsen
2021-11-07  0:41                         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-07  0:55                           ` Lars Ingebrigtsen
2021-11-07  1:16                             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-07  1:04                           ` Lars Ingebrigtsen
2021-11-07  1:18                             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-07  1:23                               ` Lars Ingebrigtsen
2021-11-07  1:26                                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-07  1:29                                   ` Lars Ingebrigtsen
2021-11-07  1:34                                     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-07  1:39                                       ` Lars Ingebrigtsen
2021-11-07  1:55                                         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-07  2:04                                           ` Lars Ingebrigtsen
2021-11-07 10:09                           ` Eli Zaretskii
2021-11-07 12:03                             ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-07 12:33                               ` Eli Zaretskii
2021-11-07 12:40                                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-07 12:52                                   ` Eli Zaretskii

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).