Subject: bug#51473: [PATCH] Enable xwidget scrolling optimizations, and other xwidgets improvements

From: Po Lu <[email-masked]>
Date: Sun, 07 Nov 2021 08:41:15 +0800

Lars Ingebrigtsen <[email-masked]> 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.

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.
+
 
 * 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.
+
 
 * 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) {}