unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* [PATCH v4] Enable xwidgets on macOS
@ 2019-07-18 19:23 Sungbin Jo
  2019-07-19  4:16 ` [PATCH v5] " Sungbin Jo
  0 siblings, 1 reply; 49+ messages in thread
From: Sungbin Jo @ 2019-07-18 19:23 UTC (permalink / raw)
  To: emacs-devel; +Cc: schwab, alan, eggert, monnier, Sungbin Jo

---
 configure.ac                     |  34 +-
 lisp/xwidget.el                  | 326 +++++++++++++----
 nextstep/templates/Info.plist.in |  12 +-
 src/Makefile.in                  |   1 +
 src/emacs.c                      |   2 +-
 src/nsterm.m                     |  22 +-
 src/nsxwidget.h                  |  80 ++++
 src/nsxwidget.m                  | 611 +++++++++++++++++++++++++++++++
 src/xwidget.c                    | 258 ++++++++++++-
 src/xwidget.h                    |  50 ++-
 10 files changed, 1292 insertions(+), 104 deletions(-)
 create mode 100644 src/nsxwidget.h
 create mode 100644 src/nsxwidget.m

diff --git a/configure.ac b/configure.ac
index c093d8650d..9685b65862 100644
--- a/configure.ac
+++ b/configure.ac
@@ -484,7 +484,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 xwidgets 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.
@@ -2808,20 +2808,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="-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 system.])
+  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)
@@ -5697,7 +5711,7 @@ 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}
   Does Emacs have threading support in lisp?              ${threads_enabled}
   Does Emacs support the portable dumper?                 ${with_pdumper}
   Does Emacs support legacy unexec dumping?               ${with_unexec}
diff --git a/lisp/xwidget.el b/lisp/xwidget.el
index 662a854ac3..9b146b0202 100644
--- a/lisp/xwidget.el
+++ b/lisp/xwidget.el
@@ -39,9 +39,10 @@
 (declare-function xwidget-buffer "xwidget.c" (xwidget))
 (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"
-                  (xwidget script &optional callback))
+(declare-function xwidget-webkit-uri "xwidget.c" (xwidget))
+(declare-function xwidget-webkit-title "xwidget.c" (xwidget))
 (declare-function xwidget-webkit-goto-uri "xwidget.c" (xwidget uri))
+(declare-function xwidget-webkit-goto-history "xwidget.c" (xwidget rel-pos))
 (declare-function xwidget-webkit-zoom "xwidget.c" (xwidget factor))
 (declare-function xwidget-plist "xwidget.c" (xwidget))
 (declare-function set-xwidget-plist "xwidget.c" (xwidget plist))
@@ -51,6 +52,10 @@
 (declare-function get-buffer-xwidgets "xwidget.c" (buffer))
 (declare-function xwidget-query-on-exit-flag "xwidget.c" (xwidget))
 
+(defgroup xwidget nil
+  "Displaying native widgets in Emacs buffers."
+  :group 'widgets)
+
 (defun xwidget-insert (pos type title width height &optional args)
   "Insert an xwidget at position POS.
 Supply the xwidget's TYPE, TITLE, WIDTH, and HEIGHT.
@@ -78,6 +83,8 @@ xwidget-at
 ;;; webkit support
 (require 'browse-url)
 (require 'image-mode);;for some image-mode alike functionality
+(require 'seq)
+(require 'url-handlers)
 
 ;;;###autoload
 (defun xwidget-webkit-browse-url (url &optional new-session)
@@ -96,6 +103,24 @@ xwidget-webkit-browse-url
         (xwidget-webkit-new-session url)
       (xwidget-webkit-goto-url url))))
 
+(defun xwidget-webkit-split-below ()
+  "Clone current URL into a new widget place in new window below.
+Get the URL of current session, then browse to the URL
+in `split-window-below' with a new xwidget webkit session."
+  (interactive)
+  (let ((url (xwidget-webkit-current-url)))
+    (with-selected-window (split-window-below)
+      (xwidget-webkit-new-session url))))
+
+(defun xwidget-webkit-split-right ()
+  "Clone current URL into a new widget place in new window right.
+Get the URL of current session, then browse to the URL
+in `split-window-right' with a new xwidget webkit session."
+  (interactive)
+  (let ((url (xwidget-webkit-current-url)))
+    (with-selected-window (split-window-right)
+      (xwidget-webkit-new-session url))))
+
 ;;todo.
 ;; - check that the webkit support is compiled in
 (defvar xwidget-webkit-mode-map
@@ -103,34 +128,42 @@ xwidget-webkit-mode-map
     (define-key map "g" 'xwidget-webkit-browse-url)
     (define-key map "a" 'xwidget-webkit-adjust-size-dispatch)
     (define-key map "b" 'xwidget-webkit-back)
+    (define-key map "f" 'xwidget-webkit-forward)
     (define-key map "r" 'xwidget-webkit-reload)
     (define-key map "t" (lambda () (interactive) (message "o"))) ;FIXME: ?!?
     (define-key map "\C-m" 'xwidget-webkit-insert-string)
-    (define-key map "w" 'xwidget-webkit-current-url)
+    (define-key map "w" 'xwidget-webkit-current-url-message-kill)
     (define-key map "+" 'xwidget-webkit-zoom-in)
     (define-key map "-" 'xwidget-webkit-zoom-out)
 
     ;;similar to image mode bindings
     (define-key map (kbd "SPC")                 'xwidget-webkit-scroll-up)
+    (define-key map (kbd "S-SPC")               'xwidget-webkit-scroll-down)
     (define-key map (kbd "DEL")                 'xwidget-webkit-scroll-down)
 
-    (define-key map [remap scroll-up]           'xwidget-webkit-scroll-up)
+    (define-key map [remap scroll-up]           'xwidget-webkit-scroll-up-line)
     (define-key map [remap scroll-up-command]   'xwidget-webkit-scroll-up)
 
-    (define-key map [remap scroll-down]         'xwidget-webkit-scroll-down)
+    (define-key map [remap scroll-down]         'xwidget-webkit-scroll-down-line)
     (define-key map [remap scroll-down-command] 'xwidget-webkit-scroll-down)
 
     (define-key map [remap forward-char]        'xwidget-webkit-scroll-forward)
     (define-key map [remap backward-char]       'xwidget-webkit-scroll-backward)
     (define-key map [remap right-char]          'xwidget-webkit-scroll-forward)
     (define-key map [remap left-char]           'xwidget-webkit-scroll-backward)
-    (define-key map [remap previous-line]       'xwidget-webkit-scroll-down)
-    (define-key map [remap next-line]           'xwidget-webkit-scroll-up)
+    (define-key map [remap previous-line]       'xwidget-webkit-scroll-down-line)
+    (define-key map [remap next-line]           'xwidget-webkit-scroll-up-line)
 
     ;; (define-key map [remap move-beginning-of-line] 'image-bol)
     ;; (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 [remap split-window-below] 'xwidget-webkit-split-below)
+      (define-key map [remap split-window-right] 'xwidget-webkit-split-right))
     map)
   "Keymap for `xwidget-webkit-mode'.")
 
@@ -144,33 +177,69 @@ xwidget-webkit-zoom-out
   (interactive)
   (xwidget-webkit-zoom (xwidget-webkit-current-session) -0.1))
 
-(defun xwidget-webkit-scroll-up ()
-  "Scroll webkit up."
-  (interactive)
+(defun xwidget-webkit-scroll-up (&optional n)
+  "Scroll webkit up by N pixels or window height pixels.
+Stop if the bottom edge of the page is reached.
+If N is omitted or nil, scroll up by window height pixels."
+  (interactive "P")
   (xwidget-webkit-execute-script
    (xwidget-webkit-current-session)
-   "window.scrollBy(0, 50);"))
-
-(defun xwidget-webkit-scroll-down ()
-  "Scroll webkit down."
-  (interactive)
+   (format "window.scrollBy(0, %d);"
+           (or n (xwidget-window-inside-pixel-height (selected-window))))))
+
+(defun xwidget-webkit-scroll-down (&optional n)
+  "Scroll webkit down by N pixels or window height pixels.
+Stop if the top edge of the page is reached.
+If N is omitted or nil, scroll down by window height pixels."
+  (interactive "P")
   (xwidget-webkit-execute-script
    (xwidget-webkit-current-session)
-   "window.scrollBy(0, -50);"))
-
-(defun xwidget-webkit-scroll-forward ()
-  "Scroll webkit forwards."
-  (interactive)
+   (format "window.scrollBy(0, -%d);"
+           (or n (xwidget-window-inside-pixel-height (selected-window))))))
+
+(defcustom xwidget-webkit-scroll-line-height 50
+  "Default line height in pixels to scroll xwidget webkit."
+  :type 'integer)
+
+(defcustom xwidget-webkit-scroll-char-width 10
+  "Default char height in pixels to scroll xwidget webkit."
+  :type 'integer)
+
+(defun xwidget-webkit-scroll-up-line (&optional n)
+  "Scroll webkit up by N lines.
+The height of line is `xwidget-webkit-scroll-line-height' pixels.
+Stop if the bottom edge of the page is reached.
+If N is omitted or nil, scroll up by one line."
+  (interactive "p")
+  (xwidget-webkit-scroll-up (* n xwidget-webkit-scroll-line-height)))
+
+(defun xwidget-webkit-scroll-down-line (&optional n)
+  "Scroll webkit down by N lines.
+The height of line is `xwidget-webkit-scroll-line-height' pixels.
+Stop if the top edge of the page is reached.
+If N is omitted or nil, scroll down by one line."
+  (interactive "p")
+  (xwidget-webkit-scroll-down (* n xwidget-webkit-scroll-line-height)))
+
+(defun xwidget-webkit-scroll-forward (&optional n)
+  "Scroll webkit forwards by N chars.
+The width of char is `xwidget-webkit-scroll-char-width' pixels.
+If N is ommited or nil, scroll forwards by one char."
+  (interactive "p")
   (xwidget-webkit-execute-script
    (xwidget-webkit-current-session)
-   "window.scrollBy(50, 0);"))
-
-(defun xwidget-webkit-scroll-backward ()
-  "Scroll webkit backwards."
-  (interactive)
+   (format "window.scrollBy(%d, 0);"
+           (* n xwidget-webkit-scroll-char-width))))
+
+(defun xwidget-webkit-scroll-backward (&optional n)
+  "Scroll webkit backwards by N chars.
+The width of char is `xwidget-webkit-scroll-char-width' pixels.
+If N is ommited or nil, scroll backwards by one char."
+  (interactive "p")
   (xwidget-webkit-execute-script
    (xwidget-webkit-current-session)
-   "window.scrollBy(-50, 0);"))
+   (format "window.scrollBy(-%d, 0);"
+           (* n xwidget-webkit-scroll-char-width))))
 
 (defun xwidget-webkit-scroll-top ()
   "Scroll webkit to the very top."
@@ -184,7 +253,7 @@ xwidget-webkit-scroll-bottom
   (interactive)
   (xwidget-webkit-execute-script
    (xwidget-webkit-current-session)
-   "window.scrollTo(pageXOffset, window.document.body.clientHeight);"))
+   "window.scrollTo(pageXOffset, window.document.body.scrollHeight);"))
 
 ;; The xwidget event needs to go into a higher level handler
 ;; since the xwidget can generate an event even if it's offscreen.
@@ -208,7 +277,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)
@@ -219,43 +287,141 @@ xwidget-webkit-callback
        "error: callback called for xwidget with dead buffer")
     (with-current-buffer (xwidget-buffer xwidget)
       (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)))
+             ;; We do not change selected window for the finish of loading a page.
+             ;; And do not adjust webkit size to window here, the selected window
+             ;; can be the mini-buffer window unwantedly.
+             (let ((title (xwidget-webkit-title xwidget)))
+               (xwidget-log "webkit finished loading: %s" title)
+               (rename-buffer (format "*xwidget webkit: %s *" title) t)))
             ((eq xwidget-event-type 'decide-policy)
              (let ((strarg  (nth 3 last-input-event)))
                (if (string-match ".*#\\(.*\\)" strarg)
                    (xwidget-webkit-show-id-or-named-element
                     xwidget
                     (match-string 1 strarg)))))
+            ;; TODO: Response handling other than download.
+            ((eq xwidget-event-type 'download-callback)
+             (let ((url  (nth 3 last-input-event))
+                   (mime-type (nth 4 last-input-event))
+                   (file-name (nth 5 last-input-event)))
+               (xwidget-webkit-save-as-file url mime-type file-name)))
             ((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
+               (funcall proc (if (vectorp arg) (seq-into arg 'list) arg))))
             (t (xwidget-log "unhandled event:%s" xwidget-event-type))))))
 
 (defvar bookmark-make-record-function)
+(defvar isearch-search-fun-function)
+(when (memq window-system '(mac ns))
+  (defcustom xwidget-webkit-enable-plugins nil
+    "Enable plugins for xwidget webkit.
+If non-nil, plugins are enabled.  Otherwise, disabled."
+    :type 'boolean))
+
 (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))
 
+;;; Download, save as file.
+
+(defcustom xwidget-webkit-download-dir "~/Downloads/"
+  "Directory where download file saved."
+  :type 'string)
+
+(defun xwidget-webkit-save-as-file (url mime-type &optional file-name)
+  "For XWIDGET webkit, save URL resource of MIME-TYPE as FILE-NAME."
+  (let ((save-name (read-file-name
+                    (format "Save '%s' file as: " mime-type)
+                    xwidget-webkit-download-dir
+                    (expand-file-name
+                     file-name
+                     xwidget-webkit-download-dir))))
+    (if (file-directory-p save-name)
+        (setq save-name
+              (expand-file-name (file-name-nondirectory file-name) save-name)))
+    (setq xwidget-webkit-download-dir (file-name-directory save-name))
+    (url-copy-file url save-name t)))
+
+;;; Bookmarks integration
+
+(defcustom 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'."
+  :type 'boolean)
+
 (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)))))))
-
+         `((page . ,(xwidget-webkit-current-url))
+           (handler  . (lambda (bmk)
+                         (xwidget-webkit-browse-url
+                          (bookmark-prop-get bmk 'page)
+                          xwidget-webkit-bookmark-jump-new-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)
+    (let* ((current-length (length string))
+           (search-forward (if isearch-forward "true" "false"))
+           (search-repeat
+            (if (eq current-length xwidget-webkit-isearch-last-length)
+                "true"
+              "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 ...'
+      (goto-char (if isearch-forward (point-min) (point-max))))))
+
+;;; xwidget webkit session
 
 (defvar xwidget-webkit-last-session-buffer nil)
 
@@ -303,7 +469,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,25 +479,29 @@ xwidget-webkit-activeelement-js"
   )
 
 (defun xwidget-webkit-insert-string ()
-  "Prompt for a string and insert it in the active field in the
-current webkit widget."
+  "Insert string into the active field in the current webkit widget."
   ;; Read out the string in the field first and provide for edit.
   (interactive)
+  ;; As the prompt needs to change based on the asynchronous execution results,
+  ;; the function must handle the string itself.
   (let ((xww (xwidget-webkit-current-session)))
+
     (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)
+       "Prompt a string for the FIELD and insert in the active input."
        (let ((str (pcase field
-                    (`[,val "text"]
+                    (`(,val "text")
                      (read-string "Text: " val))
-                    (`[,val "password"]
+                    (`(,val "password")
                      (read-passwd "Password: " nil val))
-                    (`[,val "textarea"]
+                    (`(,val "textarea")
                      (xwidget-webkit-begin-edit-textarea xww val)))))
          (xwidget-webkit-execute-script
           xww
@@ -444,11 +614,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 +669,13 @@ 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 " ")
-    (setq xw (xwidget-insert 1 'webkit bufname
-                             (window-pixel-width)
-                             (window-pixel-height)))
+    ;; Insert invisible url, good default for next `g' to browse url.
+    (let ((start (point)))
+      (insert url)
+      (put-text-property start (+ start (length url)) 'invisible t)
+      (setq xw (xwidget-insert start 'webkit bufname
+                               (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)))
@@ -506,23 +691,27 @@ xwidget-webkit-goto-url
 (defun xwidget-webkit-back ()
   "Go back in history."
   (interactive)
-  (xwidget-webkit-execute-script (xwidget-webkit-current-session)
-                                 "history.go(-1);"))
+  (xwidget-webkit-goto-history (xwidget-webkit-current-session) -1))
+
+(defun xwidget-webkit-forward ()
+  "Go forward in history."
+  (interactive)
+  (xwidget-webkit-goto-history (xwidget-webkit-current-session) 1))
 
 (defun xwidget-webkit-reload ()
-  "Reload current url."
+  "Reload current URL."
   (interactive)
-  (xwidget-webkit-execute-script (xwidget-webkit-current-session)
-                                 "history.go(0);"))
+  (xwidget-webkit-goto-history (xwidget-webkit-current-session) 0))
 
 (defun xwidget-webkit-current-url ()
-  "Get the webkit url and place it on the kill-ring."
+  "Get the current xwidget webkit URL."
   (interactive)
-  (xwidget-webkit-execute-script
-   (xwidget-webkit-current-session)
-   "document.URL" (lambda (rv)
-                    (let ((url (kill-new (or rv ""))))
-                      (message "url: %s" url)))))
+  (xwidget-webkit-uri (xwidget-webkit-current-session)))
+
+(defun xwidget-webkit-current-url-message-kill ()
+  "Display the current xwidget webkit URL and place it on the `kill-ring'."
+  (interactive)
+  (message "URL: %s" (kill-new (or (xwidget-webkit-current-url) ""))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 (defun xwidget-webkit-get-selection (proc)
@@ -533,10 +722,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 c1e50a8409..76efe95673 100644
--- a/nextstep/templates/Info.plist.in
+++ b/nextstep/templates/Info.plist.in
@@ -675,7 +675,15 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
 	</array>
 	<key>NSAppleScriptEnabled</key>
 	<string>YES</string>
-        <key>NSAppleEventsUsageDescription</key>
-        <string>Emacs requires permission to send AppleEvents to other applications.</string>
+	<key>NSAppleEventsUsageDescription</key>
+	<string>Emacs requires permission to send AppleEvents to other applications.</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 fd05a45df5..0af650244d 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -434,6 +434,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 w32cygwinx.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 ad661a081b..7c6871f1e7 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -1902,7 +1902,6 @@ main (int argc, char **argv)
       syms_of_xfns ();
       syms_of_xmenu ();
       syms_of_fontset ();
-      syms_of_xwidget ();
       syms_of_xsettings ();
 #ifdef HAVE_X_SM
       syms_of_xsmfns ();
@@ -1979,6 +1978,7 @@ main (int argc, char **argv)
 #endif /* HAVE_W32NOTIFY */
 #endif /* WINDOWSNT */
 
+      syms_of_xwidget ();
       syms_of_threads ();
       syms_of_profiler ();
       syms_of_pdumper ();
diff --git a/src/nsterm.m b/src/nsterm.m
index 02331826d9..9479c40006 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -49,6 +49,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"
@@ -2411,7 +2412,7 @@ so some key presses (TAB) are swallowed by the system.  */
 }
 
 static int
-ns_note_mouse_movement (struct frame *frame, CGFloat x, CGFloat y)
+ns_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
@@ -2420,17 +2421,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
+  /* Has movement gone beyond last rect we were tracking? */
+  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);
@@ -4182,6 +4190,10 @@ overwriting cursor (usually when cursor on a tab).  */
         }
       break;
 
+    case XWIDGET_GLYPH:
+      x_draw_xwidget_glyph_string (s);
+      break;
+
     case STRETCH_GLYPH:
       ns_dumpglyphs_stretch (s);
       break;
@@ -6835,6 +6847,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:]");
 
@@ -6877,7 +6890,8 @@ - (void)mouseMoved: (NSEvent *)e
       last_mouse_window = window;
     }
 
-  if (!ns_note_mouse_movement (emacsframe, pt.x, pt.y))
+  dragging = (e.type == NSEventTypeLeftMouseDragged);
+  if (!ns_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..6af5fe5a4d
--- /dev/null
+++ b/src/nsxwidget.h
@@ -0,0 +1,80 @@
+/* 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);
+Lisp_Object nsxwidget_webkit_uri (struct xwidget *xw);
+Lisp_Object nsxwidget_webkit_title (struct xwidget *xw);
+void nsxwidget_webkit_goto_uri (struct xwidget *xw, const char *uri);
+void nsxwidget_webkit_goto_history (struct xwidget *xw, int rel_pos);
+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..8d8a92d09c
--- /dev/null
+++ b/src/nsxwidget.m
@@ -0,0 +1,611 @@
+/* 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"
+
+#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;
+/* Map url to whether javascript is blocked by
+   'Content-Security-Policy' sandbox without allow-scripts.  */
+@property(retain) NSMutableDictionary *urlScriptBlocked;
+@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;
+
+  /* Enable inspect element context menu item for debugging.  */
+  [configuration.preferences setValue:@YES
+                               forKey:@"developerExtrasEnabled"];
+
+  Lisp_Object enablePlugins =
+    Fintern (build_string ("xwidget-webkit-enable-plugins"), Qnil);
+  if (!EQ (Fsymbol_value (enablePlugins), Qnil))
+    configuration.preferences.plugInsEnabled = YES;
+
+  self = [super initWithFrame:frame configuration:configuration];
+  if (self)
+    {
+      self.xw = xw;
+      self.urlScriptBlocked = [[NSMutableDictionary alloc] init];
+      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:
+                                  WKUserScriptInjectionTimeAtDocumentStart
+                                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
+{
+  if (EQ (Fbuffer_live_p (self.xw->buffer), Qt))
+    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)
+    {
+      NSString *url = navigationResponse.response.URL.absoluteString;
+      NSString *mimetype = navigationResponse.response.MIMEType;
+      NSString *filename = navigationResponse.response.suggestedFilename;
+      decisionHandler (WKNavigationResponsePolicyCancel);
+      store_xwidget_download_callback_event (self.xw,
+                                             url.UTF8String,
+                                             mimetype.UTF8String,
+                                             filename.UTF8String);
+      return;
+    }
+  decisionHandler (WKNavigationResponsePolicyAllow);
+
+  self.urlScriptBlocked[navigationResponse.response.URL] =
+    [NSNumber numberWithBool:NO];
+  if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]])
+    {
+      NSDictionary *headers =
+        ((NSHTTPURLResponse *) navigationResponse.response).allHeaderFields;
+      NSString *value = headers[@"Content-Security-Policy"];
+      if (value)
+        {
+          /* TODO: Sloppy parsing of 'Content-Security-Policy' value.  */
+          NSRange sandbox = [value rangeOfString:@"sandbox"];
+          if (sandbox.location != NSNotFound
+              && (sandbox.location == 0
+                  || [value characterAtIndex:(sandbox.location - 1)] == ' '
+                  || [value characterAtIndex:(sandbox.location - 1)] == ';'))
+            {
+              NSRange allowScripts = [value rangeOfString:@"allow-scripts"];
+              if (allowScripts.location == NSNotFound
+                  || allowScripts.location < sandbox.location)
+                self.urlScriptBlocked[navigationResponse.response.URL] =
+                  [NSNumber numberWithBool:YES];
+            }
+        }
+    }
+}
+
+/* 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;
+}
+
+/* Open panel for file upload.  */
+- (void)webView:(WKWebView *)webView
+runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters
+initiatedByFrame:(WKFrameInfo *)frame
+completionHandler:(void (^)(NSArray<NSURL *> *URLs))completionHandler
+{
+  NSOpenPanel *openPanel = [NSOpenPanel openPanel];
+  openPanel.canChooseFiles = YES;
+  openPanel.canChooseDirectories = NO;
+  openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection;
+  if ([openPanel runModal] == NSModalResponseOK)
+    completionHandler (openPanel.URLs);
+  else
+    completionHandler (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;
+    }
+
+  /* Emacs handles keyboard events when javascript is blocked.  */
+  if ([self.urlScriptBlocked[self.URL] boolValue])
+    {
+      [self.xw->xv->emacswindow keyDown:event];
+      return;
+    }
+
+  [self evaluateJavaScript:@"xwHasFocus()"
+         completionHandler:^(id result, NSError *error) {
+      if (error)
+        {
+          NSLog (@"xwHasFocus: %@", error);
+          [self.xw->xv->emacswindow keyDown:event];
+        }
+      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.  */
+
+static Lisp_Object build_string_with_nsstr (NSString *nsstr);
+
+bool
+nsxwidget_is_web_view (struct xwidget *xw)
+{
+  return xw->xwWidget != NULL &&
+    [xw->xwWidget isKindOfClass:WKWebView.class];
+}
+
+Lisp_Object
+nsxwidget_webkit_uri (struct xwidget *xw)
+{
+  XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
+  return build_string_with_nsstr (xwWebView.URL.absoluteString);
+}
+
+Lisp_Object
+nsxwidget_webkit_title (struct xwidget *xw)
+{
+  XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
+  return build_string_with_nsstr (xwWebView.title);
+}
+
+/* @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_goto_history (struct xwidget *xw, int rel_pos)
+{
+  XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
+  switch (rel_pos) {
+  case -1: [xwWebView goBack]; break;
+  case 0: [xwWebView reload]; break;
+  case 1: [xwWebView goForward]; break;
+  }
+}
+
+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_int (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)
+{
+  XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
+  if ([xwWebView.urlScriptBlocked[xwWebView.URL] boolValue])
+    {
+      message ("Javascript is blocked by 'CSP: sandbox'.");
+      return;
+    }
+
+  NSString *javascriptString = [NSString stringWithUTF8String:script];
+  [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.  */
+
+      /* This stops playing audio when a xwidget-webkit buffer is
+         killed.  I could not find other solution.  */
+      nsxwidget_webkit_goto_uri (xw, "about:blank");
+
+      [((XwWebView *) xw->xwWidget).urlScriptBlocked release];
+      [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 list2i (xw->xwWidget.frame.size.width,
+                 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 121510ebac..d63844ad92 100644
--- a/src/xwidget.c
+++ b/src/xwidget.c
@@ -23,21 +23,31 @@ Copyright (C) 2011-2019 Free Software Foundation, Inc.
 
 #include "lisp.h"
 #include "blockinput.h"
+#include "dispextern.h"
 #include "frame.h"
 #include "keyboard.h"
 #include "gtkutil.h"
 #include "sysstdio.h"
+#include "termhooks.h"
+#include "window.h"
 
+/* Include xwidget bottom end headers.  */
+#ifdef USE_GTK
 #include <webkit2/webkit2.h>
 #include <JavaScriptCore/JavaScript.h>
+#elif defined NS_IMPL_COCOA
+#include "nsxwidget.h"
+#endif
 
 /* Suppress GCC deprecation warnings starting in WebKitGTK+ 2.21.1 for
    webkit_javascript_result_get_global_context and
    webkit_javascript_result_get_value (Bug#33679).
    FIXME: Use the JavaScriptCore GLib API instead, and remove this hack.  */
+#ifdef USE_GTK
 #if WEBKIT_CHECK_VERSION (2, 21, 1) && GNUC_PREREQ (4, 2, 0)
 # pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 #endif
+#endif
 
 static struct xwidget *
 allocate_xwidget (void)
@@ -56,6 +66,7 @@ #define XSETXWIDGET_VIEW(a, b) XSETPSEUDOVECTOR (a, b, PVEC_XWIDGET_VIEW)
 
 static struct xwidget_view *xwidget_view_lookup (struct xwidget *,
 						 struct window *);
+#ifdef USE_GTK
 static void webkit_view_load_changed_cb (WebKitWebView *,
                                          WebKitLoadEvent,
                                          gpointer);
@@ -69,6 +80,7 @@ webkit_decide_policy_cb (WebKitWebView *,
                          WebKitPolicyDecision *,
                          WebKitPolicyDecisionType,
                          gpointer);
+#endif
 
 
 DEFUN ("make-xwidget",
@@ -86,8 +98,10 @@ DEFUN ("make-xwidget",
    Lisp_Object title, Lisp_Object width, Lisp_Object height,
    Lisp_Object arguments, Lisp_Object buffer)
 {
+#ifdef USE_GTK
   if (!xg_gtk_initialized)
     error ("make-xwidget: GTK has not been initialized");
+#endif
   CHECK_SYMBOL (type);
   CHECK_FIXNAT (width);
   CHECK_FIXNAT (height);
@@ -102,10 +116,11 @@ DEFUN ("make-xwidget",
   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;
 
+#ifdef USE_GTK
+  xw->widgetwindow_osr = NULL;
+  xw->widget_osr = NULL;
   if (EQ (xw->type, Qwebkit))
     {
       block_input ();
@@ -160,6 +175,9 @@ DEFUN ("make-xwidget",
 
       unblock_input ();
     }
+#elif defined NS_IMPL_COCOA
+  nsxwidget_init (xw);
+#endif
 
   return val;
 }
@@ -195,6 +213,7 @@ xwidget_hidden (struct xwidget_view *xv)
   return xv->hidden;
 }
 
+#ifdef USE_GTK
 static void
 xwidget_show_view (struct xwidget_view *xv)
 {
@@ -228,13 +247,14 @@ offscreen_damage_event (GtkWidget *widget, GdkEvent *event,
   if (GTK_IS_WIDGET (xv_widget))
     gtk_widget_queue_draw (GTK_WIDGET (xv_widget));
   else
-    printf ("Warning, offscreen_damage_event received invalid xv pointer:%p\n",
-            xv_widget);
+    message ("Warning, offscreen_damage_event received invalid xv pointer:%p\n",
+             xv_widget);
 
   return FALSE;
 }
+#endif /* USE_GTK */
 
-static void
+void
 store_xwidget_event_string (struct xwidget *xw, const char *eventname,
                             const char *eventstr)
 {
@@ -248,7 +268,27 @@ store_xwidget_event_string (struct xwidget *xw, const char *eventname,
   kbd_buffer_store_event (&event);
 }
 
-static void
+void
+store_xwidget_download_callback_event (struct xwidget *xw,
+                                       const char *url,
+                                       const char *mimetype,
+                                       const char *filename)
+{
+  struct input_event event;
+  Lisp_Object xwl;
+  XSETXWIDGET (xwl, xw);
+  EVENT_INIT (event);
+  event.kind = XWIDGET_EVENT;
+  event.frame_or_window = Qnil;
+  event.arg = list5 (intern ("download-callback"),
+                     xwl,
+                     build_string (url),
+                     build_string (mimetype),
+                     build_string (filename));
+  kbd_buffer_store_event (&event);
+}
+
+void
 store_xwidget_js_callback_event (struct xwidget *xw,
                                  Lisp_Object proc,
                                  Lisp_Object argument)
@@ -264,6 +304,7 @@ store_xwidget_js_callback_event (struct xwidget *xw,
 }
 
 
+#ifdef USE_GTK
 void
 webkit_view_load_changed_cb (WebKitWebView *webkitwebview,
                              WebKitLoadEvent load_event,
@@ -521,6 +562,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.  */
@@ -530,8 +572,10 @@ xwidget_init_view (struct xwidget *xww,
                    int x, int y)
 {
 
+#ifdef USE_GTK
   if (!xg_gtk_initialized)
     error ("xwidget_init_view: GTK has not been initialized");
+#endif
 
   struct xwidget_view *xv = allocate_xwidget_view ();
   Lisp_Object val;
@@ -542,6 +586,7 @@ xwidget_init_view (struct xwidget *xww,
   XSETWINDOW (xv->w, s->w);
   XSETXWIDGET (xv->model, xww);
 
+#ifdef USE_GTK
   if (EQ (xww->type, Qwebkit))
     {
       xv->widget = gtk_drawing_area_new ();
@@ -599,6 +644,10 @@ 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);
+  nsxwidget_resize_view(xv, xww->width, xww->height);
+#endif
 
   return xv;
 }
@@ -611,24 +660,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.  */
+#ifdef 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_int (text_area_width),
+                       make_int (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));
@@ -651,8 +735,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);
+    {
+#ifdef 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
@@ -663,10 +753,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)
     {
+#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);
+#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;
@@ -680,22 +776,66 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
      xwidgets background.  It's just a visual glitch though.  */
   if (!xwidget_hidden (xv))
     {
+#ifdef 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)
+{
+#ifdef 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))					\
     {									\
       fputs ("ERROR xw->widget_osr does not hold a webkit instance\n",	\
 	     stdout);							\
       return Qnil;							\
     }
 
+DEFUN ("xwidget-webkit-uri",
+       Fxwidget_webkit_uri, Sxwidget_webkit_uri,
+       1, 1, 0,
+       doc: /* Get the current URL of XWIDGET webkit.  */)
+  (Lisp_Object xwidget)
+{
+  WEBKIT_FN_INIT ();
+#ifdef USE_GTK
+  WebKitWebView *wkwv = WEBKIT_WEB_VIEW (xw->widget_osr);
+  return build_string (webkit_web_view_get_uri (wkwv));
+#elif defined NS_IMPL_COCOA
+  return nsxwidget_webkit_uri (xw);
+#endif
+}
+
+DEFUN ("xwidget-webkit-title",
+       Fxwidget_webkit_title, Sxwidget_webkit_title,
+       1, 1, 0,
+       doc: /* Get the current title of XWIDGET webkit.  */)
+  (Lisp_Object xwidget)
+{
+  WEBKIT_FN_INIT ();
+#ifdef USE_GTK
+  WebKitWebView *wkwv = WEBKIT_WEB_VIEW (xw->widget_osr);
+  return build_string (webkit_web_view_get_title (wkwv));
+#elif defined NS_IMPL_COCOA
+  return nsxwidget_webkit_title (xw);
+#endif
+}
+
 DEFUN ("xwidget-webkit-goto-uri",
        Fxwidget_webkit_goto_uri, Sxwidget_webkit_goto_uri,
        2, 2, 0,
@@ -705,7 +845,32 @@ DEFUN ("xwidget-webkit-goto-uri",
   WEBKIT_FN_INIT ();
   CHECK_STRING (uri);
   uri = ENCODE_FILE (uri);
+#ifdef 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;
+}
+
+DEFUN ("xwidget-webkit-goto-history",
+       Fxwidget_webkit_goto_history, Sxwidget_webkit_goto_history,
+       2, 2, 0,
+       doc: /* Make the XWIDGET webkit load REL-POS (-1, 0, 1) page in browse history.  */)
+  (Lisp_Object xwidget, Lisp_Object rel_pos)
+{
+  WEBKIT_FN_INIT ();
+  CHECK_RANGED_INTEGER (rel_pos, -1, 1); /* -1, 0, 1 */
+#ifdef USE_GTK
+  WebKitWebView *wkwv = WEBKIT_WEB_VIEW (xw->widget_osr);
+  switch (XFIXNAT (rel_pos)) {
+  case -1: webkit_web_view_go_back (wkwv); break;
+  case 0: webkit_web_view_reload (wkwv); break;
+  case 1: webkit_web_view_go_forward (wkwv); break;
+  }
+#elif defined NS_IMPL_COCOA
+  nsxwidget_webkit_goto_history (xw, XFIXNAT (rel_pos));
+#endif
   return Qnil;
 }
 
@@ -719,14 +884,19 @@ DEFUN ("xwidget-webkit-zoom",
   if (FLOATP (factor))
     {
       double zoom_change = XFLOAT_DATA (factor);
+#ifdef 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;
 }
 
+#ifdef USE_GTK
 /* Save script and fun in the script/callback save vector and return
    its index.  */
 static ptrdiff_t
@@ -748,6 +918,7 @@ save_script_callback (struct xwidget *xw, Lisp_Object script, Lisp_Object fun)
   ASET (cbs, idx, Fcons (make_mint_ptr (xlispstrdup (script)), fun));
   return idx;
 }
+#endif
 
 DEFUN ("xwidget-webkit-execute-script",
        Fxwidget_webkit_execute_script, Sxwidget_webkit_execute_script,
@@ -759,11 +930,15 @@ DEFUN ("xwidget-webkit-execute-script",
 {
   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) && !NILP (Ffboundp (fun)))
     wrong_type_argument (Qinvalid_function, fun);
 
   script = ENCODE_SYSTEM (script);
 
+#ifdef USE_GTK
   /* Protect script and fun during GC.  */
   intptr_t idx = save_script_callback (xw, script, fun);
 
@@ -777,6 +952,9 @@ DEFUN ("xwidget-webkit-execute-script",
                                   NULL, /* cancelable */
                                   webkit_javascript_finished_cb,
 				  (gpointer) idx);
+#elif defined NS_IMPL_COCOA
+  nsxwidget_webkit_execute_script (xw, SSDATA (script), fun);
+#endif
   return Qnil;
 }
 
@@ -795,6 +973,7 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
   xw->height = h;
 
   /* If there is an offscreen widget resize it first.  */
+#ifdef USE_GTK
   if (xw->widget_osr)
     {
       gtk_window_resize (GTK_WINDOW (xw->widgetwindow_osr), xw->width,
@@ -803,6 +982,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))
     {
@@ -810,8 +992,14 @@ 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
+            }
         }
     }
 
@@ -830,9 +1018,13 @@ DEFUN ("xwidget-size-request",
   (Lisp_Object xwidget)
 {
   CHECK_XWIDGET (xwidget);
+#ifdef USE_GTK
   GtkRequisition requisition;
   gtk_widget_size_request (XXWIDGET (xwidget)->widget_osr, &requisition);
   return list2i (requisition.width, requisition.height);
+#elif defined NS_IMPL_COCOA
+  return nsxwidget_get_size(XXWIDGET (xwidget));
+#endif
 }
 
 DEFUN ("xwidgetp",
@@ -909,14 +1101,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);
+#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);
+#elif defined NS_IMPL_COCOA
+  nsxwidget_delete_view (xv);
+#endif
+
   return Qnil;
 }
 
@@ -1022,7 +1219,10 @@ syms_of_xwidget (void)
   defsubr (&Sxwidget_query_on_exit_flag);
   defsubr (&Sset_xwidget_query_on_exit_flag);
 
+  defsubr (&Sxwidget_webkit_uri);
+  defsubr (&Sxwidget_webkit_title);
   defsubr (&Sxwidget_webkit_goto_uri);
+  defsubr (&Sxwidget_webkit_goto_history);
   defsubr (&Sxwidget_webkit_zoom);
   defsubr (&Sxwidget_webkit_execute_script);
   DEFSYM (Qwebkit, "webkit");
@@ -1193,11 +1393,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);
+#ifdef 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
 		}
 	  }
     }
@@ -1214,9 +1422,21 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
           if (XWINDOW (xv->w) == w)
             {
               if (xwidget_touched (xv))
-                xwidget_show_view (xv);
+                {
+#ifdef USE_GTK
+                  xwidget_show_view (xv);
+#elif defined NS_IMPL_COCOA
+                  nsxwidget_show_view (xv);
+#endif
+                }
               else
-                xwidget_hide_view (xv);
+                {
+#ifdef USE_GTK
+                  xwidget_hide_view (xv);
+#elif defined NS_IMPL_COCOA
+                  nsxwidget_hide_view (xv);
+#endif
+                }
             }
         }
     }
@@ -1235,6 +1455,7 @@ kill_buffer_xwidgets (Lisp_Object buffer)
       {
         CHECK_XWIDGET (xwidget);
         struct xwidget *xw = XXWIDGET (xwidget);
+#ifdef USE_GTK
         if (xw->widget_osr && xw->widgetwindow_osr)
           {
             gtk_widget_destroy (xw->widget_osr);
@@ -1248,6 +1469,9 @@ kill_buffer_xwidgets (Lisp_Object buffer)
 		xfree (xmint_pointer (XCAR (cb)));
 	      ASET (xw->script_callbacks, idx, Qnil);
 	    }
+#elif defined NS_IMPL_COCOA
+        nsxwidget_kill (xw);
+#endif
       }
     }
 }
diff --git a/src/xwidget.h b/src/xwidget.h
index 1b6368daab..11260f98b1 100644
--- a/src/xwidget.h
+++ b/src/xwidget.h
@@ -29,7 +29,13 @@ #define XWIDGET_H_INCLUDED
 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
 {
@@ -54,9 +60,25 @@ #define XWIDGET_H_INCLUDED
   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;
@@ -75,9 +97,20 @@ #define XWIDGET_H_INCLUDED
   /* 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;
@@ -116,6 +149,21 @@ #define XG_XWIDGET_VIEW "emacs_xwidget_view"
 struct xwidget *lookup_xwidget (Lisp_Object spec);
 void xwidget_end_redisplay (struct window *, struct glyph_matrix *);
 void kill_buffer_xwidgets (Lisp_Object);
+#ifdef NS_IMPL_COCOA
+/* Defined in 'xwidget.c'.  */
+void store_xwidget_event_string (struct xwidget *xw,
+                                 const char *eventname,
+                                 const char *eventstr);
+
+void store_xwidget_download_callback_event (struct xwidget *xw,
+                                            const char *url,
+                                            const char *mimetype,
+                                            const char *filename);
+
+void store_xwidget_js_callback_event (struct xwidget *xw,
+                                      Lisp_Object proc,
+                                      Lisp_Object argument);
+#endif
 #else
 INLINE_HEADER_BEGIN
 INLINE void syms_of_xwidget (void) {}
-- 
2.17.2 (Apple Git-113)




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

end of thread, other threads:[~2019-08-18 23:42 UTC | newest]

Thread overview: 49+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2019-07-18 19:23 [PATCH v4] Enable xwidgets on macOS Sungbin Jo
2019-07-19  4:16 ` [PATCH v5] " Sungbin Jo
2019-07-19 19:17   ` Alan Third
2019-07-19 19:25     ` Savannah down (was: [PATCH v5] Enable xwidgets on macOS) Paul Eggert
2019-07-20  8:21   ` [PATCH v5] Enable xwidgets on macOS Eli Zaretskii
2019-07-23 18:36     ` 조성빈
2019-07-25 18:51       ` 조성빈
2019-07-26  5:55         ` Eli Zaretskii
2019-07-27  2:48           ` Richard Stallman
2019-07-27  8:38             ` Eli Zaretskii
2019-07-27 10:41         ` Eli Zaretskii
2019-07-27 12:35           ` 조성빈
2019-07-27 13:03             ` Eli Zaretskii
2019-07-29 17:08               ` 조성빈
2019-08-03 10:03                 ` Eli Zaretskii
2019-08-03 10:52                   ` 조성빈
2019-07-29 20:26         ` Alan Third
2019-07-29 21:02           ` Stefan Monnier
2019-07-30 15:35             ` 조성빈
2019-07-30 19:25               ` Stefan Monnier
2019-07-31 15:52                 ` 조성빈
2019-07-30 15:33           ` 조성빈
2019-07-30 19:12             ` Juri Linkov
2019-07-31 15:55               ` 조성빈
2019-07-31 19:56             ` Alan Third
2019-08-01  2:35               ` Eli Zaretskii
2019-08-01  4:00                 ` 조성빈
2019-08-02  0:47                   ` Richard Stallman
2019-08-02  7:02                     ` Eli Zaretskii
2019-08-03  2:23                       ` Richard Stallman
2019-08-03  6:58                         ` Eli Zaretskii
2019-08-01 21:39                 ` macOS/GCC support policy (was: [PATCH v5] Enable xwidgets on macOS) Alan Third
2019-08-02  2:22                   ` Noam Postavsky
2019-08-02  6:56                   ` macOS/GCC support policy Eli Zaretskii
2019-08-02 10:08                     ` Philipp Stephani
2019-08-02 11:51                       ` Eli Zaretskii
2019-08-02 14:55                         ` Philipp Stephani
2019-08-02 14:59                           ` Philipp Stephani
2019-08-02 15:06                             ` Philipp Stephani
2019-08-02 15:05                           ` Eli Zaretskii
2019-08-03 11:02                             ` Alan Third
2019-08-03 11:10                               ` Eli Zaretskii
2019-08-03 11:18                                 ` Alan Third
2019-08-03 11:43                                   ` Eli Zaretskii
2019-08-04  2:56                                   ` Richard Stallman
2019-08-10  9:56                                     ` 조성빈
2019-08-18 15:43                                       ` Alan Third
2019-08-18 16:40                                         ` Eli Zaretskii
2019-08-18 23:42                                         ` Richard Stallman

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

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

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