From: 조성빈 <pcr910303@icloud.com>
To: Paul Eggert <eggert@cs.ucla.edu>
Cc: emacs-devel@gnu.org
Subject: Re: [PATCH v2] Add xwidget webkit support for macOS Cocoa
Date: Sun, 2 Jun 2019 15:14:14 +0900 [thread overview]
Message-ID: <FD1E6D44-4FB0-4491-A0E6-8C043E1F4B40@icloud.com> (raw)
In-Reply-To: <374E0E1B-6642-4338-A1B1-F373AC587FD2@icloud.com>
It feels like I’m spamming the mailing list, (Sorry!) but can anybody review my patch? I would really like to use the xwidget functionality in macOS :-(
2019. 5. 29. 오전 12:07, 조성빈 <pcr910303@icloud.com> 작성:
> ---
> configure.ac | 34 +-
> lisp/xwidget.el | 315 ++++++++++++----
> nextstep/templates/Info.plist.in | 12 +-
> src/Makefile.in | 1 +
> src/emacs.c | 2 +-
> src/nsterm.m | 23 +-
> src/nsxwidget.h | 80 ++++
> src/nsxwidget.m | 611 +++++++++++++++++++++++++++++++
> src/xwidget.c | 261 ++++++++++++-
> src/xwidget.h | 50 ++-
> 10 files changed, 1288 insertions(+), 101 deletions(-)
> create mode 100644 src/nsxwidget.h
> create mode 100644 src/nsxwidget.m
>
> diff --git a/configure.ac b/configure.ac
> index 0f1fd5d26e..443ff4e6c2 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -483,7 +483,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.
> @@ -2804,20 +2804,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)
> @@ -5656,7 +5670,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..f93b4bedba 100644
> --- a/lisp/xwidget.el
> +++ b/lisp/xwidget.el
> @@ -39,9 +39,14 @@
> (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))
> +;; @callback - Prefer defun to lambda, not to be garbage collected
> +;; before its execution in `xwidget-webkit-callback'.
> (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))
> @@ -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,22 @@ xwidget-webkit-browse-url
> (xwidget-webkit-new-session url)
> (xwidget-webkit-goto-url url))))
>
> +(defun xwidget-webkit-cx2 ()
> + "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-cx3 ()
> + "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 +126,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 (kbd "C-x 2") 'xwidget-webkit-cx2)
> + (define-key map (kbd "C-x 3") 'xwidget-webkit-cx3))
> map)
> "Keymap for `xwidget-webkit-mode'.")
>
> @@ -144,19 +175,48 @@ 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)
> + (cond ((null n)
> + (format "window.scrollBy(0, %d);"
> + (xwidget-window-inside-pixel-height (selected-window))))
> + (t (format "window.scrollBy(0, %d);" n)))))
> +
> +(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);"))
> + (cond ((null n)
> + (format "window.scrollBy(0, %d);"
> + (- (xwidget-window-inside-pixel-height (selected-window)))))
> + (t (format "window.scrollBy(0, %d);" (- n))))))
> +
> +(defvar xwidget-webkit-scroll-line-height 50
> + "Default line height in pixels for scroll xwidget webkit.")
> +
> +(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 ()
> "Scroll webkit forwards."
> @@ -184,7 +244,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.
> @@ -192,7 +252,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 +268,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 +278,148 @@ 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 'response-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 xwidget 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
> + (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)
> +(when (memq window-system '(mac ns))
> + (defvar xwidget-webkit-enable-plugins nil
> + "Enable plugins for xwidget webkit.
> +If non-nil, plugins are enabled. Otherwise, disabled."))
> +
> (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.
> +
> +(defvar xwidget-webkit-download-dir "~/Downloads/"
> + "Directory where download file saved.")
> +
> +(defun xwidget-webkit-save-as-file (xwidget url mime-type &optional file-name)
> + "For XWIDGET webkit, save URL resource of MIME-TYPE as FILE-NAME."
> + (ignore xwidget) ;; Not used currently
> + (let ((save-name (read-file-name
> + (format "Save '%s' file as: " mime-type)
> + xwidget-webkit-download-dir file-name nil file-name)))
> + (if (file-directory-p save-name)
> + (setq save-name (concat (file-name-as-directory save-name) file-name)))
> + (setq xwidget-webkit-download-dir (file-name-directory save-name))
> + (url-copy-file url save-name t)))
> +
> +;;; 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'.")
> +
> (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 . ,(xwidget-webkit-current-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 +467,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 +477,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 +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,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)))
> @@ -506,23 +690,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 ()
> + "Message 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 +721,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.inb/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 3aab5270a4..77200440af 100644
> --- a/src/Makefile.in
> +++ b/src/Makefile.in
> @@ -426,6 +426,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 fd46540ce2..e568724ac2 100644
> --- a/src/emacs.c
> +++ b/src/emacs.c
> @@ -1778,7 +1778,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 ();
> @@ -1855,6 +1854,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 0cae5e9d44..14e11580c1 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"
> @@ -827,7 +828,6 @@ Free a pool and temporary objects it refers to (callable from C)
> enum glyph_row_area area)
> /* Get the row as an NSRect. */
> {
> - struct frame *f = XFRAME (WINDOW_FRAME (w));
> NSRect rect;
> int window_x, window_y, window_width;
>
> @@ -2412,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
> @@ -2421,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);
> @@ -4170,6 +4177,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;
> @@ -6823,6 +6834,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:]");
>
> @@ -6865,7 +6877,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..0119087f47
> --- /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_response_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 2486a2d4da..a6bf13821d 100644
> --- a/src/xwidget.c
> +++ b/src/xwidget.c
> @@ -18,25 +18,36 @@ Copyright (C) 2011-2019 Free Software Foundation, Inc.
> 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
>
> /* 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. */
> +#if defined (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)
> @@ -55,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 *);
> +#if defined (USE_GTK)
> static void webkit_view_load_changed_cb (WebKitWebView *,
> WebKitLoadEvent,
> gpointer);
> @@ -68,6 +80,7 @@ webkit_decide_policy_cb (WebKitWebView *,
> WebKitPolicyDecision *,
> WebKitPolicyDecisionType,
> gpointer);
> +#endif
>
>
> DEFUN ("make-xwidget",
> @@ -85,8 +98,10 @@ DEFUN ("make-xwidget",
> Lisp_Object title, Lisp_Object width, Lisp_Object height,
> Lisp_Object arguments, Lisp_Object buffer)
> {
> +#if defined (USE_GTK)
> if (!xg_gtk_initialized)
> error ("make-xwidget: GTK has not been initialized");
> +#endif
> CHECK_SYMBOL (type);
> CHECK_FIXNAT (width);
> CHECK_FIXNAT (height);
> @@ -101,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;
>
> +#if defined (USE_GTK)
> + xw->widgetwindow_osr = NULL;
> + xw->widget_osr = NULL;
> if (EQ (xw->type, Qwebkit))
> {
> block_input ();
> @@ -159,6 +175,9 @@ DEFUN ("make-xwidget",
>
> unblock_input ();
> }
> +#elif defined (NS_IMPL_COCOA)
> + nsxwidget_init (xw);
> +#endif
>
> return val;
> }
> @@ -194,6 +213,7 @@ xwidget_hidden (struct xwidget_view *xv)
> return xv->hidden;
> }
>
> +#if defined (USE_GTK)
> static void
> xwidget_show_view (struct xwidget_view *xv)
> {
> @@ -227,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)
> {
> @@ -247,7 +268,27 @@ store_xwidget_event_string (struct xwidget *xw, const char *eventname,
> kbd_buffer_store_event (&event);
> }
>
> -static void
> +void
> +store_xwidget_response_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 ("response-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)
> @@ -263,6 +304,7 @@ store_xwidget_js_callback_event (struct xwidget *xw,
> }
>
>
> +#if defined (USE_GTK)
> void
> webkit_view_load_changed_cb (WebKitWebView *webkitwebview,
> WebKitLoadEvent load_event,
> @@ -520,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. */
> @@ -529,8 +572,10 @@ xwidget_init_view (struct xwidget *xww,
> int x, int y)
> {
>
> +#if defined (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;
> @@ -541,6 +586,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 ();
> @@ -598,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;
> }
> @@ -610,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. */
> +#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_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));
> @@ -650,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);
> + {
> +#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
> @@ -662,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)
> {
> +#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;
> @@ -679,21 +776,65 @@ 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"); \
> + message ("ERROR xwidget does not hold a webkit instance\n"); \
> 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 ();
> +#if defined (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 ();
> +#if defined (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,
> @@ -703,7 +844,32 @@ DEFUN ("xwidget-webkit-goto-uri",
> WEBKIT_FN_INIT ();
> CHECK_STRING (uri);
> uri = ENCODE_FILE (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;
> +}
> +
> +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 */
> +#if defined (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;
> }
>
> @@ -717,14 +883,19 @@ DEFUN ("xwidget-webkit-zoom",
> 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;
> }
>
> +#if defined(USE_GTK)
> /* Save script and fun in the script/callback save vector and return
> its index. */
> static ptrdiff_t
> @@ -746,6 +917,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,
> @@ -757,11 +929,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) && !SYMBOLP (fun) && !NILP (Ffboundp (fun)))
> wrong_type_argument (Qinvalid_function, fun);
>
> script = ENCODE_SYSTEM (script);
>
> +#if defined (USE_GTK)
> /* Protect script and fun during GC. */
> intptr_t idx = save_script_callback (xw, script, fun);
>
> @@ -775,6 +951,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;
> }
>
> @@ -793,6 +972,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,
> @@ -801,6 +981,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))
> {
> @@ -808,8 +991,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
> + }
> }
> }
>
> @@ -828,9 +1017,13 @@ DEFUN ("xwidget-size-request",
> (Lisp_Object xwidget)
> {
> CHECK_XWIDGET (xwidget);
> +#if defined (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",
> @@ -907,14 +1100,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;
> }
>
> @@ -1020,7 +1218,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");
> @@ -1191,11 +1392,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
> }
> }
> }
> @@ -1212,9 +1421,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
> + }
> }
> }
> }
> @@ -1233,6 +1454,7 @@ 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);
> @@ -1246,6 +1468,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..0042912fc6 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_response_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)
next prev parent reply other threads:[~2019-06-02 6:14 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-05-25 13:27 [PATCH] Add xwidget webkit support for macOS Cocoa Sungbin Jo
2019-05-25 17:47 ` Paul Eggert
2019-05-26 2:57 ` 조성빈
2019-05-26 6:02 ` Paul Eggert
2019-05-28 15:07 ` [PATCH v2] " 조성빈
2019-06-02 6:14 ` 조성빈 [this message]
2019-06-02 14:39 ` Eli Zaretskii
2019-06-03 16:11 ` Alan Third
2019-06-03 17:25 ` Stefan Monnier
2019-06-03 23:36 ` Paul Eggert
2019-06-03 23:51 ` Sungbin Jo
2019-06-04 2:40 ` Stefan Monnier
2019-06-04 8:33 ` Andreas Schwab
2019-06-05 3:06 ` Sungbin Jo
2019-06-05 3:18 ` [PATCH v3] " Sungbin Jo
2019-06-05 13:55 ` Stefan Monnier
2019-06-05 14:34 ` Eli Zaretskii
2019-06-23 22:38 ` Alan Third
2019-06-04 16:22 ` [PATCH v2] " Alan Third
-- strict thread matches above, loose matches on Subject: below --
2019-05-27 9:50 Sungbin Jo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=FD1E6D44-4FB0-4491-A0E6-8C043E1F4B40@icloud.com \
--to=pcr910303@icloud.com \
--cc=eggert@cs.ucla.edu \
--cc=emacs-devel@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.