* [PATCH] Add xwidget webkit support for macOS Cocoa @ 2019-05-25 13:27 Sungbin Jo 2019-05-25 17:47 ` Paul Eggert 0 siblings, 1 reply; 19+ messages in thread From: Sungbin Jo @ 2019-05-25 13:27 UTC (permalink / raw) To: emacs-devel; +Cc: Sungbin Jo --- configure.ac | 35 +- lisp/xwidget.el | 315 ++++++++++++---- nextstep/templates/Info.plist.in | 12 +- src/Makefile.in | 1 + src/emacs.c | 5 +- src/nsterm.m | 22 +- src/nsxwidget.h | 80 ++++ src/nsxwidget.m | 625 +++++++++++++++++++++++++++++++ src/xwidget.c | 255 ++++++++++++- src/xwidget.h | 35 +- 10 files changed, 1287 insertions(+), 98 deletions(-) create mode 100644 src/nsxwidget.h create mode 100644 src/nsxwidget.m diff --git a/configure.ac b/configure.ac index 0f1fd5d26e..4551fe2fc7 100644 --- a/configure.ac +++ b/configure.ac @@ -483,7 +483,7 @@ otherwise for the first of 'inotify', 'kqueue' or 'gfile' that is usable.]) [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 @@ fi dnl Enable xwidgets if GTK3 and WebKitGTK+ are available. +dnl Enable xwidgets if macOS Cocoa and WebKit framework are available. HAVE_XWIDGETS=no XWIDGETS_OBJ= if test "$with_xwidgets" != "no"; then - test "$USE_GTK_TOOLKIT" = "GTK3" && test "$window_system" != "none" || - AC_MSG_ERROR([xwidgets requested but gtk3 not used.]) + if test "$USE_GTK_TOOLKIT" = "GTK3" && test "$window_system" != "none"; then + WEBKIT_REQUIRED=2.12 + WEBKIT_MODULES="webkit2gtk-4.0 >= $WEBKIT_REQUIRED" + EMACS_CHECK_MODULES([WEBKIT], [$WEBKIT_MODULES]) + HAVE_XWIDGETS=$HAVE_WEBKIT + XWIDGETS_OBJ="xwidget.o" + elif test "${NS_IMPL_COCOA}" = "yes"; then + dnl FIXME: Check framework WebKit2 + dnl WEBKIT_REQUIRED=M.m.p + WEBKIT_LIBS="-Wl,-framework -Wl,WebKit" + WEBKIT_CFLAGS="-D_REENTRANT -I/System/Library/Frameworks/WebKit.framework/Headers" + HAVE_WEBKIT="yes" + HAVE_XWIDGETS=$HAVE_WEBKIT + XWIDGETS_OBJ="xwidget.o" + NS_OBJC_OBJ="$NS_OBJC_OBJ nsxwidget.o" + dnl Update NS_OBJC_OBJ with added nsxwidget.o + AC_SUBST(NS_OBJC_OBJ) + else + AC_MSG_ERROR([xwidgets requested, it requires GTK3 as X window toolkit or macOS Cocoa as window 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,8 @@ AS_ECHO([" Does Emacs use -lXaw3d? ${HAVE_XAW3D Does Emacs directly use zlib? ${HAVE_ZLIB} Does Emacs have dynamic modules support? ${HAVE_MODULES} Does Emacs use toolkit scroll bars? ${USE_TOOLKIT_SCROLL_BARS} - Does Emacs support Xwidgets (requires gtk3)? ${HAVE_XWIDGETS} + Does Emacs support Xwidgets? ${HAVE_XWIDGETS} + (requires gtk3 or macOS Cocoa) Does Emacs have threading support in lisp? ${threads_enabled} 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 @@ This returns the result of `make-xwidget'." ;;; 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 @@ Interactively, URL defaults to the string looking like a url around point." (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 @@ Interactively, URL defaults to the string looking like a url around point." (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 @@ Interactively, URL defaults to the string looking like a url around point." (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 @@ Interactively, URL defaults to the string looking like a url around point." (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 @@ Interactively, URL defaults to the string looking like a url around point." (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 @@ Interactively, URL defaults to the string looking like a url around point." ;;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 instance, XWIDGET-EVENT-TYPE depends on the originating xwidget." "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 @@ function findactiveelement(doc){ " - "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 @@ function findactiveelement(doc){ ) (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 @@ For example, use this to display an anchor." (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 @@ For example, use this to display an anchor." (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 @@ For example, use this to display an anchor." (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 @@ For example, use this to display an anchor." 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..6ec4c6f764 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 = dosfns.o msdos.o \ 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..4b6d3c2a41 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -1778,7 +1778,6 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem syms_of_xfns (); syms_of_xmenu (); syms_of_fontset (); - syms_of_xwidget (); syms_of_xsettings (); #ifdef HAVE_X_SM syms_of_xsmfns (); @@ -1855,6 +1854,10 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem #endif /* HAVE_W32NOTIFY */ #endif /* WINDOWSNT */ +#ifdef HAVE_XWIDGETS + syms_of_xwidget (); +#endif /* HAVE_XWIDGETS */ + syms_of_threads (); syms_of_profiler (); syms_of_pdumper (); diff --git a/src/nsterm.m b/src/nsterm.m index 0cae5e9d44..552786be1f 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" @@ -2412,7 +2413,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 +2422,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 +4178,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 +6835,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 +6878,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..0d628b533a --- /dev/null +++ b/src/nsxwidget.m @@ -0,0 +1,625 @@ +/* NS Cocoa part implementation of xwidget and webkit widget. + +Copyright (C) 1989, 1992-1994, 2005-2006, 2008-2017 Free Software +Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */ + +#include <config.h> + +#include "lisp.h" +#include "blockinput.h" +#include "dispextern.h" +#include "buffer.h" +#include "frame.h" +#include "nsterm.h" +#include "xwidget.h" + +/* Defined in 'xwidget.c'. */ +void store_xwidget_event_string (struct xwidget *xw, + const char *eventname, + const char *eventstr); + +void store_xwidget_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); + +#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..dad7945437 100644 --- a/src/xwidget.c +++ b/src/xwidget.c @@ -18,25 +18,36 @@ You should have received a copy of the GNU General Public License along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ #include <config.h> +#include <stdio.h> /* FIXME: Emacs error? message? instead of printf. */ #include "xwidget.h" #include "lisp.h" #include "blockinput.h" +#include "dispextern.h" #include "frame.h" #include "keyboard.h" #include "gtkutil.h" +#include "termhooks.h" +#include "window.h" +/* Include xwidget bottom end headers. */ +#if defined (USE_GTK) #include <webkit2/webkit2.h> #include <JavaScriptCore/JavaScript.h> +#elif defined (NS_IMPL_COCOA) +#include "nsxwidget.h" +#endif /* 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 @@ allocate_xwidget_view (void) static struct xwidget_view *xwidget_view_lookup (struct xwidget *, struct window *); +#if defined (USE_GTK) static void webkit_view_load_changed_cb (WebKitWebView *, WebKitLoadEvent, gpointer); @@ -68,6 +80,7 @@ webkit_decide_policy_cb (WebKitWebView *, WebKitPolicyDecision *, WebKitPolicyDecisionType, gpointer); +#endif DEFUN ("make-xwidget", @@ -85,8 +98,10 @@ Returns the newly constructed xwidget, or nil if construction fails. */) 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 @@ Returns the newly constructed xwidget, or nil if construction fails. */) xw->kill_without_query = false; XSETXWIDGET (val, xw); Vxwidget_list = Fcons (val, Vxwidget_list); - xw->widgetwindow_osr = NULL; - xw->widget_osr = NULL; xw->plist = Qnil; +#if defined (USE_GTK) + xw->widgetwindow_osr = NULL; + xw->widget_osr = NULL; if (EQ (xw->type, Qwebkit)) { block_input (); @@ -159,6 +175,9 @@ Returns the newly constructed xwidget, or nil if construction fails. */) 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) { @@ -232,8 +252,9 @@ offscreen_damage_event (GtkWidget *widget, GdkEvent *event, return FALSE; } +#endif /* USE_GTK */ -static void +void store_xwidget_event_string (struct xwidget *xw, const char *eventname, const char *eventstr) { @@ -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"); \ + printf ("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,10 +883,14 @@ 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; } @@ -757,7 +927,10 @@ argument procedure FUN.*/) { WEBKIT_FN_INIT (); CHECK_STRING (script); - if (!NILP (fun) && !FUNCTIONP (fun)) + /* FUN will not be garbage collected if it is defined with `defun' + instead of `lambda'. If it is garbage collected even though it + is `defun', we can counter by pinning the FUN's symbol. */ + if (!NILP (fun) && !SYMBOLP (fun) && !NILP (Ffboundp (fun))) wrong_type_argument (Qinvalid_function, fun); script = ENCODE_SYSTEM (script); @@ -765,6 +938,7 @@ argument procedure FUN.*/) /* Protect script and fun during GC. */ intptr_t idx = save_script_callback (xw, script, fun); +#if defined (USE_GTK) /* JavaScript execution happens asynchronously. If an elisp callback function is provided we pass it to the C callback procedure that retrieves the return value. */ @@ -775,6 +949,9 @@ argument procedure FUN.*/) 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 +970,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 +979,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 +989,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 +1015,13 @@ Emacs allocated area accordingly. */) (Lisp_Object xwidget) { CHECK_XWIDGET (xwidget); +#if defined (USE_GTK) GtkRequisition requisition; gtk_widget_size_request (XXWIDGET (xwidget)->widget_osr, &requisition); return list2i (requisition.width, requisition.height); +#elif defined (NS_IMPL_COCOA) + return nsxwidget_get_size(XXWIDGET (xwidget)); +#endif } DEFUN ("xwidgetp", @@ -907,14 +1098,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 +1216,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 +1390,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 +1419,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 +1452,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 +1466,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..057d200c3b 100644 --- a/src/xwidget.h +++ b/src/xwidget.h @@ -29,7 +29,13 @@ struct xwidget_view; struct window; #ifdef HAVE_XWIDGETS -# include <gtk/gtk.h> + +#if defined (USE_GTK) +#include <gtk/gtk.h> +#elif defined (NS_IMPL_COCOA) && defined (__OBJC__) +#import <AppKit/NSView.h> +#import "nsxwidget.h" +#endif struct xwidget { @@ -54,9 +60,25 @@ struct xwidget int height; int width; +#if defined (USE_GTK) /* For offscreen widgets, unused if not osr. */ GtkWidget *widget_osr; GtkWidget *widgetwindow_osr; +#elif defined (NS_IMPL_COCOA) +# ifdef __OBJC__ + /* For offscreen widgets, unused if not osr. */ + NSView *xwWidget; + XwWindow *xwWindow; + + /* Used only for xwidget types (such as webkit2) enforcing 1 to 1 + relationship between model and view. */ + struct xwidget_view *xv; +# else + void *xwWidget; + void *xwWindow; + struct xwidget_view *xv; +# endif +#endif /* Kill silently if Emacs is exited. */ bool_bf kill_without_query : 1; @@ -75,9 +97,20 @@ struct xwidget_view /* The "live" instance isn't drawn. */ bool hidden; +#if defined (USE_GTK) GtkWidget *widget; GtkWidget *widgetwindow; GtkWidget *emacswindow; +#elif defined (NS_IMPL_COCOA) +# ifdef __OBJC__ + XvWindow *xvWindow; + NSView *emacswindow; +# else + void *xvWindow; + void *emacswindow; +# endif +#endif + int x; int y; int clip_right; -- 2.17.2 (Apple Git-113) ^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH] Add xwidget webkit support for macOS Cocoa 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 ` 조성빈 0 siblings, 1 reply; 19+ messages in thread From: Paul Eggert @ 2019-05-25 17:47 UTC (permalink / raw) To: Sungbin Jo; +Cc: emacs-devel Thanks for doing all that work! Although I have no expertise in macOS I have a few minor comments about the patch from the perspective of someone who builds and ports Emacs, and who wants to help navigate this patch through the Emacs build bureaucracy. First, when I tried to use "git am" to apply the patch, I got these diagnostics about white-space problems that should be looked at: Applying: Add xwidget webkit support for macOS Cocoa .git/rebase-apply/patch:594: space before tab in indent. for detail information about `NSApplicationDefinedMask'. --> warning: 1 line adds whitespace errors. nextstep/templates/Info.plist.in:682: space before tab in indent. + for detail information about `NSApplicationDefinedMask'. --> Next, when I tried to build by doing "./configure --enable-gcc-warnings --with-xwidgets" on Fedora 30, I got the following diagnostics that should get fixed: xwidget.c:258:1: warning: no previous prototype for ‘store_xwidget_event_string’ [-Wmissing-prototypes] 258 | store_xwidget_event_string (struct xwidget *xw, const char *eventname, | ^~~~~~~~~~~~~~~~~~~~~~~~~~ xwidget.c:272:1: warning: no previous prototype for ‘store_xwidget_response_callback_event’ [-Wmissing-prototypes] 272 | store_xwidget_response_callback_event (struct xwidget *xw, | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ xwidget.c:292:1: warning: no previous prototype for ‘store_xwidget_js_callback_event’ [-Wmissing-prototypes] 292 | store_xwidget_js_callback_event (struct xwidget *xw, | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > + WEBKIT_CFLAGS="-D_REENTRANT -I/System/Library/Frameworks/WebKit.framework/Headers" Why is that -D_REENTRANT needed? I thought _REENTRANT was obsolete on macOS. > - Does Emacs support Xwidgets (requires gtk3)? ${HAVE_XWIDGETS} > + Does Emacs support Xwidgets? ${HAVE_XWIDGETS} > + (requires gtk3 or macOS Cocoa) Probably better to omit that second line; that part of the output is getting too long anyway. > - syms_of_xwidget (); > syms_of_xsettings (); > #ifdef HAVE_X_SM > syms_of_xsmfns (); > @@ -1855,6 +1854,10 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem > #endif /* HAVE_W32NOTIFY */ > #endif /* WINDOWSNT */ > > +#ifdef HAVE_XWIDGETS > + syms_of_xwidget (); > +#endif /* HAVE_XWIDGETS */ > + Why move the call to syms_of_xwidget? And why surround it with "#ifdef", since syms_of_xwidget is a no-op if HAVE_XWIDGETS is not defined? It's better to avoid #if when that's convenient. > +#include <stdio.h> /* FIXME: Emacs error? message? instead of printf. */ Yes, we don't want printfs there. > +#if defined (USE_GTK) > #include <webkit2/webkit2.h> > #include <JavaScriptCore/JavaScript.h> > +#elif defined (NS_IMPL_COCOA) > +#include "nsxwidget.h" > +#endif Indent preprocessor directives consistently. No parens needed in "defined X". "#ifdef X" is easier to read than "#if defined X". > +#if defined (USE_GTK) > #if WEBKIT_CHECK_VERSION (2, 21, 1) && GNUC_PREREQ (4, 2, 0) > # pragma GCC diagnostic ignored "-Wdeprecated-declarations" > #endif > +#endif Use just one "#if" rather than nested ones. I didn't look in detail at the .m or .el changes, but this is good enough for now. ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH] Add xwidget webkit support for macOS Cocoa 2019-05-25 17:47 ` Paul Eggert @ 2019-05-26 2:57 ` 조성빈 2019-05-26 6:02 ` Paul Eggert 0 siblings, 1 reply; 19+ messages in thread From: 조성빈 @ 2019-05-26 2:57 UTC (permalink / raw) To: Paul Eggert; +Cc: emacs-devel 2019. 5. 26. 오전 2:47, Paul Eggert <eggert@cs.ucla.edu> 작성: > Thanks for doing all that work! Although I have no expertise in macOS I have a few minor comments about the patch from the perspective of someone who builds and ports Emacs, and who wants to help navigate this patch through the Emacs build bureaucracy. First, when I tried to use "git am" to apply the patch, I got these diagnostics about white-space problems that should be looked at: > > Applying: Add xwidget webkit support for macOS Cocoa > .git/rebase-apply/patch:594: space before tab in indent. > for detail information about `NSApplicationDefinedMask'. --> > warning: 1 line adds whitespace errors. > nextstep/templates/Info.plist.in:682: space before tab in indent. > + for detail information about `NSApplicationDefinedMask'. --> Definitely a mistake, will fix as soon as I get home :-) > Next, when I tried to build by doing "./configure --enable-gcc-warnings --with-xwidgets" on Fedora 30, I got the following diagnostics that should get fixed: > > > xwidget.c:258:1: warning: no previous prototype for ‘store_xwidget_event_string’ [-Wmissing-prototypes] > 258 | store_xwidget_event_string (struct xwidget *xw, const char *eventname, > | ^~~~~~~~~~~~~~~~~~~~~~~~~~ > xwidget.c:272:1: warning: no previous prototype for ‘store_xwidget_response_callback_event’ [-Wmissing-prototypes] > 272 | store_xwidget_response_callback_event (struct xwidget *xw, > | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > xwidget.c:292:1: warning: no previous prototype for ‘store_xwidget_js_callback_event’ [-Wmissing-prototypes] > 292 | store_xwidget_js_callback_event (struct xwidget *xw, > | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Will add prototypes where appropriate... >> + WEBKIT_CFLAGS="-D_REENTRANT -I/System/Library/Frameworks/WebKit.framework/Headers" > > Why is that -D_REENTRANT needed? I thought _REENTRANT was obsolete on macOS. This was because this patch vas developed back from 2017 (by another person who signed the FSF agreements and also expressed his intents to contribute but didn’t...), and I was just maintaining it. Didn’t bother to fix that... will fix that too. >> - Does Emacs support Xwidgets (requires gtk3)? ${HAVE_XWIDGETS} >> + Does Emacs support Xwidgets? ${HAVE_XWIDGETS} >> + (requires gtk3 or macOS Cocoa) > > Probably better to omit that second line; that part of the output is getting too long anyway. Agreed. >> - syms_of_xwidget (); >> syms_of_xsettings (); >> #ifdef HAVE_X_SM >> syms_of_xsmfns (); >> @@ -1855,6 +1854,10 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem >> #endif /* HAVE_W32NOTIFY */ >> #endif /* WINDOWSNT */ >> +#ifdef HAVE_XWIDGETS >> + syms_of_xwidget (); >> +#endif /* HAVE_XWIDGETS */ >> + > > Why move the call to syms_of_xwidget? And why surround it with "#ifdef", since syms_of_xwidget is a no-op if HAVE_XWIDGETS is not defined? It's better to avoid #if when that's convenient. I vaguely remember that it once emitted an error when compiling... will try again. >> +#include <stdio.h> /* FIXME: Emacs error? message? instead of printf. */ > > Yes, we don't want printfs there. Can you give some candidates to use? I’m actually not that proficient in programming elisp functions in C... :-( >> +#if defined (USE_GTK) >> #include <webkit2/webkit2.h> >> #include <JavaScriptCore/JavaScript.h> >> +#elif defined (NS_IMPL_COCOA) >> +#include "nsxwidget.h" >> +#endif > > Indent preprocessor directives consistently. No parens needed in "defined X". "#ifdef X" is easier to read than "#if defined X". Will fix. >> +#if defined (USE_GTK) >> #if WEBKIT_CHECK_VERSION (2, 21, 1) && GNUC_PREREQ (4, 2, 0) >> # pragma GCC diagnostic ignored "-Wdeprecated-declarations" >> #endif >> +#endif > > Use just one "#if" rather than nested ones. That one was because the macro WEBKIT_CHECK_VERSION is only defined in GTK webkit... Last time I looked, I remember being puzzled because C preprocessor didn’t do proper short circuiting?̊̈ I’ll try that again. > I didn't look in detail at the .m or .el changes, but this is good enough for now. > ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH] Add xwidget webkit support for macOS Cocoa 2019-05-26 2:57 ` 조성빈 @ 2019-05-26 6:02 ` Paul Eggert 2019-05-28 15:07 ` [PATCH v2] " 조성빈 0 siblings, 1 reply; 19+ messages in thread From: Paul Eggert @ 2019-05-26 6:02 UTC (permalink / raw) To: 조성빈; +Cc: emacs-devel 조성빈 wrote: > Can you give some candidates to use? Perhaps the 'message' function? >>> +#if defined (USE_GTK) >>> #if WEBKIT_CHECK_VERSION (2, 21, 1) && GNUC_PREREQ (4, 2, 0) >>> # pragma GCC diagnostic ignored "-Wdeprecated-declarations" >>> #endif >>> +#endif >> >> Use just one "#if" rather than nested ones. > > That one was because the macro WEBKIT_CHECK_VERSION is only defined in GTK webkit... Last time I looked, I remember being puzzled because C preprocessor didn’t do proper short circuiting?̊̈ I’ll try that again. Oh, you're right. Please ignore my suggestion there. ^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v2] Add xwidget webkit support for macOS Cocoa 2019-05-26 6:02 ` Paul Eggert @ 2019-05-28 15:07 ` 조성빈 2019-06-02 6:14 ` 조성빈 2019-06-03 16:11 ` Alan Third 0 siblings, 2 replies; 19+ messages in thread From: 조성빈 @ 2019-05-28 15:07 UTC (permalink / raw) To: Paul Eggert; +Cc: emacs-devel --- 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) ^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH v2] Add xwidget webkit support for macOS Cocoa 2019-05-28 15:07 ` [PATCH v2] " 조성빈 @ 2019-06-02 6:14 ` 조성빈 2019-06-02 14:39 ` Eli Zaretskii 2019-06-03 16:11 ` Alan Third 1 sibling, 1 reply; 19+ messages in thread From: 조성빈 @ 2019-06-02 6:14 UTC (permalink / raw) To: Paul Eggert; +Cc: emacs-devel 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) ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2] Add xwidget webkit support for macOS Cocoa 2019-06-02 6:14 ` 조성빈 @ 2019-06-02 14:39 ` Eli Zaretskii 0 siblings, 0 replies; 19+ messages in thread From: Eli Zaretskii @ 2019-06-02 14:39 UTC (permalink / raw) To: 조성빈; +Cc: eggert, emacs-devel > From: 조성빈 <pcr910303@icloud.com> > Date: Sun, 2 Jun 2019 15:14:14 +0900 > Cc: emacs-devel@gnu.org > > 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 :-( Thank you for your patience, I think I need to hear opinions from people who develop for macOS and are familiar with the special aspects of that platform. ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2] Add xwidget webkit support for macOS Cocoa 2019-05-28 15:07 ` [PATCH v2] " 조성빈 2019-06-02 6:14 ` 조성빈 @ 2019-06-03 16:11 ` Alan Third 2019-06-03 17:25 ` Stefan Monnier 1 sibling, 1 reply; 19+ messages in thread From: Alan Third @ 2019-06-03 16:11 UTC (permalink / raw) To: 조성빈; +Cc: Paul Eggert, emacs-devel Even using mutt to write out the patch file I can’t get it to apply. I don’t see anything obviously wrong with it, though. Git complains it’s corrupt. Is it the quoted printable encoding? -- Alan Third ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2] Add xwidget webkit support for macOS Cocoa 2019-06-03 16:11 ` Alan Third @ 2019-06-03 17:25 ` Stefan Monnier 2019-06-03 23:36 ` Paul Eggert 0 siblings, 1 reply; 19+ messages in thread From: Stefan Monnier @ 2019-06-03 17:25 UTC (permalink / raw) To: emacs-devel > Even using mutt to write out the patch file I can’t get it to apply. I > don’t see anything obviously wrong with it, though. Git complains it’s > corrupt. Is it the quoted printable encoding? It seems that at least leading whitespace has been removed along the way, so it's indeed "corrupted". Stefan ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2] Add xwidget webkit support for macOS Cocoa 2019-06-03 17:25 ` Stefan Monnier @ 2019-06-03 23:36 ` Paul Eggert 2019-06-03 23:51 ` Sungbin Jo 0 siblings, 1 reply; 19+ messages in thread From: Paul Eggert @ 2019-06-03 23:36 UTC (permalink / raw) To: Stefan Monnier; +Cc: 조성빈, emacs-devel On 6/3/19 10:25 AM, Stefan Monnier wrote: > It seems that at least leading whitespace has been removed along the > way, so it's indeed "corrupted". Yes, it was corrupted by Apple Mail, a notorious corrupter of patches. The patch should be resent via 'git send-email' (as was done by version 1 of the patch), or via 'git imap-send', or via an attachment unless Apple Mail corrupts attachments too. ^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v2] Add xwidget webkit support for macOS Cocoa 2019-06-03 23:36 ` Paul Eggert @ 2019-06-03 23:51 ` Sungbin Jo 2019-06-04 2:40 ` Stefan Monnier 2019-06-04 16:22 ` [PATCH v2] " Alan Third 0 siblings, 2 replies; 19+ messages in thread From: Sungbin Jo @ 2019-06-03 23:51 UTC (permalink / raw) To: emacs-devel; +Cc: eggert, monnier, Sungbin Jo --- 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.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 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) ^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH v2] Add xwidget webkit support for macOS Cocoa 2019-06-03 23:51 ` Sungbin Jo @ 2019-06-04 2:40 ` Stefan Monnier 2019-06-04 8:33 ` Andreas Schwab ` (2 more replies) 2019-06-04 16:22 ` [PATCH v2] " Alan Third 1 sibling, 3 replies; 19+ messages in thread From: Stefan Monnier @ 2019-06-04 2:40 UTC (permalink / raw) To: Sungbin Jo; +Cc: eggert, emacs-devel I know nothing about programming on MacOS systems, nor about GUI widgets (and even less about "x"widgets), and only very little about GUI programming, so I only really looked at the Elisp part. IOW, nothing really important, but that's all I can offer. > 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)]) Under MacOS these aren't "gtk widgets" right? > (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)) I don't understand this comment. It seems to hint at a GC bug, maybe because the callback is not registered via `staticpro` or something like that? > +(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." You don't need a terminating \ to continue strings on the next line. But the first line of a docstring should be less than 80 chars long and should make sense on its own (sometimes we only show the first line of a docstring). E.g. the first line could be something like: "Clone current URL into a new widget place in new window below > + ;; 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)) Rather than rebind C-x 2 and C-x 3, I think you should use remapping, as in: (define-key map [remap split-window-below] 'xwidget-webkit-cx2) (define-key map [remap split-window-right] 'xwidget-webkit-cx3) Also the function names should probably refer to "below" and "right" rather than to "cx2" and "cx3": the names should describe what the function does rather than where it's expected to be bound. > + (cond ((null n) > + (format "window.scrollBy(0, %d);" > + (xwidget-window-inside-pixel-height (selected-window)))) > + (t (format "window.scrollBy(0, %d);" n))))) Aka (format "window.scrollBy(0, %d);" (or n (xwidget-window-inside-pixel-height (selected-window)))) > +(defvar xwidget-webkit-scroll-line-height 50 > + "Default line height in pixels for scroll xwidget webkit.") Specifying such a distance in pixels is probably not a good idea in these days where DPI can vary from 70 to 400. Maybe you should use a multiple of the default face's height or something like that? > @@ -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*"))) Why? Is this buffer expected to be displayed to the user? If not, then a leading space is preferable. > +;;; 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. Three (or more) semicolons means "section heading" (as used in ";;; Commentary:" at the beginning of the file), so please don't use it like you do here. > +;;; TODO: Response handling other than download. Same here. > + (if (vectorp arg) > + (funcall proc (seq-into arg 'list)) > + (funcall proc arg)))) Aka (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)) > + (defvar xwidget-webkit-enable-plugins nil > + "Enable plugins for xwidget webkit. > +If non-nil, plugins are enabled. Otherwise, disabled.")) Why is this defvar here? Shouldn't this be defined in the C code (presumably via DEFVAR_BOOL) since it's only used in the C code? > +(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 If not used, then why have it as an argument? > + (let ((save-name (read-file-name > + (format "Save '%s' file as: " mime-type) > + xwidget-webkit-download-dir file-name nil file-name))) Is `file-name` expected to be an absolute file name here? If not, then please use (expand-file-name file-name xwidget-webkit-download-dir) as DEFAULT argument. Also please don't provide the INITIAL argument since there's no reason this read-file-name should behave differently than all others. > + (if (file-directory-p save-name) > + (setq save-name (concat (file-name-as-directory save-name) file-name))) Don't use `concat`: use `expand-file-name`. This will save you from needing file-name-as-directory. Also, unless you're sure file-name is not absolute, you might like to use (file-name-nondirectory file-name) to avoid surprises. > +(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'.") Shouldn't this be a defcustom rather than a defvar? > (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)))))))) Bookmarks can be saved, so please make sure the old "page" attribute still works with the new code. > +(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) Rather than use `ignore` you can simply add an underscore at the beginning of the arg's names, as in (string &optional _bound _noerror _count) > + ;; Forward or backward > + (if (eq isearch-forward nil) > + (setq search-forward "false") > + (setq search-forward "true")) Aka (setq search-forward (if isearch-forward "true" "false")) BTW, even better, you can fold this directly into the `let`: (let* ((current-length (length string)) (search-forward (if isearch-forward "true" "false")) ... so you don't need the `setq` at all. > + (if (eq current-length xwidget-webkit-isearch-last-length) > + (setq search-repeat "true") > + (setq search-repeat "false")) I think you can guess what I'm about to say here (I already added the `*` at the end of the `let` above for that ;-) > + (if (eq isearch-forward nil) > + (goto-char (point-max)) > + (goto-char (point-min))) And here (goto-char (if isearch-forward (point-min) (point-max))) > - "javascript that finds the active element." > + "Javascript that finds the active element." Good, thanks. > (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." Better shorten it to fit with 80 (or even less) columns. E.g. "Insert string into the active field in the current webkit widget." This should normally take `string` as argument (and hence prompt from within the `interactive`). If that's not possible, please add a comment explaining why. > + ;; @javascript-callback > + (defun xwidget-webkit-insert-string-cb (field) Don't nest defuns within other defuns: it doesn't do what it intends to do. Just move the inner defun outside, so the syntax matches the behavior. Or just keep the `lambda` as it was before (and if that doesn't work, then we should likely fix that rather than work around the problem). > + 'xwidget-webkit-insert-string-cb))) Please use #' rather than ' when quoting a function. > ;; 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) Better avoid such immediate positions. E.g. use (let ((start (point))) (insert url) (put-text-property start (point) 'invisible t) > (setq xw (xwidget-insert 1 'webkit bufname And I think the `1` can then be changed to `start`. > +(defun xwidget-webkit-current-url-message-kill () > + "Message the current xwidget webkit URL and place it on the `kill-ring'." "Message" is not really a verb and when used as such (which English lets you do, admittedly), I tend to understand it as "send a message to the current xwidget ...". I think "Display" or "Show" will work better. > + (interactive) > + (message "url: %s" (kill-new (or (xwidget-webkit-current-url) "")))) I'd use a capitalized "URL:". > - (xwidget-webkit-get-selection (lambda (selection) (kill-new selection)))) > - > + (xwidget-webkit-get-selection #'kill-new)) Nice! As for the rest, I didn't notice any of the usual cosmetic problems (such as lack of space before open parens), so from a purely cosmetic point of view, it looked fine. Of course something that's still missing is a commit message. For nsxwidget.h and nsxwidget.m that's trivial (it need just say "New files" for them), but it should also include some author information since you're not the original author of (most of) this code, IIUC. Stefan ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2] Add xwidget webkit support for macOS Cocoa 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 2 siblings, 0 replies; 19+ messages in thread From: Andreas Schwab @ 2019-06-04 8:33 UTC (permalink / raw) To: Stefan Monnier; +Cc: eggert, Sungbin Jo, emacs-devel On Jun 03 2019, Stefan Monnier <monnier@iro.umontreal.ca> wrote: >> (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)) > > I don't understand this comment. It seems to hint at a GC bug, maybe > because the callback is not registered via `staticpro` or something like that? Could be referring to the situation before commit 8811c2408d. Andreas. -- Andreas Schwab, SUSE Labs, schwab@suse.de GPG Key fingerprint = 0196 BAD8 1CE9 1970 F4BE 1748 E4D4 88E3 0EEA B9D7 "And now for something completely different." ^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v2] Add xwidget webkit support for macOS Cocoa 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 2 siblings, 0 replies; 19+ messages in thread From: Sungbin Jo @ 2019-06-05 3:06 UTC (permalink / raw) To: emacs-devel; +Cc: schwab, alan, eggert, monnier, Sungbin Jo --- configure.ac | 34 +- lisp/xwidget.el | 304 +++++++++++---- 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, 1279 insertions(+), 99 deletions(-) create mode 100644 src/nsxwidget.h create mode 100644 src/nsxwidget.m diff --git a/configure.ac b/configure.ac index 0f1fd5d26e..4cfa3f4457 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 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. @@ -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..287f6048d9 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)) @@ -78,6 +79,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 +99,23 @@ 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 () + "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 +123,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,19 +172,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) + (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);")) + (cond ((null n) + (format "window.scrollBy(0, %d);" + (- (xwidget-window-inside-pixel-height (selected-window))))) + (t (format "window.scrollBy(0, %d);" (- n)))))) + +(defcustom xwidget-webkit-scroll-line-height 50 + "Default line height in pixels for scroll xwidget webkit." + :type 'integer + :group 'xwidget) + +(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 +241,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. @@ -203,12 +260,10 @@ xwidget-event-handler (xwidget-log "stuff happened to xwidget %S" last-input-event) (let* ((xwidget-event-type (nth 1 last-input-event)) - (xwidget (nth 2 last-input-event)) - ;;(xwidget-callback (xwidget-get xwidget 'callback)) - ;;TODO stopped working for some reason - ) + (xwidget (nth 2 last-input-event))) + ;;(xwidget-callback (xwidget-get xwidget 'callback)) + ;;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 +274,146 @@ 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 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 + :group 'xwidget)) + (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 + :group 'xwidget) + +(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) nil file-name))) + (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 + :group 'xwidget) + (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) + (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) + (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 +461,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 +471,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 +606,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 +661,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 " ") + ;; 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 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 +683,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 +714,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 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) ^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v3] Add xwidget webkit support for macOS Cocoa @ 2019-06-05 3:18 ` Sungbin Jo 2019-06-05 13:55 ` Stefan Monnier ` (2 more replies) 0 siblings, 3 replies; 19+ messages in thread From: Sungbin Jo @ 2019-06-05 3:18 UTC (permalink / raw) To: emacs-devel; +Cc: schwab, alan, eggert, monnier, Sungbin Jo --- configure.ac | 34 +- lisp/xwidget.el | 304 +++++++++++---- 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, 1279 insertions(+), 99 deletions(-) create mode 100644 src/nsxwidget.h create mode 100644 src/nsxwidget.m diff --git a/configure.ac b/configure.ac index 0f1fd5d26e..4cfa3f4457 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 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. @@ -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..8126b9c6de 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)) @@ -78,6 +79,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 +99,23 @@ 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 () + "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 +123,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,19 +172,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) + (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);")) + (cond ((null n) + (format "window.scrollBy(0, %d);" + (- (xwidget-window-inside-pixel-height (selected-window))))) + (t (format "window.scrollBy(0, %d);" (- n)))))) + +(defcustom xwidget-webkit-scroll-line-height 50 + "Default line height in pixels for scroll xwidget webkit." + :type 'integer + :group 'xwidget) + +(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 +241,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. @@ -203,12 +260,10 @@ xwidget-event-handler (xwidget-log "stuff happened to xwidget %S" last-input-event) (let* ((xwidget-event-type (nth 1 last-input-event)) - (xwidget (nth 2 last-input-event)) - ;;(xwidget-callback (xwidget-get xwidget 'callback)) - ;;TODO stopped working for some reason - ) + (xwidget (nth 2 last-input-event))) + ;;(xwidget-callback (xwidget-get xwidget 'callback)) + ;;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 +274,146 @@ 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 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 + :group 'xwidget)) + (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 + :group 'xwidget) + +(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) nil file-name))) + (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 + :group 'xwidget) + (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) + (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) + (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 +461,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 +471,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 +606,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 +661,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 " ") + ;; 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 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 +683,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 +714,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 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) ^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH v3] Add xwidget webkit support for macOS Cocoa 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 2 siblings, 0 replies; 19+ messages in thread From: Stefan Monnier @ 2019-06-05 13:55 UTC (permalink / raw) To: emacs-devel Thanks, looks good for the part I can review. See additional nitpicks in case nobody comes up with more substantial comments. > +(defun xwidget-webkit-split-below () > + "Clone current URL into a new widget place in new window below. Great. > +(defun xwidget-webkit-split-right () > + "Get the URL of current session, then browse to the URL \ > +in `split-window-right' with a new xwidget webkit session." You missed this one, tho. > + (format "window.scrollBy(0, %d);" > + (or n (xwidget-window-inside-pixel-height (selected-window)))))) Great. > - "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)))))) You missed this one, tho. > +(defcustom xwidget-webkit-scroll-line-height 50 > + "Default line height in pixels for scroll xwidget webkit." > + :type 'integer > + :group 'xwidget) Those `:group 'xwidget` are redundant since it defaults to the last `defgroup` in the buffer anyway. > + ;; 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 url mime-type file-name))) BTW, where does the "response-callback" name come from? According to the above code, this is used in the case of downloads, so why is it not called "download-callback"? > +(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) nil file-name))) Again, please don't pass `file-name` as INITIAL arg: leave it nil. > - `((page . ,(xwidget-webkit-current-url)) > - (handler . (lambda (bmk) (browse-url > - (bookmark-prop-get bmk 'page))))))) > - > + `((page . ,(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)))))))) I see you reverted to `page`, which is fine, but then the (bookmark-prop-get bmk 'filename) needs to be changed back to (bookmark-prop-get bmk 'page) as well, shouldn't it? Also, I don't really understand the new code: - how do we know that `browse-url` will use the xwidget browser rather than any other? I think we should either call `browse-url` and presume it can be *any* browser, or call xwidget-webkit-browse-url instead. - Why do we need the switch-to-buffer? Why not rely on (xwidget-webkit-)browse-url to Do The Right Thing? - switch-to-buffer fails miserably in configs such as mine (separate minibuffer frame and dedicated windows), so better avoid it in Elisp and use things like pop-to-buffer or pop-to-buffer-same-window. > + ;; 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 1 'webkit bufname Please use `start` rather than 1. Stefan ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v3] Add xwidget webkit support for macOS Cocoa 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 2 siblings, 0 replies; 19+ messages in thread From: Eli Zaretskii @ 2019-06-05 14:34 UTC (permalink / raw) To: Sungbin Jo; +Cc: alan, eggert, schwab, emacs-devel, monnier, pcr910303 > From: Sungbin Jo <pcr910303@icloud.com> > Date: Wed, 5 Jun 2019 12:18:23 +0900 > Cc: schwab@suse.de, alan@idiocy.org, eggert@cs.ucla.edu, > monnier@iro.umontreal.ca, Sungbin Jo <pcr910303@icloud.com> > > --- > configure.ac | 34 +- > lisp/xwidget.el | 304 +++++++++++---- > 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, 1279 insertions(+), 99 deletions(-) > create mode 100644 src/nsxwidget.h > create mode 100644 src/nsxwidget.m Thanks for working on this. I think this new feature (for macOS users) should be called out in NEWS. ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v3] Add xwidget webkit support for macOS Cocoa 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 2 siblings, 0 replies; 19+ messages in thread From: Alan Third @ 2019-06-23 22:38 UTC (permalink / raw) To: Sungbin Jo; +Cc: schwab, eggert, monnier, emacs-devel Hi, thanks for this. Sorry for the long delay. I’ve cleaned up a few places and have pushed it up to scratch/nsxwidget on savannah for anyone else who wants a look. I tried filling out the commit log, but I have to admit that the purpose of a few of the changes went entirely over my head. -- Alan Third ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2] Add xwidget webkit support for macOS Cocoa 2019-06-03 23:51 ` Sungbin Jo 2019-06-04 2:40 ` Stefan Monnier @ 2019-06-04 16:22 ` Alan Third 1 sibling, 0 replies; 19+ messages in thread From: Alan Third @ 2019-06-04 16:22 UTC (permalink / raw) To: Sungbin Jo; +Cc: emacs-devel On Tue, Jun 04, 2019 at 08:51:20AM +0900, Sungbin Jo wrote: > --- > 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 This applies fine now. Thanks. I’ve only had a brief test and it appears to work, but I did something wrong the first time I tried it. M‐x xwidget-webkit-browse-url RET idiocy.org RET it silently fails to load the page, then try the same but with https://idiocy.org/ and it starts spewing lots of this message into the terminal: 2019-06-04 17:15:16.041 Emacs[38758:2115790] xwHasFocus: Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" UserInfo={WKJavaScriptExceptionLineNumber=1, WKJavaScriptExceptionMessage=ReferenceError: Can't find variable: xwHasFocus, WKJavaScriptExceptionColumnNumber=11, WKJavaScriptExceptionSourceURL=undefined, NSLocalizedDescription=A JavaScript exception occurred} If I type in the full correct URL the first time it’s fine. -- Alan Third ^ permalink raw reply [flat|nested] 19+ messages in thread
end of thread, other threads:[~2019-06-23 22:38 UTC | newest] Thread overview: 19+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 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 ` 조성빈 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
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.