From 3be38a63735e911b7322591af5e38d1ac90a3125 Mon Sep 17 00:00:00 2001
From: Jaesup Kwak <veshboo@gmail.com>
Date: Mon, 4 Dec 2017 21:23:19 +0900
Subject: [PATCH] Support xwidget webkit for macOS X

Add xwidget webkit support for macOS X / NS Cocoa and accompanying
changes.

Squash changes for comments from Alan Third in Bug#29565.

* configure.ac: Allow '--with-xwidgets' for "${NS_IMPL_COCOA}".
* lisp/xwidget.el
(xwidget-webkit-cx2-cb, xwidget-webkit-cx3-cb, xwidget-webkit-cx2)
(xwidget-webkit-cx3, xwidget-webkit-mode-map): New webkit session when
window split and key map for 'nextstep'.
(xwidget-log): Fix typo in buffer name.
(xwidget-event-handler): Remove a 'message'.
(xwidget-webkit-callback, xwidget-webkit-url-title-cb): Replace lambda
with defun for GC safe, remember URL for bookmark integration, and
support vector result from javascript.
(xwidget-webkit-mode): No cursor and integration for 'isearch'.
(xwidget-webkit-bookmark-jump-new-session): Add new variable.
(xwidget-webkit-bookmark-make-record): Changes to save URL in
'filename' attribute of a bookmark record, the URL obtained when the
page was loaded, and 'switch-to-buffer' when a bookmark selected.
(xwidget-webkit-isearch-last-length, xwidget-webkit-search-js)
(xwidget-webkit-search-fun-function): Integration for 'isearch'.
(xwidget-webkit-insert-string, xwidget-webkit-insert-string-cb): GC
safe, FIELD is now a list, and fix for a javascript exception.
(xwidget-window-inside-pixel-width)
(xwidget-window-inside-pixel-height)
(xwidget-webkit-adjust-size-to-window)
(xwidget-webkit-new-session): New functions to get window inside width
and height in pixel and insert invisible URL instead of ' '.
(xwidget-webkit-current-url, xwidget-webkit-current-url-cb): GC safe.
(xwidget-webkit-copy-selection-as-kill): GC safe.
* nextstep/templates/Info.plist.in: Add 'NSAppTransportSecurity'.
* src/Makefile.in: Add nsxwidget.o for compilation.
* src/emacs.c (main): Simplify conditional call to 'syms_of_xwidget'.
* src/nsterm.m (ns_draw_glyph_string): Add case for 'XWIDGET_GLYPH'.
(note_mouse_movement mouseMoved): Make it easy to resize window by
dragging mode-line or vertical separator adjacent to large glyph.
* src/nsxwidget.h src/nsxwidget.m: Newly added files, xwidget webkit
backend for macOS Cocoa.
* src/xwidget.c src/xwidget.h (xwidget xwidget_view): Add macOS Cocoa
specific fields with 'NS_IMPL_COCOA', guard GTK specific fields with
'USE_GTK' in the structures, and implement accordingly.
(x_draw_xwidget_glyph_string): Change for top alignment of xwidgets in
a glyph string instead of vertically middle alignment.
---
 configure.ac                     |  35 ++-
 lisp/xwidget.el                  | 235 ++++++++++++++----
 nextstep/templates/Info.plist.in |   8 +
 src/Makefile.in                  |   1 +
 src/emacs.c                      |   5 +-
 src/nsterm.m                     |  20 +-
 src/nsxwidget.h                  |  77 ++++++
 src/nsxwidget.m                  | 509 +++++++++++++++++++++++++++++++++++++++
 src/xwidget.c                    | 182 ++++++++++++--
 src/xwidget.h                    |  35 ++-
 10 files changed, 1029 insertions(+), 78 deletions(-)
 create mode 100644 src/nsxwidget.h
 create mode 100644 src/nsxwidget.m

diff --git a/configure.ac b/configure.ac
index 61455a4b0f..3d521fcfb1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -399,7 +399,7 @@ AC_DEFUN
  [with_file_notification=$with_features])
 
 OPTION_DEFAULT_OFF([xwidgets],
-  [enable use of some gtk widgets in Emacs buffers (requires gtk3)])
+  [enable use of some gtk widgets in Emacs buffers (requires gtk3 or macOS Cocoa)])
 
 ## For the times when you want to build Emacs but don't have
 ## a suitable makeinfo, and can live without the manuals.
@@ -2715,20 +2715,34 @@ AC_DEFUN
 
 
 dnl Enable xwidgets if GTK3 and WebKitGTK+ are available.
+dnl Enable xwidgets if macOS Cocoa and WebKit framework are available.
 HAVE_XWIDGETS=no
 XWIDGETS_OBJ=
 if test "$with_xwidgets" != "no"; then
-  test "$USE_GTK_TOOLKIT" = "GTK3" && test "$window_system" != "none" ||
-    AC_MSG_ERROR([xwidgets requested but gtk3 not used.])
+  if test "$USE_GTK_TOOLKIT" = "GTK3" && test "$window_system" != "none"; then
+    WEBKIT_REQUIRED=2.12
+    WEBKIT_MODULES="webkit2gtk-4.0 >= $WEBKIT_REQUIRED"
+    EMACS_CHECK_MODULES([WEBKIT], [$WEBKIT_MODULES])
+    HAVE_XWIDGETS=$HAVE_WEBKIT
+    XWIDGETS_OBJ="xwidget.o"
+  elif test "${NS_IMPL_COCOA}" = "yes"; then
+    dnl FIXME: Check framework WebKit2
+    dnl WEBKIT_REQUIRED=M.m.p
+    WEBKIT_LIBS="-Wl,-framework -Wl,WebKit"
+    WEBKIT_CFLAGS="-D_REENTRANT -I/System/Library/Frameworks/WebKit.framework/Headers"
+    HAVE_WEBKIT="yes"
+    HAVE_XWIDGETS=$HAVE_WEBKIT
+    XWIDGETS_OBJ="xwidget.o"
+    NS_OBJC_OBJ="$NS_OBJC_OBJ nsxwidget.o"
+    dnl Update NS_OBJC_OBJ with added nsxwidget.o
+    AC_SUBST(NS_OBJC_OBJ)
+  else
+    AC_MSG_ERROR([xwidgets requested, it requires GTK3 as X window toolkit or macOS Cocoa as window systeml])
+  fi
 
-  WEBKIT_REQUIRED=2.12
-  WEBKIT_MODULES="webkit2gtk-4.0 >= $WEBKIT_REQUIRED"
-  EMACS_CHECK_MODULES([WEBKIT], [$WEBKIT_MODULES])
-  HAVE_XWIDGETS=$HAVE_WEBKIT
   test $HAVE_XWIDGETS = yes ||
-    AC_MSG_ERROR([xwidgets requested but WebKitGTK+ not found.])
+    AC_MSG_ERROR([xwidgets requested but WebKitGTK+ or WebKit framework not found.])
 
-  XWIDGETS_OBJ=xwidget.o
   AC_DEFINE([HAVE_XWIDGETS], 1, [Define to 1 if you have xwidgets support.])
 fi
 AC_SUBST(XWIDGETS_OBJ)
@@ -5419,7 +5433,8 @@ AC_DEFUN
   Does Emacs directly use zlib?                           ${HAVE_ZLIB}
   Does Emacs have dynamic modules support?                ${HAVE_MODULES}
   Does Emacs use toolkit scroll bars?                     ${USE_TOOLKIT_SCROLL_BARS}
-  Does Emacs support Xwidgets (requires gtk3)?            ${HAVE_XWIDGETS}
+  Does Emacs support Xwidgets?                            ${HAVE_XWIDGETS}
+    (requires gtk3 or macOS Cocoa)
   Does Emacs have threading support in lisp?              ${threads_enabled}
 "])
 
diff --git a/lisp/xwidget.el b/lisp/xwidget.el
index 5e37209cc2..03183c19d5 100644
--- a/lisp/xwidget.el
+++ b/lisp/xwidget.el
@@ -78,6 +78,7 @@ xwidget-at
 ;;; webkit support
 (require 'browse-url)
 (require 'image-mode);;for some image-mode alike functionality
+(require 'seq)
 
 ;;;###autoload
 (defun xwidget-webkit-browse-url (url &optional new-session)
@@ -96,6 +97,38 @@ xwidget-webkit-browse-url
         (xwidget-webkit-new-session url)
       (xwidget-webkit-goto-url url))))
 
+;; NOTE: @javascript-callback - prefer defun to lambda.
+;; Lambda seems to be more easily garbage collected in flight from
+;; `xwidget-webkit-execute-script' to its execution via event.
+
+;; @javascript-callback
+(defun xwidget-webkit-cx2-cb (url)
+  "New xwidget webkit session and buffer with URL in split window below."
+  (with-selected-window (split-window-below)
+    (xwidget-webkit-new-session url)))
+
+;; @javascript-callback
+(defun xwidget-webkit-cx3-cb (url)
+  "New xwidget webkit session and buffer with URL in split window right."
+  (with-selected-window (split-window-right)
+    (xwidget-webkit-new-session url)))
+
+(defun xwidget-webkit-cx2 ()
+  "Get the URL of current session, then `xwidget-webkit-cx2-cb'."
+  (interactive)
+  (xwidget-webkit-execute-script
+   (xwidget-webkit-current-session)
+   "document.URL"
+   'xwidget-webkit-cx2-cb))
+
+(defun xwidget-webkit-cx3 ()
+  "Get the URL of current session, then `xwidget-webkit-cx3-cb'."
+  (interactive)
+  (xwidget-webkit-execute-script
+   (xwidget-webkit-current-session)
+   "document.URL"
+   'xwidget-webkit-cx3-cb))
+
 ;;todo.
 ;; - check that the webkit support is compiled in
 (defvar xwidget-webkit-mode-map
@@ -131,6 +164,12 @@ xwidget-webkit-mode-map
     ;; (define-key map [remap move-end-of-line]       'image-eol)
     (define-key map [remap beginning-of-buffer] 'xwidget-webkit-scroll-top)
     (define-key map [remap end-of-buffer]       'xwidget-webkit-scroll-bottom)
+
+    ;; For macOS xwidget webkit, we don't support multiple views for a
+    ;; model, instead, create a new session and model behind the scene.
+    (when (memq window-system '(mac ns))
+      (define-key map (kbd "C-x 2") 'xwidget-webkit-cx2)
+      (define-key map (kbd "C-x 3") 'xwidget-webkit-cx3))
     map)
   "Keymap for `xwidget-webkit-mode'.")
 
@@ -192,7 +231,7 @@ xwidget-webkit-scroll-bottom
 (define-key (current-global-map) [xwidget-event] #'xwidget-event-handler)
 (defun xwidget-log (&rest msg)
   "Log MSG to a buffer."
-  (let ((buf (get-buffer-create " *xwidget-log*")))
+  (let ((buf (get-buffer-create "*xwidget-log*")))
     (with-current-buffer buf
       (insert (apply #'format msg))
       (insert "\n"))))
@@ -208,7 +247,6 @@ xwidget-event-handler
        ;;TODO stopped working for some reason
        )
     ;;(funcall  xwidget-callback xwidget xwidget-event-type)
-    (message "xw callback %s" xwidget)
     (funcall  'xwidget-webkit-callback xwidget xwidget-event-type)))
 
 (defun xwidget-webkit-callback (xwidget xwidget-event-type)
@@ -218,16 +256,25 @@ xwidget-webkit-callback
       (xwidget-log
        "error: callback called for xwidget with dead buffer")
     (with-current-buffer (xwidget-buffer xwidget)
+
+      ;; @javascript-callback
+      ;; We do not change selected window due to getting to knowing
+      ;; URL and title.  And also do not adjust webkit size to window
+      ;; here, the window can be the mini-buffer window unwantedly.
+      (defun xwidget-webkit-url-title-cb (url-title)
+        "Put URL as text property and change buffer name using TITLE."
+        (let ((url (car url-title))
+              (title (car (cdr url-title))))
+          (xwidget-log "webkit finished loading: '%s' from '%s'" title url)
+          (setq buffer-read-only nil)
+          (put-text-property 2 3 'URL url)
+          (setq buffer-read-only t)
+          (rename-buffer (format "*xwidget webkit: %s *" title))))
+
       (cond ((eq xwidget-event-type 'load-changed)
              (xwidget-webkit-execute-script
-              xwidget "document.title"
-              (lambda (title)
-                (xwidget-log "webkit finished loading: '%s'" title)
-                ;;TODO - check the native/internal scroll
-                ;;(xwidget-adjust-size-to-content xwidget)
-                (xwidget-webkit-adjust-size-to-window xwidget)
-                (rename-buffer (format "*xwidget webkit: %s *" title))))
-             (pop-to-buffer (current-buffer)))
+              xwidget "[document.URL, document.title]"
+              'xwidget-webkit-url-title-cb))
             ((eq xwidget-event-type 'decide-policy)
              (let ((strarg  (nth 3 last-input-event)))
                (if (string-match ".*#\\(.*\\)" strarg)
@@ -237,25 +284,108 @@ xwidget-webkit-callback
             ((eq xwidget-event-type 'javascript-callback)
              (let ((proc (nth 3 last-input-event))
                    (arg  (nth 4 last-input-event)))
-               (funcall proc arg)))
+               ;; Some javascript return vector as result
+               (if (vectorp arg)
+                   (funcall proc (seq-into arg 'list))
+                 (funcall proc arg))))
             (t (xwidget-log "unhandled event:%s" xwidget-event-type))))))
 
 (defvar bookmark-make-record-function)
+(defvar isearch-search-fun-function)
 (define-derived-mode xwidget-webkit-mode
     special-mode "xwidget-webkit" "Xwidget webkit view mode."
     (setq buffer-read-only t)
+    (setq cursor-type nil)
     (setq-local bookmark-make-record-function
                 #'xwidget-webkit-bookmark-make-record)
+    (setq-local isearch-search-fun-function
+                #'xwidget-webkit-search-fun-function)
+    (setq-local isearch-lazy-highlight nil)
     ;; Keep track of [vh]scroll when switching buffers
     (image-mode-setup-winprops))
 
+;;; Bookmarks integration
+
+(defvar xwidget-webkit-bookmark-jump-new-session nil
+  "Control bookmark jump to use new session or not.
+If non-nil, it will use a new session.  Otherwise, it will use
+`xwidget-webkit-last-session'.  When you set this variable to
+nil, consider further customization with
+`xwidget-webkit-last-session-buffer'.")
+
+;; We avoid using async `xwidget-webkit-current-url', instead use URL
+;; kept in xwidget webkit as property
 (defun xwidget-webkit-bookmark-make-record ()
   "Integrate Emacs bookmarks with the webkit xwidget."
   (nconc (bookmark-make-record-default t t)
-         `((page     . ,(xwidget-webkit-current-url))
-           (handler  . (lambda (bmk) (browse-url
-                                 (bookmark-prop-get bmk 'page)))))))
-
+         `((filename . ,(get-text-property 2 'URL))
+           (handler  . (lambda (bmk)
+                         (browse-url
+                          (bookmark-prop-get bmk 'filename)
+                          xwidget-webkit-bookmark-jump-new-session)
+                         (switch-to-buffer
+                          (xwidget-buffer (xwidget-webkit-last-session))))))))
+
+;;; Search text in page
+
+;; Initialize last search text length variable when isearch starts
+(defvar xwidget-webkit-isearch-last-length 0)
+(add-hook 'isearch-mode-hook
+          (lambda ()
+            (setq xwidget-webkit-isearch-last-length 0)))
+
+;; This is minimal. Regex and incremental search will be nice
+(defvar xwidget-webkit-search-js "
+var xwSearchForward = %s;
+var xwSearchRepeat = %s;
+var xwSearchString = '%s';
+if (window.getSelection() && !window.getSelection().isCollapsed) {
+  if (xwSearchRepeat) {
+    if (xwSearchForward)
+      window.getSelection().collapseToEnd();
+    else
+      window.getSelection().collapseToStart();
+  } else {
+    if (xwSearchForward)
+      window.getSelection().collapseToStart();
+    else {
+      var sel = window.getSelection();
+      window.getSelection().collapse(sel.focusNode, sel.focusOffset + 1);
+    }
+  }
+}
+window.find(xwSearchString, false, !xwSearchForward, true, false, true);
+")
+
+(defun xwidget-webkit-search-fun-function ()
+  "Return the function which perform the search in xwidget webkit."
+  (lambda (string &optional bound noerror count)
+    (ignore bound noerror count)
+    (let ((current-length (length string))
+          search-forward
+          search-repeat)
+      ;; Forward or backward
+      (if (eq isearch-forward nil)
+          (setq search-forward "false")
+        (setq search-forward "true"))
+      ;; Repeat if search string length not changed
+      (if (eq current-length xwidget-webkit-isearch-last-length)
+          (setq search-repeat "true")
+        (setq search-repeat "false"))
+      (setq xwidget-webkit-isearch-last-length current-length)
+      (xwidget-webkit-execute-script
+       (xwidget-webkit-current-session)
+       (format xwidget-webkit-search-js
+               search-forward
+               search-repeat
+               (regexp-quote string)))
+      ;; Unconditionally avoid 'Failing I-search ...'
+      (if (eq isearch-forward nil)
+          (goto-char (point-max))
+        (goto-char (point-min)))
+      )))
+
+;;; xwidget webkit session
 
 (defvar xwidget-webkit-last-session-buffer nil)
 
@@ -303,7 +433,7 @@ xwidget-webkit-activeelement-js"
 
 "
 
-  "javascript that finds the active element."
+  "Javascript that finds the active element."
   ;; Yes it's ugly, because:
   ;; - there is apparently no way to find the active frame other than recursion
   ;; - the js "for each" construct misbehaved on the "frames" collection
@@ -313,29 +443,35 @@ xwidget-webkit-activeelement-js"
   )
 
 (defun xwidget-webkit-insert-string ()
-  "Prompt for a string and insert it in the active field in the
+  "Prompt for a string and insert it in the active field in the \
 current webkit widget."
   ;; Read out the string in the field first and provide for edit.
   (interactive)
   (let ((xww (xwidget-webkit-current-session)))
+
+    ;; @javascript-callback
+    (defun xwidget-webkit-insert-string-cb (field)
+      "Prompt a string for the FIELD and insert in the active input."
+      (let ((str (pcase field
+                   (`(,val "text")
+                    (read-string "Text: " val))
+                   (`(,val "password")
+                    (read-passwd "Password: " nil val))
+                   (`(,val "textarea")
+                    (xwidget-webkit-begin-edit-textarea xww val)))))
+        (xwidget-webkit-execute-script
+         xww
+         (format "findactiveelement(document).value='%s'" str))))
+
     (xwidget-webkit-execute-script
      xww
      (concat xwidget-webkit-activeelement-js "
 (function () {
   var res = findactiveelement(document);
-  return [res.value, res.type];
+  if (res)
+    return [res.value, res.type];
 })();")
-     (lambda (field)
-       (let ((str (pcase field
-                    (`[,val "text"]
-                     (read-string "Text: " val))
-                    (`[,val "password"]
-                     (read-passwd "Password: " nil val))
-                    (`[,val "textarea"]
-                     (xwidget-webkit-begin-edit-textarea xww val)))))
-         (xwidget-webkit-execute-script
-          xww
-          (format "findactiveelement(document).value='%s'" str)))))))
+     'xwidget-webkit-insert-string-cb)))
 
 (defvar xwidget-xwbl)
 (defun xwidget-webkit-begin-edit-textarea (xw text)
@@ -444,11 +580,23 @@ xwidget-webkit-adjust-size-dispatch
   (ignore-errors
     (recenter-top-bottom)))
 
+;; Utility functions, wanted in `window.el'
+
+(defun xwidget-window-inside-pixel-width (window)
+  "Return Emacs WINDOW body width in pixel."
+  (let ((edges (window-inside-pixel-edges window)))
+    (- (nth 2 edges) (nth 0 edges))))
+
+(defun xwidget-window-inside-pixel-height (window)
+  "Return Emacs WINDOW body height in pixel."
+  (let ((edges (window-inside-pixel-edges window)))
+    (- (nth 3 edges) (nth 1 edges))))
+
 (defun xwidget-webkit-adjust-size-to-window (xwidget &optional window)
   "Adjust the size of the webkit XWIDGET to fit the WINDOW."
   (xwidget-resize xwidget
-                  (window-pixel-width window)
-                  (window-pixel-height window)))
+                  (xwidget-window-inside-pixel-width window)
+                  (xwidget-window-inside-pixel-height window)))
 
 (defun xwidget-webkit-adjust-size (w h)
   "Manually set webkit size to width W, height H."
@@ -487,10 +635,12 @@ xwidget-webkit-new-session
                                               (get-buffer-create bufname)))
     ;; The xwidget id is stored in a text property, so we need to have
     ;; at least character in this buffer.
-    (insert " ")
+    ;; Insert invisible url, good default for next `g' to browse url.
+    (insert url)
+    (put-text-property 1 (+ 1 (length url)) 'invisible t)
     (setq xw (xwidget-insert 1 'webkit bufname
-                             (window-pixel-width)
-                             (window-pixel-height)))
+                             (xwidget-window-inside-pixel-width (selected-window))
+                             (xwidget-window-inside-pixel-height (selected-window))))
     (xwidget-put xw 'callback 'xwidget-webkit-callback)
     (xwidget-webkit-mode)
     (xwidget-webkit-goto-uri (xwidget-webkit-last-session) url)))
@@ -515,14 +665,18 @@ xwidget-webkit-reload
   (xwidget-webkit-execute-script (xwidget-webkit-current-session)
                                  "history.go(0);"))
 
+;; @javascript-callback
+(defun xwidget-webkit-current-url-cb (result)
+  "Callback for `xwidget-webkit-current-url', message and kill the RESULT."
+  (let ((url (kill-new (or result ""))))
+    (message "url: %s" url)))
+
 (defun xwidget-webkit-current-url ()
-  "Get the webkit url and place it on the kill-ring."
+  "Get the webkit url and place it on the `kill-ring'."
   (interactive)
   (xwidget-webkit-execute-script
    (xwidget-webkit-current-session)
-   "document.URL" (lambda (rv)
-                    (let ((url (kill-new (or rv ""))))
-                      (message "url: %s" url)))))
+   "document.URL" 'xwidget-webkit-current-url-cb))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 (defun xwidget-webkit-get-selection (proc)
@@ -533,10 +687,9 @@ xwidget-webkit-get-selection
    proc))
 
 (defun xwidget-webkit-copy-selection-as-kill ()
-  "Get the webkit selection and put it on the kill-ring."
+  "Get the webkit selection and put it on the `kill-ring'."
   (interactive)
-  (xwidget-webkit-get-selection (lambda (selection) (kill-new selection))))
-
+  (xwidget-webkit-get-selection #'kill-new))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Xwidget plist management (similar to the process plist functions)
diff --git a/nextstep/templates/Info.plist.in b/nextstep/templates/Info.plist.in
index 5d2eb7def3..54544fb7fa 100644
--- a/nextstep/templates/Info.plist.in
+++ b/nextstep/templates/Info.plist.in
@@ -675,5 +675,13 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
 	</array>
 	<key>NSAppleScriptEnabled</key>
 	<string>YES</string>
+        <!-- For xwidget webkit to browse remote url,
+             but this set no restriction at all.  Consult apple's documentation
+             for detail information about `NSApplicationDefinedMask'. -->
+        <key>NSAppTransportSecurity</key>
+        <dict>
+          <key>NSAllowsArbitraryLoads</key>
+          <true/>
+        </dict>
 </dict>
 </plist>
diff --git a/src/Makefile.in b/src/Makefile.in
index 9a8c9c85f0..5cadb71c95 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -408,6 +408,7 @@ SOME_MACHINE_OBJECTS =
   xterm.o xfns.o xmenu.o xselect.o xrdb.o xsmfns.o fringe.o image.o \
   fontset.o dbusbind.o cygw32.o \
   nsterm.o nsfns.o nsmenu.o nsselect.o nsimage.o nsfont.o macfont.o \
+  nsxwidget.o \
   w32.o w32console.o w32fns.o w32heap.o w32inevt.o w32notify.o \
   w32menu.o w32proc.o w32reg.o w32select.o w32term.o w32xfns.o \
   w16select.o widget.o xfont.o ftfont.o xftfont.o ftxfont.o gtkutil.o \
diff --git a/src/emacs.c b/src/emacs.c
index 808abcd9aa..8e740381e1 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -1532,7 +1532,6 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
       syms_of_xfns ();
       syms_of_xmenu ();
       syms_of_fontset ();
-      syms_of_xwidget ();
       syms_of_xsettings ();
 #ifdef HAVE_X_SM
       syms_of_xsmfns ();
@@ -1605,6 +1604,10 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
 #endif /* HAVE_W32NOTIFY */
 #endif /* WINDOWSNT */
 
+#ifdef HAVE_XWIDGETS
+      syms_of_xwidget ();
+#endif /* HAVE_XWIDGETS */
+
       syms_of_threads ();
       syms_of_profiler ();
 
diff --git a/src/nsterm.m b/src/nsterm.m
index 50e06c94d4..3d85fdc0d5 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -48,6 +48,7 @@ Updated by Christian Limpach (chris@nice.ch)
 #include "nsterm.h"
 #include "systime.h"
 #include "character.h"
+#include "xwidget.h"
 #include "fontset.h"
 #include "composite.h"
 #include "ccl.h"
@@ -2429,7 +2430,7 @@ so some key presses (TAB) are swallowed by the system. */
 }
 
 static int
-note_mouse_movement (struct frame *frame, CGFloat x, CGFloat y)
+note_mouse_movement (struct frame *frame, CGFloat x, CGFloat y, BOOL dragging)
 /*   ------------------------------------------------------------------------
      Called by EmacsView on mouseMovement events.  Passes on
      to emacs mainstream code if we moved off of a rect of interest
@@ -2438,17 +2439,24 @@ so some key presses (TAB) are swallowed by the system. */
 {
   struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (frame);
   NSRect *r;
+  BOOL force_update = NO;
 
 //  NSTRACE ("note_mouse_movement");
 
   dpyinfo->last_mouse_motion_frame = frame;
   r = &dpyinfo->last_mouse_glyph;
 
+  /* If the last rect is too large (ex, xwidget webkit), update at
+     every move, or resizing by dragging modeline or vertical split is
+     very hard to make its way.  */
+  if (dragging && (r->size.width > 32 || r->size.height > 32))
+    force_update = YES;
+
   /* Note, this doesn't get called for enter/leave, since we don't have a
      position.  Those are taken care of in the corresponding NSView methods. */
 
   /* has movement gone beyond last rect we were tracking? */
-  if (x < r->origin.x || x >= r->origin.x + r->size.width
+  if (force_update || x < r->origin.x || x >= r->origin.x + r->size.width
       || y < r->origin.y || y >= r->origin.y + r->size.height)
     {
       ns_update_begin (frame);
@@ -4066,6 +4074,10 @@ overwriting cursor (usually when cursor on a tab) */
       ns_unfocus (s->f);
       break;
 
+    case XWIDGET_GLYPH:
+      x_draw_xwidget_glyph_string (s);
+      break;
+
     case STRETCH_GLYPH:
       ns_dumpglyphs_stretch (s);
       break;
@@ -6724,6 +6736,7 @@ - (void)mouseMoved: (NSEvent *)e
   struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (emacsframe);
   Lisp_Object frame;
   NSPoint pt;
+  BOOL dragging;
 
   NSTRACE_WHEN (NSTRACE_GROUP_EVENTS, "[EmacsView mouseMoved:]");
 
@@ -6766,7 +6779,8 @@ - (void)mouseMoved: (NSEvent *)e
       last_mouse_window = window;
     }
 
-  if (!note_mouse_movement (emacsframe, pt.x, pt.y))
+  dragging = (e.type == NSEventTypeLeftMouseDragged);
+  if (!note_mouse_movement (emacsframe, pt.x, pt.y, dragging))
     help_echo_string = previous_help_echo_string;
 
   XSETFRAME (frame, emacsframe);
diff --git a/src/nsxwidget.h b/src/nsxwidget.h
new file mode 100644
index 0000000000..a617a0d527
--- /dev/null
+++ b/src/nsxwidget.h
@@ -0,0 +1,77 @@
+/* Header for NS Cocoa part of xwidget and webkit widget.
+
+Copyright (C) 2011-2017 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef NSXWIDGET_H_INCLUDED
+#define NSXWIDGET_H_INCLUDED
+
+/* This file can be included from non-objc files through 'xwidget.h'.  */
+#ifdef __OBJC__
+#import <AppKit/NSView.h>
+#endif
+
+#include "dispextern.h"
+#include "lisp.h"
+#include "xwidget.h"
+
+/* Functions for xwidget webkit.  */
+
+bool nsxwidget_is_web_view (struct xwidget *xw);
+void nsxwidget_webkit_goto_uri (struct xwidget *xw, const char *uri);
+void nsxwidget_webkit_zoom (struct xwidget *xw, double zoom_change);
+void nsxwidget_webkit_execute_script (struct xwidget *xw, const char *script,
+                                      Lisp_Object fun);
+
+/* Functions for xwidget model.  */
+
+#ifdef __OBJC__
+@interface XwWindow : NSView
+@property struct xwidget *xw;
+@end
+#endif
+
+void nsxwidget_init (struct xwidget *xw);
+void nsxwidget_kill (struct xwidget *xw);
+void nsxwidget_resize (struct xwidget *xw);
+Lisp_Object nsxwidget_get_size (struct xwidget *xw);
+
+/* Functions for xwidget view.  */
+
+#ifdef __OBJC__
+@interface XvWindow : NSView
+@property struct xwidget *xw;
+@property struct xwidget_view *xv;
+@end
+#endif
+
+void nsxwidget_init_view (struct xwidget_view *xv,
+                          struct xwidget *xww,
+                          struct glyph_string *s,
+                          int x, int y);
+void nsxwidget_delete_view (struct xwidget_view *xv);
+
+void nsxwidget_show_view (struct xwidget_view *xv);
+void nsxwidget_hide_view (struct xwidget_view *xv);
+void nsxwidget_resize_view (struct xwidget_view *xv,
+                            int widget, int height);
+
+void nsxwidget_move_view (struct xwidget_view *xv, int x, int y);
+void nsxwidget_move_widget_in_view (struct xwidget_view *xv, int x, int y);
+void nsxwidget_set_needsdisplay (struct xwidget_view *xv);
+
+#endif /* NSXWIDGET_H_INCLUDED */
diff --git a/src/nsxwidget.m b/src/nsxwidget.m
new file mode 100644
index 0000000000..04f1472c96
--- /dev/null
+++ b/src/nsxwidget.m
@@ -0,0 +1,509 @@
+/* NS Cocoa part implementation of xwidget and webkit widget.
+
+Copyright (C) 1989, 1992-1994, 2005-2006, 2008-2017 Free Software
+Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include "lisp.h"
+#include "blockinput.h"
+#include "dispextern.h"
+#include "buffer.h"
+#include "frame.h"
+#include "nsterm.h"
+#include "xwidget.h"
+
+/* Defined in 'xwidget.c'.  */
+void store_xwidget_event_string (struct xwidget *xw,
+                                 const char *eventname,
+                                 const char *eventstr);
+
+void store_xwidget_js_callback_event (struct xwidget *xw,
+                                      Lisp_Object proc,
+                                      Lisp_Object argument);
+
+#import <AppKit/AppKit.h>
+#import <WebKit/WebKit.h>
+
+/* Thoughts on NS Cocoa xwidget and webkit2:
+
+   Webkit2 process architecture seems to be very hostile for offscreen
+   rendering techniques, which is used by GTK xwiget implementation;
+   Specifically NSView level view sharing / copying is not working.
+
+   *** So only one view can be associcated with a model. ***
+
+   With this decision, implementation is plain and can expect best out
+   of webkit2's rationale.  But process and session structures will
+   diverge from GTK xwiget.  Though, cosmetically similar usages can
+   be presented and will be preferred, if agreeable.
+
+   For other widget types, OSR seems possible, but will not care for a
+   while.  */
+
+/* Xwidget webkit.  */
+
+@interface XwWebView : WKWebView
+<WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler>
+@property struct xwidget *xw;
+@end
+@implementation XwWebView : WKWebView
+
+- (id)initWithFrame:(CGRect)frame
+      configuration:(WKWebViewConfiguration *)configuration
+            xwidget:(struct xwidget *)xw
+{
+  /* Script controller to add script message handler and user script.  */
+  WKUserContentController *scriptor = [[WKUserContentController alloc] init];
+  configuration.userContentController = scriptor;
+
+  self = [super initWithFrame:frame configuration:configuration];
+  if (self)
+    {
+      self.xw = xw;
+      self.navigationDelegate = self;
+      self.UIDelegate = self;
+      self.customUserAgent =
+        @"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6)"
+        @" AppleWebKit/603.3.8 (KHTML, like Gecko)"
+        @" Version/11.0.1 Safari/603.3.8";
+      [scriptor addScriptMessageHandler:self name:@"keyDown"];
+      [scriptor addUserScript:[[WKUserScript alloc]
+                                initWithSource:xwScript
+                                 injectionTime:
+                                  WKUserScriptInjectionTimeAtDocumentEnd
+                                forMainFrameOnly:NO]];
+    }
+  return self;
+}
+
+#if 0
+/* Non ARC - just to check lifecycle.  */
+- (void)dealloc
+{
+  NSLog (@"XwWebView dealloc");
+  [super dealloc];
+}
+#endif
+
+- (void)webView:(WKWebView *)webView
+didFinishNavigation:(WKNavigation *)navigation
+{
+  store_xwidget_event_string (self.xw, "load-changed", "");
+}
+
+- (void)webView:(WKWebView *)webView
+decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
+decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
+{
+  switch (navigationAction.navigationType) {
+  case WKNavigationTypeLinkActivated:
+    decisionHandler (WKNavigationActionPolicyAllow);
+    break;
+  default:
+    // decisionHandler (WKNavigationActionPolicyCancel);
+    decisionHandler (WKNavigationActionPolicyAllow);
+    break;
+  }
+}
+
+- (void)webView:(WKWebView *)webView
+decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
+decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
+{
+  if (!navigationResponse.canShowMIMEType)
+    {
+      /* TODO: download using NSURLxxx?  */
+    }
+  decisionHandler (WKNavigationResponsePolicyAllow);
+}
+
+/* No additional new webview or emacs window will be created
+   for <a ... target="_blank">.  */
+- (WKWebView *)webView:(WKWebView *)webView
+createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
+   forNavigationAction:(WKNavigationAction *)navigationAction
+        windowFeatures:(WKWindowFeatures *)windowFeatures
+{
+  if (!navigationAction.targetFrame.isMainFrame)
+    [webView loadRequest:navigationAction.request];
+  return nil;
+}
+
+/* By forwarding mouse events to emacs view (frame)
+   - Mouse click in webview selects the window contains the webview.
+   - Correct mouse hand/arrow/I-beam is displayed (TODO: not perfect yet).
+*/
+
+- (void)mouseDown:(NSEvent *)event
+{
+  [self.xw->xv->emacswindow mouseDown:event];
+  [super mouseDown:event];
+}
+
+- (void)mouseUp:(NSEvent *)event
+{
+  [self.xw->xv->emacswindow mouseUp:event];
+  [super mouseUp:event];
+}
+
+/* Basically we want keyboard events handled by emacs unless an input
+   element has focus.  Especially, while incremental search, we set
+   emacs as first responder to avoid focus held in an input element
+   with matching text.  */
+
+- (void)keyDown:(NSEvent *)event
+{
+  Lisp_Object var = Fintern (build_string ("isearch-mode"), Qnil);
+  Lisp_Object val = buffer_local_value (var, Fcurrent_buffer ());
+  if (!EQ (val, Qunbound) && !EQ (val, Qnil))
+    {
+      [self.window makeFirstResponder:self.xw->xv->emacswindow];
+      [self.xw->xv->emacswindow keyDown:event];
+      return;
+    }
+
+  [self evaluateJavaScript:@"xwHasFocus()"
+         completionHandler:^(id result, NSError *error) {
+      if (error)
+        NSLog (@"xwHasFocus: %@", error.localizedDescription);
+      else if (result)
+        {
+          NSNumber *hasFocus = result; /* __NSCFBoolean */
+          if (!hasFocus.boolValue)
+            [self.xw->xv->emacswindow keyDown:event];
+          else
+            [super keyDown:event];
+        }
+    }];
+}
+
+- (void)interpretKeyEvents:(NSArray<NSEvent *> *)eventArray
+{
+  /* We should do nothing and do not forward (default implementation
+     if we not override here) to let emacs collect key events and ask
+     interpretKeyEvents to its superclass.  */
+}
+
+static NSString *xwScript;
++ (void)initialize
+{
+  /* Find out if an input element has focus.
+     Message to script message handler when 'C-g' key down.  */
+  if (!xwScript)
+    xwScript =
+      @"function xwHasFocus() {"
+      @"  var ae = document.activeElement;"
+      @"  if (ae) {"
+      @"    var name = ae.nodeName;"
+      @"    return name == 'INPUT' || name == 'TEXTAREA';"
+      @"  } else {"
+      @"    return false;"
+      @"  }"
+      @"}"
+      @"function xwKeyDown(event) {"
+      @"  if (event.ctrlKey && event.key == 'g') {"
+      @"    window.webkit.messageHandlers.keyDown.postMessage('C-g');"
+      @"  }"
+      @"}"
+      @"document.addEventListener('keydown', xwKeyDown);"
+      ;
+}
+
+/* Confirming to WKScriptMessageHandler, listens concerning keyDown in
+   webkit. Currently 'C-g'.  */
+- (void)userContentController:(WKUserContentController *)userContentController
+      didReceiveScriptMessage:(WKScriptMessage *)message
+{
+  if ([message.body isEqualToString:@"C-g"])
+    {
+      /* Just give up focus, no relay "C-g" to emacs, another "C-g"
+         follows will be handled by emacs.  */
+      [self.window makeFirstResponder:self.xw->xv->emacswindow];
+    }
+}
+
+@end
+
+/* Xwidget webkit commands.  */
+
+bool
+nsxwidget_is_web_view (struct xwidget *xw)
+{
+  return xw->xwWidget != NULL &&
+    [xw->xwWidget isKindOfClass:WKWebView.class];
+}
+
+/* @Note ATS - Need application transport security in 'Info.plist' or
+   remote pages will not loaded.  */
+void
+nsxwidget_webkit_goto_uri (struct xwidget *xw, const char *uri)
+{
+  XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
+  NSString *urlString = [NSString stringWithUTF8String:uri];
+  NSURL *url = [NSURL URLWithString:urlString];
+  NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
+  [xwWebView loadRequest:urlRequest];
+}
+
+void
+nsxwidget_webkit_zoom (struct xwidget *xw, double zoom_change)
+{
+  XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
+  xwWebView.magnification += zoom_change;
+  /* TODO: setMagnification:centeredAtPoint.  */
+}
+
+/* Build lisp string */
+static Lisp_Object
+build_string_with_nsstr (NSString *nsstr)
+{
+  const char *utfstr = [nsstr UTF8String];
+  NSUInteger bytes = [nsstr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+  return make_string (utfstr, bytes);
+}
+
+/* Recursively convert an objc native type JavaScript value to a Lisp
+   value.  Mostly copied from GTK xwidget 'webkit_js_to_lisp'.  */
+static Lisp_Object
+js_to_lisp (id value)
+{
+  if (value == nil || [value isKindOfClass:NSNull.class])
+    return Qnil;
+  else if ([value isKindOfClass:NSString.class])
+    return build_string_with_nsstr ((NSString *) value);
+  else if ([value isKindOfClass:NSNumber.class])
+    {
+      NSNumber *nsnum = (NSNumber *) value;
+      char type = nsnum.objCType[0];
+      if (type == 'c') /* __NSCFBoolean has type character 'c'.  */
+        return nsnum.boolValue? Qt : Qnil;
+      else
+        {
+          if (type == 'i' || type == 'l')
+            return make_number (nsnum.longValue);
+          else if (type == 'f' || type == 'd')
+            return make_float (nsnum.doubleValue);
+          /* else fall through.  */
+        }
+    }
+  else if ([value isKindOfClass:NSArray.class])
+    {
+      NSArray *nsarr = (NSArray *) value;
+      EMACS_INT n = nsarr.count;
+      Lisp_Object obj;
+      struct Lisp_Vector *p = allocate_vector (n);
+
+      for (ptrdiff_t i = 0; i < n; ++i)
+        p->contents[i] = js_to_lisp ([nsarr objectAtIndex:i]);
+      XSETVECTOR (obj, p);
+      return obj;
+    }
+  else if ([value isKindOfClass:NSDictionary.class])
+    {
+      NSDictionary *nsdict = (NSDictionary *) value;
+      NSArray *keys = nsdict.allKeys;
+      ptrdiff_t n = keys.count;
+      Lisp_Object obj;
+      struct Lisp_Vector *p = allocate_vector (n);
+
+      for (ptrdiff_t i = 0; i < n; ++i)
+        {
+          NSString *prop_key = (NSString *) [keys objectAtIndex:i];
+          id prop_value = [nsdict valueForKey:prop_key];
+          p->contents[i] = Fcons (build_string_with_nsstr (prop_key),
+                                  js_to_lisp (prop_value));
+        }
+      XSETVECTOR (obj, p);
+      return obj;
+    }
+  NSLog (@"Unhandled type in javascript result");
+  return Qnil;
+}
+
+void
+nsxwidget_webkit_execute_script (struct xwidget *xw, const char *script,
+                                 Lisp_Object fun)
+{
+  NSString *javascriptString = [NSString stringWithUTF8String:script];
+  XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
+
+  [xwWebView evaluateJavaScript:javascriptString
+              completionHandler:^(id result, NSError *error) {
+      if (error)
+        {
+          NSLog (@"evaluateJavaScript error : %@", error.localizedDescription);
+          NSLog (@"error script=%@", javascriptString);
+        }
+      else if (result && FUNCTIONP (fun))
+        {
+          // NSLog (@"result=%@, type=%@", result, [result class]);
+          Lisp_Object lisp_value = js_to_lisp (result);
+          store_xwidget_js_callback_event (xw, fun, lisp_value);
+        }
+    }];
+}
+
+/* Window containing an xwidget.  */
+
+@implementation XwWindow
+- (BOOL)isFlipped { return YES; }
+@end
+
+/* Xwidget model, macOS Cocoa part.  */
+
+void
+nsxwidget_init(struct xwidget *xw)
+{
+  block_input ();
+  NSRect rect = NSMakeRect (0, 0, xw->width, xw->height);
+  xw->xwWidget = [[XwWebView alloc]
+                   initWithFrame:rect
+                   configuration:[[WKWebViewConfiguration alloc] init]
+                         xwidget:xw];
+  xw->xwWindow = [[XwWindow alloc]
+                   initWithFrame:rect];
+  [xw->xwWindow addSubview:xw->xwWidget];
+  xw->xv = NULL; /* for 1 to 1 relationship of webkit2.  */
+  unblock_input ();
+}
+
+void
+nsxwidget_kill (struct xwidget *xw)
+{
+  if (xw)
+    {
+      WKUserContentController *scriptor =
+        ((XwWebView *) xw->xwWidget).configuration.userContentController;
+      [scriptor removeAllUserScripts];
+      [scriptor removeScriptMessageHandlerForName:@"keyDown"];
+      [scriptor release];
+      if (xw->xv)
+        xw->xv->model = Qnil; /* Make sure related view stale.  */
+      [xw->xwWidget removeFromSuperviewWithoutNeedingDisplay];
+      [xw->xwWidget release];
+      [xw->xwWindow removeFromSuperviewWithoutNeedingDisplay];
+      [xw->xwWindow release];
+      xw->xwWidget = nil;
+    }
+}
+
+void
+nsxwidget_resize (struct xwidget *xw)
+{
+  if (xw->xwWidget)
+    {
+      [xw->xwWindow setFrameSize:NSMakeSize(xw->width, xw->height)];
+      [xw->xwWidget setFrameSize:NSMakeSize(xw->width, xw->height)];
+    }
+}
+
+Lisp_Object
+nsxwidget_get_size (struct xwidget *xw)
+{
+  return list2 (make_number (xw->xwWidget.frame.size.width),
+                make_number (xw->xwWidget.frame.size.height));
+}
+
+/* Xwidget view, macOS Cocoa part.  */
+
+@implementation XvWindow : NSView
+- (BOOL)isFlipped { return YES; }
+@end
+
+void
+nsxwidget_init_view (struct xwidget_view *xv,
+                     struct xwidget *xw,
+                     struct glyph_string *s,
+                     int x, int y)
+{
+  /* 'x_draw_xwidget_glyph_string' will calculate correct position and
+     size of clip to draw in emacs buffer window.  Thus, just begin at
+     origin with no crop.  */
+  xv->x = x;
+  xv->y = y;
+  xv->clip_left = 0;
+  xv->clip_right = xw->width;
+  xv->clip_top = 0;
+  xv->clip_bottom = xw->height;
+
+  xv->xvWindow = [[XvWindow alloc]
+                   initWithFrame:NSMakeRect (x, y, xw->width, xw->height)];
+  xv->xvWindow.xw = xw;
+  xv->xvWindow.xv = xv;
+
+  xw->xv = xv; /* For 1 to 1 relationship of webkit2.  */
+  [xv->xvWindow addSubview:xw->xwWindow];
+
+  xv->emacswindow = FRAME_NS_VIEW (s->f);
+  [xv->emacswindow addSubview:xv->xvWindow];
+}
+
+void
+nsxwidget_delete_view (struct xwidget_view *xv)
+{
+  if (!EQ (xv->model, Qnil))
+    {
+      struct xwidget *xw = XXWIDGET (xv->model);
+      [xw->xwWindow removeFromSuperviewWithoutNeedingDisplay];
+      xw->xv = NULL; /* Now model has no view.  */
+    }
+  [xv->xvWindow removeFromSuperviewWithoutNeedingDisplay];
+  [xv->xvWindow release];
+}
+
+void
+nsxwidget_show_view (struct xwidget_view *xv)
+{
+  xv->hidden = NO;
+  [xv->xvWindow setFrameOrigin:NSMakePoint(xv->x + xv->clip_left,
+                                           xv->y + xv->clip_top)];
+}
+
+void
+nsxwidget_hide_view (struct xwidget_view *xv)
+{
+  xv->hidden = YES;
+  [xv->xvWindow setFrameOrigin:NSMakePoint(10000, 10000)];
+}
+
+void
+nsxwidget_resize_view (struct xwidget_view *xv, int width, int height)
+{
+  [xv->xvWindow setFrameSize:NSMakeSize(width, height)];
+}
+
+void
+nsxwidget_move_view (struct xwidget_view *xv, int x, int y)
+{
+  [xv->xvWindow setFrameOrigin:NSMakePoint (x, y)];
+}
+
+/* Move model window in container (view window).  */
+void
+nsxwidget_move_widget_in_view (struct xwidget_view *xv, int x, int y)
+{
+  struct xwidget *xww = xv->xvWindow.xw;
+  [xww->xwWindow setFrameOrigin:NSMakePoint (x, y)];
+}
+
+void
+nsxwidget_set_needsdisplay (struct xwidget_view *xv)
+{
+  xv->xvWindow.needsDisplay = YES;
+}
diff --git a/src/xwidget.c b/src/xwidget.c
index a67dc0ecf4..17e1e6428a 100644
--- a/src/xwidget.c
+++ b/src/xwidget.c
@@ -18,17 +18,26 @@ You should have received a copy of the GNU General Public License
 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
 #include <config.h>
+#include <stdio.h> /* FIXME: Emacs error? message? instead of printf.  */
 
 #include "xwidget.h"
 
 #include "lisp.h"
 #include "blockinput.h"
+#include "dispextern.h"
 #include "frame.h"
 #include "keyboard.h"
 #include "gtkutil.h"
+#include "termhooks.h"
+#include "window.h"
 
+/* Include xwidget bottom end headers.  */
+#if defined (USE_GTK)
 #include <webkit2/webkit2.h>
 #include <JavaScriptCore/JavaScript.h>
+#elif defined (NS_IMPL_COCOA)
+#include "nsxwidget.h"
+#endif
 
 static struct xwidget *
 allocate_xwidget (void)
@@ -48,6 +57,7 @@ allocate_xwidget_view (void)
 
 static struct xwidget_view *xwidget_view_lookup (struct xwidget *,
 						 struct window *);
+#if defined (USE_GTK)
 static void webkit_view_load_changed_cb (WebKitWebView *,
                                          WebKitLoadEvent,
                                          gpointer);
@@ -61,6 +71,7 @@ webkit_decide_policy_cb (WebKitWebView *,
                          WebKitPolicyDecision *,
                          WebKitPolicyDecisionType,
                          gpointer);
+#endif
 
 
 DEFUN ("make-xwidget",
@@ -92,10 +103,11 @@ Returns the newly constructed xwidget, or nil if construction fails.  */)
   xw->kill_without_query = false;
   XSETXWIDGET (val, xw);
   Vxwidget_list = Fcons (val, Vxwidget_list);
-  xw->widgetwindow_osr = NULL;
-  xw->widget_osr = NULL;
   xw->plist = Qnil;
 
+#if defined (USE_GTK)
+  xw->widgetwindow_osr = NULL;
+  xw->widget_osr = NULL;
   if (EQ (xw->type, Qwebkit))
     {
       block_input ();
@@ -150,6 +162,9 @@ Returns the newly constructed xwidget, or nil if construction fails.  */)
 
       unblock_input ();
     }
+#elif defined (NS_IMPL_COCOA)
+  nsxwidget_init (xw);
+#endif
 
   return val;
 }
@@ -185,6 +200,7 @@ xwidget_hidden (struct xwidget_view *xv)
   return xv->hidden;
 }
 
+#if defined (USE_GTK)
 static void
 xwidget_show_view (struct xwidget_view *xv)
 {
@@ -223,8 +239,9 @@ offscreen_damage_event (GtkWidget *widget, GdkEvent *event,
 
   return FALSE;
 }
+#endif /* USE_GTK */
 
-static void
+void
 store_xwidget_event_string (struct xwidget *xw, const char *eventname,
                             const char *eventstr)
 {
@@ -238,7 +255,7 @@ store_xwidget_event_string (struct xwidget *xw, const char *eventname,
   kbd_buffer_store_event (&event);
 }
 
-static void
+void
 store_xwidget_js_callback_event (struct xwidget *xw,
                                  Lisp_Object proc,
                                  Lisp_Object argument)
@@ -254,6 +271,7 @@ store_xwidget_js_callback_event (struct xwidget *xw,
 }
 
 
+#if defined (USE_GTK)
 void
 webkit_view_load_changed_cb (WebKitWebView *webkitwebview,
                              WebKitLoadEvent load_event,
@@ -389,9 +407,8 @@ webkit_javascript_finished_cb (GObject      *webview,
     /* Register an xwidget event here, which then runs the callback.
        This ensures that the callback runs in sync with the Emacs
        event loop.  */
-    /* FIXME: This might lead to disaster if LISP_CALLBACK’s object
-       was garbage collected before now.  See the FIXME in
-       Fxwidget_webkit_execute_script.  */
+    /* LISP_CALLBACK must not be garbage collected up to here.  See
+       comments in Fxwidget_webkit_execute_script.  */
     store_xwidget_js_callback_event (xw, XIL ((intptr_t) lisp_callback),
                                      lisp_value);
 }
@@ -500,6 +517,7 @@ xwidget_osr_event_set_embedder (GtkWidget *widget, GdkEvent *event,
                                      gtk_widget_get_window (xv->widget));
   return FALSE;
 }
+#endif /* USE_GTK */
 
 
 /* Initializes and does initial placement of an xwidget view on screen.  */
@@ -517,6 +535,7 @@ xwidget_init_view (struct xwidget *xww,
   XSETWINDOW (xv->w, s->w);
   XSETXWIDGET (xv->model, xww);
 
+#if defined (USE_GTK)
   if (EQ (xww->type, Qwebkit))
     {
       xv->widget = gtk_drawing_area_new ();
@@ -574,6 +593,9 @@ xwidget_init_view (struct xwidget *xww,
   xv->x = x;
   xv->y = y;
   gtk_widget_show_all (xv->widgetwindow);
+#elif defined (NS_IMPL_COCOA)
+  nsxwidget_init_view (xv, xww, s, x, y);
+#endif
 
   return xv;
 }
@@ -586,24 +608,59 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
      initialization.  */
   struct xwidget *xww = s->xwidget;
   struct xwidget_view *xv = xwidget_view_lookup (xww, s->w);
+  int text_area_x, text_area_y, text_area_width, text_area_height;
   int clip_right;
   int clip_bottom;
   int clip_top;
   int clip_left;
 
   int x = s->x;
-  int y = s->y + (s->height / 2) - (xww->height / 2);
+  int y = s->y;
 
   /* Do initialization here in the display loop because there is no
      other time to know things like window placement etc.  Do not
      create a new view if we have found one that is usable.  */
+#if defined (USE_GTK)
   if (!xv)
     xv = xwidget_init_view (xww, s, x, y);
-
-  int text_area_x, text_area_y, text_area_width, text_area_height;
+#elif defined (NS_IMPL_COCOA)
+  if (!xv)
+    {
+      /* Enforce 1 to 1, model and view for macOS Cocoa webkit2.  */
+      if (xww->xv)
+        {
+          if (xwidget_hidden (xww->xv))
+            {
+              Lisp_Object xvl;
+              XSETXWIDGET_VIEW (xvl, xww->xv);
+              Fdelete_xwidget_view (xvl);
+            }
+          else
+            {
+              message ("You can't share an xwidget (webkit2) among windows.");
+              return;
+            }
+        }
+      xv = xwidget_init_view (xww, s, x, y);
+    }
+#endif
 
   window_box (s->w, TEXT_AREA, &text_area_x, &text_area_y,
               &text_area_width, &text_area_height);
+
+  /* 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.  */
+  if (EQ (xww->type, Qwebkit)
+      && (xww->width != text_area_width || xww->height != text_area_height))
+    {
+      Lisp_Object xwl;
+      XSETXWIDGET (xwl, xww);
+      Fxwidget_resize (xwl,
+                       make_number (text_area_width),
+                       make_number (text_area_height));
+    }
+
   clip_left = max (0, text_area_x - x);
   clip_right = max (clip_left,
 		    min (xww->width, text_area_x + text_area_width - x));
@@ -626,8 +683,14 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
 
   /* Has it moved?  */
   if (moved)
-    gtk_fixed_move (GTK_FIXED (FRAME_GTK_WIDGET (s->f)),
-		    xv->widgetwindow, x + clip_left, y + clip_top);
+    {
+#if defined (USE_GTK)
+      gtk_fixed_move (GTK_FIXED (FRAME_GTK_WIDGET (s->f)),
+                      xv->widgetwindow, x + clip_left, y + clip_top);
+#elif defined (NS_IMPL_COCOA)
+      nsxwidget_move_view (xv, x + clip_left, y + clip_top);
+#endif
+    }
 
   /* Clip the widget window if some parts happen to be outside
      drawable area.  An Emacs window is not a gtk window.  A gtk window
@@ -638,10 +701,16 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
       || xv->clip_bottom != clip_bottom
       || xv->clip_top != clip_top || xv->clip_left != clip_left)
     {
+#if defined (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);
+#elif defined (NS_IMPL_COCOA)
+      nsxwidget_resize_view (xv, clip_right - clip_left,
+                             clip_bottom - clip_top);
+      nsxwidget_move_widget_in_view (xv, -clip_left, -clip_top);
+#endif
 
       xv->clip_right = clip_right;
       xv->clip_bottom = clip_bottom;
@@ -655,18 +724,32 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
      xwidgets background.  It's just a visual glitch though.  */
   if (!xwidget_hidden (xv))
     {
+#if defined (USE_GTK)
       gtk_widget_queue_draw (xv->widgetwindow);
       gtk_widget_queue_draw (xv->widget);
+#elif defined (NS_IMPL_COCOA)
+      nsxwidget_set_needsdisplay (xv);
+#endif
     }
 }
 
-/* Macro that checks WEBKIT_IS_WEB_VIEW (xw->widget_osr) first.  */
+static bool
+xwidget_is_web_view (struct xwidget *xw)
+{
+#if defined (USE_GTK)
+  return xw->widget_osr != NULL && WEBKIT_IS_WEB_VIEW (xw->widget_osr);
+#elif defined (NS_IMPL_COCOA)
+  return nsxwidget_is_web_view (xw);
+#endif
+}
+
+/* Macro that checks xwidget hold webkit web view first.  */
 #define WEBKIT_FN_INIT()						\
   CHECK_XWIDGET (xwidget);						\
   struct xwidget *xw = XXWIDGET (xwidget);				\
-  if (!xw->widget_osr || !WEBKIT_IS_WEB_VIEW (xw->widget_osr))		\
+  if (!xwidget_is_web_view (xw))					\
     {									\
-      printf ("ERROR xw->widget_osr does not hold a webkit instance\n"); \
+      printf ("ERROR xwidget does not hold a webkit instance\n");	\
       return Qnil;							\
     }
 
@@ -678,7 +761,11 @@ DEFUN ("xwidget-webkit-goto-uri",
 {
   WEBKIT_FN_INIT ();
   CHECK_STRING (uri);
+#if defined (USE_GTK)
   webkit_web_view_load_uri (WEBKIT_WEB_VIEW (xw->widget_osr), SSDATA (uri));
+#elif defined (NS_IMPL_COCOA)
+  nsxwidget_webkit_goto_uri (xw, SSDATA (uri));
+#endif
   return Qnil;
 }
 
@@ -693,10 +780,14 @@ referenced by XWIDGET.  */)
   if (FLOATP (factor))
     {
       double zoom_change = XFLOAT_DATA (factor);
+#if defined (USE_GTK)
       webkit_web_view_set_zoom_level
         (WEBKIT_WEB_VIEW (xw->widget_osr),
          webkit_web_view_get_zoom_level
          (WEBKIT_WEB_VIEW (xw->widget_osr)) + zoom_change);
+#elif defined (NS_IMPL_COCOA)
+      nsxwidget_webkit_zoom (xw, zoom_change);
+#endif
     }
   return Qnil;
 }
@@ -712,17 +803,18 @@ argument procedure FUN.*/)
 {
   WEBKIT_FN_INIT ();
   CHECK_STRING (script);
-  if (!NILP (fun) && !FUNCTIONP (fun))
+  /* FUN will not be garbage collected if it is defined with `defun'
+     instead of `lambda'.  If it is garbage collected even though it
+     is `defun', we can counter by pinning the FUN's symbol.  */
+  if (!NILP (fun) && !SYMBOLP (fun) && !NILP (Ffboundp (fun)))
     wrong_type_argument (Qinvalid_function, fun);
 
+#if defined (USE_GTK)
   GAsyncReadyCallback callback
     = FUNCTIONP (fun) ? webkit_javascript_finished_cb : NULL;
 
   /* FIXME: The following hack assumes USE_LSB_TAG.  */
   verify (USE_LSB_TAG);
-  /* FIXME: This hack might lead to disaster if FUN is garbage
-     collected before store_xwidget_js_callback_event makes it visible
-     to Lisp again.  See the FIXME in webkit_javascript_finished_cb.  */
   gpointer callback_arg = (gpointer) (intptr_t) XLI (fun);
 
   /* JavaScript execution happens asynchronously.  If an elisp
@@ -732,6 +824,9 @@ argument procedure FUN.*/)
                                   SSDATA (script),
                                   NULL, /* cancelable */
                                   callback, callback_arg);
+#elif defined (NS_IMPL_COCOA)
+  nsxwidget_webkit_execute_script (xw, SSDATA (script), fun);
+#endif
   return Qnil;
 }
 
@@ -750,6 +845,7 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
   xw->height = h;
 
   /* If there is an offscreen widget resize it first.  */
+#if defined (USE_GTK)
   if (xw->widget_osr)
     {
       gtk_window_resize (GTK_WINDOW (xw->widgetwindow_osr), xw->width,
@@ -758,6 +854,9 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
       gtk_widget_set_size_request (GTK_WIDGET (xw->widget_osr), xw->width,
                                    xw->height);
     }
+#elif defined (NS_IMPL_COCOA)
+  nsxwidget_resize (xw);
+#endif
 
   for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail); tail = XCDR (tail))
     {
@@ -765,8 +864,14 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
         {
           struct xwidget_view *xv = XXWIDGET_VIEW (XCAR (tail));
           if (XXWIDGET (xv->model) == xw)
+            {
+#if defined (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
+            }
         }
     }
 
@@ -785,10 +890,14 @@ Emacs allocated area accordingly.  */)
   (Lisp_Object xwidget)
 {
   CHECK_XWIDGET (xwidget);
+#if defined (USE_GTK)
   GtkRequisition requisition;
   gtk_widget_size_request (XXWIDGET (xwidget)->widget_osr, &requisition);
   return list2 (make_number (requisition.width),
 		make_number (requisition.height));
+#elif defined (NS_IMPL_COCOA)
+  return nsxwidget_get_size(XXWIDGET (xwidget));
+#endif
 }
 
 DEFUN ("xwidgetp",
@@ -865,14 +974,19 @@ DEFUN ("delete-xwidget-view",
 {
   CHECK_XWIDGET_VIEW (xwidget_view);
   struct xwidget_view *xv = XXWIDGET_VIEW (xwidget_view);
-  gtk_widget_destroy (xv->widgetwindow);
   Vxwidget_view_list = Fdelq (xwidget_view, Vxwidget_view_list);
+#if defined (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);
+#elif defined (NS_IMPL_COCOA)
+  nsxwidget_delete_view (xv);
+#endif
+
   return Qnil;
 }
 
@@ -1151,11 +1265,19 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
 		     xwidget_end_redisplay (w->current_matrix);  */
 		  struct xwidget_view *xv
 		    = xwidget_view_lookup (glyph->u.xwidget, w);
+#if defined (USE_GTK)
 		  /* FIXME: Is it safe to assume xwidget_view_lookup
 		     always succeeds here?  If so, this comment can be removed.
 		     If not, the code probably needs fixing.  */
 		  eassume (xv);
 		  xwidget_touch (xv);
+#elif defined (NS_IMPL_COCOA)
+                  /* In NS xwidget, xv can be NULL for the second or
+                     later views for a model, the result of 1 to 1
+                     model view relation enforcement.  */
+                  if (xv)
+                    xwidget_touch (xv);
+#endif
 		}
 	  }
     }
@@ -1172,9 +1294,21 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
           if (XWINDOW (xv->w) == w)
             {
               if (xwidget_touched (xv))
-                xwidget_show_view (xv);
+                {
+#if defined (USE_GTK)
+                  xwidget_show_view (xv);
+#elif defined (NS_IMPL_COCOA)
+                  nsxwidget_show_view (xv);
+#endif
+                }
               else
-                xwidget_hide_view (xv);
+                {
+#if defined (USE_GTK)
+                  xwidget_hide_view (xv);
+#elif defined (NS_IMPL_COCOA)
+                  nsxwidget_hide_view (xv);
+#endif
+                }
             }
         }
     }
@@ -1193,11 +1327,15 @@ kill_buffer_xwidgets (Lisp_Object buffer)
       {
         CHECK_XWIDGET (xwidget);
         struct xwidget *xw = XXWIDGET (xwidget);
+#if defined (USE_GTK)
         if (xw->widget_osr && xw->widgetwindow_osr)
           {
             gtk_widget_destroy (xw->widget_osr);
             gtk_widget_destroy (xw->widgetwindow_osr);
           }
+#elif defined (NS_IMPL_COCOA)
+        nsxwidget_kill (xw);
+#endif
       }
     }
 }
diff --git a/src/xwidget.h b/src/xwidget.h
index 02a0453dab..eee9654a6f 100644
--- a/src/xwidget.h
+++ b/src/xwidget.h
@@ -29,7 +29,13 @@ struct xwidget_view;
 struct window;
 
 #ifdef HAVE_XWIDGETS
-# include <gtk/gtk.h>
+
+#if defined (USE_GTK)
+#include <gtk/gtk.h>
+#elif defined (NS_IMPL_COCOA) && defined (__OBJC__)
+#import <AppKit/NSView.h>
+#import "nsxwidget.h"
+#endif
 
 struct xwidget
 {
@@ -52,9 +58,25 @@ struct xwidget
   int height;
   int width;
 
+#if defined (USE_GTK)
   /* For offscreen widgets, unused if not osr.  */
   GtkWidget *widget_osr;
   GtkWidget *widgetwindow_osr;
+#elif defined (NS_IMPL_COCOA)
+# ifdef __OBJC__
+  /* For offscreen widgets, unused if not osr.  */
+  NSView *xwWidget;
+  XwWindow *xwWindow;
+
+  /* Used only for xwidget types (such as webkit2) enforcing 1 to 1
+     relationship between model and view.  */
+  struct xwidget_view *xv;
+# else
+  void *xwWidget;
+  void *xwWindow;
+  struct xwidget_view *xv;
+# endif
+#endif
 
   /* Kill silently if Emacs is exited.  */
   bool_bf kill_without_query : 1;
@@ -74,9 +96,20 @@ struct xwidget_view
   /* The "live" instance isn't drawn.  */
   bool hidden;
 
+#if defined (USE_GTK)
   GtkWidget *widget;
   GtkWidget *widgetwindow;
   GtkWidget *emacswindow;
+#elif defined (NS_IMPL_COCOA)
+# ifdef __OBJC__
+  XvWindow *xvWindow;
+  NSView *emacswindow;
+# else
+  void *xvWindow;
+  void *emacswindow;
+# endif
+#endif
+
   int x;
   int y;
   int clip_right;
-- 
2.15.0

