· 6 years ago · Sep 11, 2019, 01:28 PM
1From 4f5c264a412c4187d86f5e43218d6ffb7de1bf76 Mon Sep 17 00:00:00 2001
2From: Sungbin Jo <pcr910303@icloud.com>
3Date: Wed, 5 Jun 2019 12:18:23 +0900
4Subject: [PATCH] Add xwidget webkit support for macOS Cocoa
5
6* configure.ac: Allow xwidgets to build under Cocoa.
7* lisp/xwidget.el (xwidget-webkit-split-below):
8(xwidget-webkit-split-right): New functions.
9(xwidget-webkit-mode-map): Add new keybindings.
10(xwidget-webkit-scroll-up-line):
11(xwidget-webkit-scroll-down-line):
12(xwidget-webkit-scroll-up):
13(xwidget-webkit-scroll-down): Add ability to scroll specific amounts.
14(xwidget-webkit-scroll-line-height): New variable.
15(xwidget-webkit-scroll-bottom): Change clientHeight to scrollHeight.
16(xwidget-event-handler): Remove message.
17(xwidget-webkit-scroll-backward): Modify some window handling.
18(xwidget-webkit-mode): Add new functions.
19(xwidget-webkit-download-dir):
20(xwidget-webkit-save-as-file): Add support for downloading files.
21(xwidget-webkit-bookmark-jump-new-session): New function.
22(xwidget-webkit-bookmark-make-record): Modify bookmark loading.
23(isearch-mode-hook):
24(xwidget-webkit-search-js):
25(xwidget-webkit-isearch-last-length):
26(xwidget-webkit-search-fun-function): Add search within xwidget.
27(xwidget-webkit-insert-string): Use lists instead of vectors.
28(xwidget-window-inside-pixel-width):
29(xwidget-window-inside-pixel-height): New functions
30(xwidget-webkit-adjust-size-to-window): Use new functions.
31(xwidget-webkit-new-session): Modify session default.
32(xwidget-webkit-show-element):
33(xwidget-webkit-end-edit-textarea):
34(xwidget-webkit-back): Use functions instead of explicit JS scripts.
35(xwidget-webkit-current-url-message-kill):
36(xwidget-webkit-forward): New function.
37(xwidget-webkit-copy-selection-as-kill): Remove unneeded lambda.
38* nextstep/templates/Info.plist.in: Modify Emacs's system permissions.
39* src/Makefile.in (SOME_MACHINE_OBJECTS): Add nsxwidget.o.
40* src/emacs.c (main): Move call to syms_of_xwidget.
41* src/nsterm.m (ns_note_mouse_movement): Handle the dragging case
42differently.
43(ns_draw_glyph_string): Handle xwidget drawing.
44([EmacsView mouseMoved:]): Handle dragging case.
45* src/nsxwidget.h:
46* src/nsxwidget.m: New files.
47* src/xwidget.c (xwidget_init_view):
48(Fxwidget_webkit_zoom):
49(Fxwidget_resize):
50(Fxwidget_size_request):
51(Fdelete_xwidget_view):
52(kill_buffer_xwidgets):
53(Fmake_xwidget): Separate out GTK and NS code.
54(xwidget_hide_view): Replace printf with message.
55(xwidget_is_web_view):
56(Fxwidget_webkit_uri):
57(Fxwidget_webkit_title):
58(Fxwidget_webkit_goto_history):
59(store_xwidget_response_callback_event): New function.
60(x_draw_xwidget_glyph_string): NS xwidgets only support one view.
61Separate out GTK and NS code.
62(WEBKIT_FN_INIT): Use new function.
63(Fxwidget_webkit_execute_script): Something to do with detecting a
64function.
65(syms_of_xwidget): Define new functions.
66(xwidget_end_redisplay): Handle NS xwidget peculiarities.
67* src/xwidget.h: define new functions and add NS includes.
68---
69 configure.ac | 34 +-
70 lisp/xwidget.el | 304 +++++++++++----
71 nextstep/templates/Info.plist.in | 8 +
72 src/Makefile.in | 1 +
73 src/emacs.c | 2 +-
74 src/nsterm.m | 21 +-
75 src/nsxwidget.h | 80 ++++
76 src/nsxwidget.m | 611 +++++++++++++++++++++++++++++++
77 src/xwidget.c | 260 ++++++++++++-
78 src/xwidget.h | 50 ++-
79 10 files changed, 1275 insertions(+), 96 deletions(-)
80 create mode 100644 src/nsxwidget.h
81 create mode 100644 src/nsxwidget.m
82
83diff --git a/configure.ac b/configure.ac
84index 0507f58054a..47fd792c796 100644
85--- a/configure.ac
86+++ b/configure.ac
87@@ -484,7 +484,7 @@ otherwise for the first of 'inotify', 'kqueue' or 'gfile' that is usable.])
88 [with_file_notification=$with_features])
89
90 OPTION_DEFAULT_OFF([xwidgets],
91- [enable use of some gtk widgets in Emacs buffers (requires gtk3)])
92+ [enable use of some xwidgets in Emacs buffers (requires gtk3 or macOS Cocoa)])
93
94 ## For the times when you want to build Emacs but don't have
95 ## a suitable makeinfo, and can live without the manuals.
96@@ -2808,20 +2808,34 @@ fi
97
98
99 dnl Enable xwidgets if GTK3 and WebKitGTK+ are available.
100+dnl Enable xwidgets if macOS Cocoa and WebKit framework are available.
101 HAVE_XWIDGETS=no
102 XWIDGETS_OBJ=
103 if test "$with_xwidgets" != "no"; then
104- test "$USE_GTK_TOOLKIT" = "GTK3" && test "$window_system" != "none" ||
105- AC_MSG_ERROR([xwidgets requested but gtk3 not used.])
106+ if test "$USE_GTK_TOOLKIT" = "GTK3" && test "$window_system" != "none"; then
107+ WEBKIT_REQUIRED=2.12
108+ WEBKIT_MODULES="webkit2gtk-4.0 >= $WEBKIT_REQUIRED"
109+ EMACS_CHECK_MODULES([WEBKIT], [$WEBKIT_MODULES])
110+ HAVE_XWIDGETS=$HAVE_WEBKIT
111+ XWIDGETS_OBJ="xwidget.o"
112+ elif test "${NS_IMPL_COCOA}" = "yes"; then
113+ dnl FIXME: Check framework WebKit2
114+ dnl WEBKIT_REQUIRED=M.m.p
115+ WEBKIT_LIBS="-Wl,-framework -Wl,WebKit"
116+ WEBKIT_CFLAGS="-I/System/Library/Frameworks/WebKit.framework/Headers"
117+ HAVE_WEBKIT="yes"
118+ HAVE_XWIDGETS=$HAVE_WEBKIT
119+ XWIDGETS_OBJ="xwidget.o"
120+ NS_OBJC_OBJ="$NS_OBJC_OBJ nsxwidget.o"
121+ dnl Update NS_OBJC_OBJ with added nsxwidget.o
122+ AC_SUBST(NS_OBJC_OBJ)
123+ else
124+ AC_MSG_ERROR([xwidgets requested, it requires GTK3 as X window toolkit or macOS Cocoa as window system.])
125+ fi
126
127- WEBKIT_REQUIRED=2.12
128- WEBKIT_MODULES="webkit2gtk-4.0 >= $WEBKIT_REQUIRED"
129- EMACS_CHECK_MODULES([WEBKIT], [$WEBKIT_MODULES])
130- HAVE_XWIDGETS=$HAVE_WEBKIT
131 test $HAVE_XWIDGETS = yes ||
132- AC_MSG_ERROR([xwidgets requested but WebKitGTK+ not found.])
133+ AC_MSG_ERROR([xwidgets requested but WebKitGTK+ or WebKit framework not found.])
134
135- XWIDGETS_OBJ=xwidget.o
136 AC_DEFINE([HAVE_XWIDGETS], 1, [Define to 1 if you have xwidgets support.])
137 fi
138 AC_SUBST(XWIDGETS_OBJ)
139@@ -5695,7 +5709,7 @@ AS_ECHO([" Does Emacs use -lXaw3d? ${HAVE_XAW3D
140 Does Emacs directly use zlib? ${HAVE_ZLIB}
141 Does Emacs have dynamic modules support? ${HAVE_MODULES}
142 Does Emacs use toolkit scroll bars? ${USE_TOOLKIT_SCROLL_BARS}
143- Does Emacs support Xwidgets (requires gtk3)? ${HAVE_XWIDGETS}
144+ Does Emacs support Xwidgets? ${HAVE_XWIDGETS}
145 Does Emacs have threading support in lisp? ${threads_enabled}
146 Does Emacs support the portable dumper? ${with_pdumper}
147 Does Emacs support legacy unexec dumping? ${with_unexec}
148diff --git a/lisp/xwidget.el b/lisp/xwidget.el
149index 662a854ac3c..8126b9c6def 100644
150--- a/lisp/xwidget.el
151+++ b/lisp/xwidget.el
152@@ -39,9 +39,10 @@
153 (declare-function xwidget-buffer "xwidget.c" (xwidget))
154 (declare-function xwidget-size-request "xwidget.c" (xwidget))
155 (declare-function xwidget-resize "xwidget.c" (xwidget new-width new-height))
156-(declare-function xwidget-webkit-execute-script "xwidget.c"
157- (xwidget script &optional callback))
158+(declare-function xwidget-webkit-uri "xwidget.c" (xwidget))
159+(declare-function xwidget-webkit-title "xwidget.c" (xwidget))
160 (declare-function xwidget-webkit-goto-uri "xwidget.c" (xwidget uri))
161+(declare-function xwidget-webkit-goto-history "xwidget.c" (xwidget rel-pos))
162 (declare-function xwidget-webkit-zoom "xwidget.c" (xwidget factor))
163 (declare-function xwidget-plist "xwidget.c" (xwidget))
164 (declare-function set-xwidget-plist "xwidget.c" (xwidget plist))
165@@ -78,6 +79,8 @@ This returns the result of `make-xwidget'."
166 ;;; webkit support
167 (require 'browse-url)
168 (require 'image-mode);;for some image-mode alike functionality
169+(require 'seq)
170+(require 'url-handlers)
171
172 ;;;###autoload
173 (defun xwidget-webkit-browse-url (url &optional new-session)
174@@ -96,6 +99,23 @@ Interactively, URL defaults to the string looking like a url around point."
175 (xwidget-webkit-new-session url)
176 (xwidget-webkit-goto-url url))))
177
178+(defun xwidget-webkit-split-below ()
179+ "Clone current URL into a new widget place in new window below.
180+Get the URL of current session, then browse to the URL
181+in `split-window-below' with a new xwidget webkit session."
182+ (interactive)
183+ (let ((url (xwidget-webkit-current-url)))
184+ (with-selected-window (split-window-below)
185+ (xwidget-webkit-new-session url))))
186+
187+(defun xwidget-webkit-split-right ()
188+ "Get the URL of current session, then browse to the URL \
189+in `split-window-right' with a new xwidget webkit session."
190+ (interactive)
191+ (let ((url (xwidget-webkit-current-url)))
192+ (with-selected-window (split-window-right)
193+ (xwidget-webkit-new-session url))))
194+
195 ;;todo.
196 ;; - check that the webkit support is compiled in
197 (defvar xwidget-webkit-mode-map
198@@ -103,34 +123,42 @@ Interactively, URL defaults to the string looking like a url around point."
199 (define-key map "g" 'xwidget-webkit-browse-url)
200 (define-key map "a" 'xwidget-webkit-adjust-size-dispatch)
201 (define-key map "b" 'xwidget-webkit-back)
202+ (define-key map "f" 'xwidget-webkit-forward)
203 (define-key map "r" 'xwidget-webkit-reload)
204 (define-key map "t" (lambda () (interactive) (message "o"))) ;FIXME: ?!?
205 (define-key map "\C-m" 'xwidget-webkit-insert-string)
206- (define-key map "w" 'xwidget-webkit-current-url)
207+ (define-key map "w" 'xwidget-webkit-current-url-message-kill)
208 (define-key map "+" 'xwidget-webkit-zoom-in)
209 (define-key map "-" 'xwidget-webkit-zoom-out)
210
211 ;;similar to image mode bindings
212 (define-key map (kbd "SPC") 'xwidget-webkit-scroll-up)
213+ (define-key map (kbd "S-SPC") 'xwidget-webkit-scroll-down)
214 (define-key map (kbd "DEL") 'xwidget-webkit-scroll-down)
215
216- (define-key map [remap scroll-up] 'xwidget-webkit-scroll-up)
217+ (define-key map [remap scroll-up] 'xwidget-webkit-scroll-up-line)
218 (define-key map [remap scroll-up-command] 'xwidget-webkit-scroll-up)
219
220- (define-key map [remap scroll-down] 'xwidget-webkit-scroll-down)
221+ (define-key map [remap scroll-down] 'xwidget-webkit-scroll-down-line)
222 (define-key map [remap scroll-down-command] 'xwidget-webkit-scroll-down)
223
224 (define-key map [remap forward-char] 'xwidget-webkit-scroll-forward)
225 (define-key map [remap backward-char] 'xwidget-webkit-scroll-backward)
226 (define-key map [remap right-char] 'xwidget-webkit-scroll-forward)
227 (define-key map [remap left-char] 'xwidget-webkit-scroll-backward)
228- (define-key map [remap previous-line] 'xwidget-webkit-scroll-down)
229- (define-key map [remap next-line] 'xwidget-webkit-scroll-up)
230+ (define-key map [remap previous-line] 'xwidget-webkit-scroll-down-line)
231+ (define-key map [remap next-line] 'xwidget-webkit-scroll-up-line)
232
233 ;; (define-key map [remap move-beginning-of-line] 'image-bol)
234 ;; (define-key map [remap move-end-of-line] 'image-eol)
235 (define-key map [remap beginning-of-buffer] 'xwidget-webkit-scroll-top)
236 (define-key map [remap end-of-buffer] 'xwidget-webkit-scroll-bottom)
237+
238+ ;; For macOS xwidget webkit, we don't support multiple views for a
239+ ;; model, instead, create a new session and model behind the scene.
240+ (when (memq window-system '(mac ns))
241+ (define-key map [remap split-window-below] 'xwidget-webkit-split-below)
242+ (define-key map [remap split-window-right] 'xwidget-webkit-split-right))
243 map)
244 "Keymap for `xwidget-webkit-mode'.")
245
246@@ -144,19 +172,48 @@ Interactively, URL defaults to the string looking like a url around point."
247 (interactive)
248 (xwidget-webkit-zoom (xwidget-webkit-current-session) -0.1))
249
250-(defun xwidget-webkit-scroll-up ()
251- "Scroll webkit up."
252- (interactive)
253+(defun xwidget-webkit-scroll-up (&optional n)
254+ "Scroll webkit up by N pixels or window height pixels.
255+Stop if the bottom edge of the page is reached.
256+If N is omitted or nil, scroll up by window height pixels."
257+ (interactive "P")
258 (xwidget-webkit-execute-script
259 (xwidget-webkit-current-session)
260- "window.scrollBy(0, 50);"))
261-
262-(defun xwidget-webkit-scroll-down ()
263- "Scroll webkit down."
264- (interactive)
265+ (format "window.scrollBy(0, %d);"
266+ (or n (xwidget-window-inside-pixel-height (selected-window))))))
267+
268+(defun xwidget-webkit-scroll-down (&optional n)
269+ "Scroll webkit down by N pixels or window height pixels.
270+Stop if the top edge of the page is reached.
271+If N is omitted or nil, scroll down by window height pixels."
272+ (interactive "P")
273 (xwidget-webkit-execute-script
274 (xwidget-webkit-current-session)
275- "window.scrollBy(0, -50);"))
276+ (cond ((null n)
277+ (format "window.scrollBy(0, %d);"
278+ (- (xwidget-window-inside-pixel-height (selected-window)))))
279+ (t (format "window.scrollBy(0, %d);" (- n))))))
280+
281+(defcustom xwidget-webkit-scroll-line-height 50
282+ "Default line height in pixels for scroll xwidget webkit."
283+ :type 'integer
284+ :group 'xwidget)
285+
286+(defun xwidget-webkit-scroll-up-line (&optional n)
287+ "Scroll webkit up by N lines.
288+The height of line is `xwidget-webkit-scroll-line-height' pixels.
289+Stop if the bottom edge of the page is reached.
290+If N is omitted or nil, scroll up by one line."
291+ (interactive "p")
292+ (xwidget-webkit-scroll-up (* n xwidget-webkit-scroll-line-height)))
293+
294+(defun xwidget-webkit-scroll-down-line (&optional n)
295+ "Scroll webkit down by N lines.
296+The height of line is `xwidget-webkit-scroll-line-height' pixels.
297+Stop if the top edge of the page is reached.
298+If N is omitted or nil, scroll down by one line."
299+ (interactive "p")
300+ (xwidget-webkit-scroll-down (* n xwidget-webkit-scroll-line-height)))
301
302 (defun xwidget-webkit-scroll-forward ()
303 "Scroll webkit forwards."
304@@ -184,7 +241,7 @@ Interactively, URL defaults to the string looking like a url around point."
305 (interactive)
306 (xwidget-webkit-execute-script
307 (xwidget-webkit-current-session)
308- "window.scrollTo(pageXOffset, window.document.body.clientHeight);"))
309+ "window.scrollTo(pageXOffset, window.document.body.scrollHeight);"))
310
311 ;; The xwidget event needs to go into a higher level handler
312 ;; since the xwidget can generate an event even if it's offscreen.
313@@ -203,12 +260,10 @@ Interactively, URL defaults to the string looking like a url around point."
314 (xwidget-log "stuff happened to xwidget %S" last-input-event)
315 (let*
316 ((xwidget-event-type (nth 1 last-input-event))
317- (xwidget (nth 2 last-input-event))
318- ;;(xwidget-callback (xwidget-get xwidget 'callback))
319- ;;TODO stopped working for some reason
320- )
321+ (xwidget (nth 2 last-input-event)))
322+ ;;(xwidget-callback (xwidget-get xwidget 'callback))
323+ ;;TODO stopped working for some reason
324 ;;(funcall xwidget-callback xwidget xwidget-event-type)
325- (message "xw callback %s" xwidget)
326 (funcall 'xwidget-webkit-callback xwidget xwidget-event-type)))
327
328 (defun xwidget-webkit-callback (xwidget xwidget-event-type)
329@@ -219,43 +274,146 @@ XWIDGET instance, XWIDGET-EVENT-TYPE depends on the originating xwidget."
330 "error: callback called for xwidget with dead buffer")
331 (with-current-buffer (xwidget-buffer xwidget)
332 (cond ((eq xwidget-event-type 'load-changed)
333- (xwidget-webkit-execute-script
334- xwidget "document.title"
335- (lambda (title)
336- (xwidget-log "webkit finished loading: '%s'" title)
337- ;;TODO - check the native/internal scroll
338- ;;(xwidget-adjust-size-to-content xwidget)
339- (xwidget-webkit-adjust-size-to-window xwidget)
340- (rename-buffer (format "*xwidget webkit: %s *" title))))
341- (pop-to-buffer (current-buffer)))
342+ ;; We do not change selected window for the finish of loading a page.
343+ ;; And do not adjust webkit size to window here, the selected window
344+ ;; can be the mini-buffer window unwantedly.
345+ (let ((title (xwidget-webkit-title xwidget)))
346+ (xwidget-log "webkit finished loading: %s" title)
347+ (rename-buffer (format "*xwidget webkit: %s *" title) t)))
348 ((eq xwidget-event-type 'decide-policy)
349 (let ((strarg (nth 3 last-input-event)))
350 (if (string-match ".*#\\(.*\\)" strarg)
351 (xwidget-webkit-show-id-or-named-element
352 xwidget
353 (match-string 1 strarg)))))
354+ ;; TODO: Response handling other than download.
355+ ((eq xwidget-event-type 'response-callback)
356+ (let ((url (nth 3 last-input-event))
357+ (mime-type (nth 4 last-input-event))
358+ (file-name (nth 5 last-input-event)))
359+ (xwidget-webkit-save-as-file url mime-type file-name)))
360 ((eq xwidget-event-type 'javascript-callback)
361 (let ((proc (nth 3 last-input-event))
362 (arg (nth 4 last-input-event)))
363- (funcall proc arg)))
364+ ;; Some javascript return vector as result
365+ (funcall proc (if (vectorp arg) (seq-into arg 'list) arg))))
366 (t (xwidget-log "unhandled event:%s" xwidget-event-type))))))
367
368 (defvar bookmark-make-record-function)
369+(defvar isearch-search-fun-function)
370+(when (memq window-system '(mac ns))
371+ (defcustom xwidget-webkit-enable-plugins nil
372+ "Enable plugins for xwidget webkit.
373+If non-nil, plugins are enabled. Otherwise, disabled."
374+ :type 'boolean
375+ :group 'xwidget))
376+
377 (define-derived-mode xwidget-webkit-mode
378 special-mode "xwidget-webkit" "Xwidget webkit view mode."
379 (setq buffer-read-only t)
380+ (setq cursor-type nil)
381 (setq-local bookmark-make-record-function
382 #'xwidget-webkit-bookmark-make-record)
383+ (setq-local isearch-search-fun-function
384+ #'xwidget-webkit-search-fun-function)
385+ (setq-local isearch-lazy-highlight nil)
386 ;; Keep track of [vh]scroll when switching buffers
387 (image-mode-setup-winprops))
388
389+;;; Download, save as file.
390+
391+(defcustom xwidget-webkit-download-dir "~/Downloads/"
392+ "Directory where download file saved."
393+ :type 'string
394+ :group 'xwidget)
395+
396+(defun xwidget-webkit-save-as-file (url mime-type &optional file-name)
397+ "For XWIDGET webkit, save URL resource of MIME-TYPE as FILE-NAME."
398+ (let ((save-name (read-file-name
399+ (format "Save '%s' file as: " mime-type)
400+ xwidget-webkit-download-dir
401+ (expand-file-name
402+ file-name
403+ xwidget-webkit-download-dir) nil file-name)))
404+ (if (file-directory-p save-name)
405+ (setq save-name
406+ (expand-file-name (file-name-nondirectory file-name) save-name)))
407+ (setq xwidget-webkit-download-dir (file-name-directory save-name))
408+ (url-copy-file url save-name t)))
409+
410+;;; Bookmarks integration
411+
412+(defcustom xwidget-webkit-bookmark-jump-new-session nil
413+ "Control bookmark jump to use new session or not.
414+If non-nil, it will use a new session. Otherwise, it will use
415+`xwidget-webkit-last-session'. When you set this variable to
416+nil, consider further customization with
417+`xwidget-webkit-last-session-buffer'."
418+ :type 'boolean
419+ :group 'xwidget)
420+
421 (defun xwidget-webkit-bookmark-make-record ()
422 "Integrate Emacs bookmarks with the webkit xwidget."
423 (nconc (bookmark-make-record-default t t)
424- `((page . ,(xwidget-webkit-current-url))
425- (handler . (lambda (bmk) (browse-url
426- (bookmark-prop-get bmk 'page)))))))
427-
428+ `((page . ,(xwidget-webkit-current-url))
429+ (handler . (lambda (bmk)
430+ (browse-url
431+ (bookmark-prop-get bmk 'filename)
432+ xwidget-webkit-bookmark-jump-new-session)
433+ (switch-to-buffer
434+ (xwidget-buffer (xwidget-webkit-last-session))))))))
435+
436+;;; Search text in page
437+
438+;; Initialize last search text length variable when isearch starts
439+(defvar xwidget-webkit-isearch-last-length 0)
440+(add-hook 'isearch-mode-hook
441+ (lambda ()
442+ (setq xwidget-webkit-isearch-last-length 0)))
443+
444+;; This is minimal. Regex and incremental search will be nice
445+(defvar xwidget-webkit-search-js "
446+var xwSearchForward = %s;
447+var xwSearchRepeat = %s;
448+var xwSearchString = '%s';
449+if (window.getSelection() && !window.getSelection().isCollapsed) {
450+ if (xwSearchRepeat) {
451+ if (xwSearchForward)
452+ window.getSelection().collapseToEnd();
453+ else
454+ window.getSelection().collapseToStart();
455+ } else {
456+ if (xwSearchForward)
457+ window.getSelection().collapseToStart();
458+ else {
459+ var sel = window.getSelection();
460+ window.getSelection().collapse(sel.focusNode, sel.focusOffset + 1);
461+ }
462+ }
463+}
464+window.find(xwSearchString, false, !xwSearchForward, true, false, true);
465+")
466+
467+(defun xwidget-webkit-search-fun-function ()
468+ "Return the function which perform the search in xwidget webkit."
469+ (lambda (string &optional _bound _noerror _count)
470+ (let* ((current-length (length string))
471+ (search-forward (if isearch-forward "true" "false"))
472+ (search-repeat
473+ (if (eq current-length xwidget-webkit-isearch-last-length)
474+ "true"
475+ "false")))
476+ (setq xwidget-webkit-isearch-last-length current-length)
477+ (xwidget-webkit-execute-script
478+ (xwidget-webkit-current-session)
479+ (format xwidget-webkit-search-js
480+ search-forward
481+ search-repeat
482+ (regexp-quote string)))
483+ ;; Unconditionally avoid 'Failing I-search ...'
484+ (goto-char (if isearch-forward (point-min) (point-max))))))
485+
486+;;; xwidget webkit session
487
488 (defvar xwidget-webkit-last-session-buffer nil)
489
490@@ -303,7 +461,7 @@ function findactiveelement(doc){
491
492 "
493
494- "javascript that finds the active element."
495+ "Javascript that finds the active element."
496 ;; Yes it's ugly, because:
497 ;; - there is apparently no way to find the active frame other than recursion
498 ;; - the js "for each" construct misbehaved on the "frames" collection
499@@ -313,25 +471,29 @@ function findactiveelement(doc){
500 )
501
502 (defun xwidget-webkit-insert-string ()
503- "Prompt for a string and insert it in the active field in the
504-current webkit widget."
505+ "Insert string into the active field in the current webkit widget."
506 ;; Read out the string in the field first and provide for edit.
507 (interactive)
508+ ;; As the prompt needs to change based on the asynchronous execution results,
509+ ;; the function must handle the string itself.
510 (let ((xww (xwidget-webkit-current-session)))
511+
512 (xwidget-webkit-execute-script
513 xww
514 (concat xwidget-webkit-activeelement-js "
515 (function () {
516 var res = findactiveelement(document);
517- return [res.value, res.type];
518+ if (res)
519+ return [res.value, res.type];
520 })();")
521 (lambda (field)
522+ "Prompt a string for the FIELD and insert in the active input."
523 (let ((str (pcase field
524- (`[,val "text"]
525+ (`(,val "text")
526 (read-string "Text: " val))
527- (`[,val "password"]
528+ (`(,val "password")
529 (read-passwd "Password: " nil val))
530- (`[,val "textarea"]
531+ (`(,val "textarea")
532 (xwidget-webkit-begin-edit-textarea xww val)))))
533 (xwidget-webkit-execute-script
534 xww
535@@ -444,11 +606,23 @@ For example, use this to display an anchor."
536 (ignore-errors
537 (recenter-top-bottom)))
538
539+;; Utility functions, wanted in `window.el'
540+
541+(defun xwidget-window-inside-pixel-width (window)
542+ "Return Emacs WINDOW body width in pixel."
543+ (let ((edges (window-inside-pixel-edges window)))
544+ (- (nth 2 edges) (nth 0 edges))))
545+
546+(defun xwidget-window-inside-pixel-height (window)
547+ "Return Emacs WINDOW body height in pixel."
548+ (let ((edges (window-inside-pixel-edges window)))
549+ (- (nth 3 edges) (nth 1 edges))))
550+
551 (defun xwidget-webkit-adjust-size-to-window (xwidget &optional window)
552 "Adjust the size of the webkit XWIDGET to fit the WINDOW."
553 (xwidget-resize xwidget
554- (window-pixel-width window)
555- (window-pixel-height window)))
556+ (xwidget-window-inside-pixel-width window)
557+ (xwidget-window-inside-pixel-height window)))
558
559 (defun xwidget-webkit-adjust-size (w h)
560 "Manually set webkit size to width W, height H."
561@@ -487,10 +661,13 @@ For example, use this to display an anchor."
562 (get-buffer-create bufname)))
563 ;; The xwidget id is stored in a text property, so we need to have
564 ;; at least character in this buffer.
565- (insert " ")
566+ ;; Insert invisible url, good default for next `g' to browse url.
567+ (let ((start (point)))
568+ (insert url)
569+ (put-text-property start (+ start (length url)) 'invisible t))
570 (setq xw (xwidget-insert 1 'webkit bufname
571- (window-pixel-width)
572- (window-pixel-height)))
573+ (xwidget-window-inside-pixel-width (selected-window))
574+ (xwidget-window-inside-pixel-height (selected-window))))
575 (xwidget-put xw 'callback 'xwidget-webkit-callback)
576 (xwidget-webkit-mode)
577 (xwidget-webkit-goto-uri (xwidget-webkit-last-session) url)))
578@@ -506,23 +683,27 @@ For example, use this to display an anchor."
579 (defun xwidget-webkit-back ()
580 "Go back in history."
581 (interactive)
582- (xwidget-webkit-execute-script (xwidget-webkit-current-session)
583- "history.go(-1);"))
584+ (xwidget-webkit-goto-history (xwidget-webkit-current-session) -1))
585+
586+(defun xwidget-webkit-forward ()
587+ "Go forward in history."
588+ (interactive)
589+ (xwidget-webkit-goto-history (xwidget-webkit-current-session) 1))
590
591 (defun xwidget-webkit-reload ()
592- "Reload current url."
593+ "Reload current URL."
594 (interactive)
595- (xwidget-webkit-execute-script (xwidget-webkit-current-session)
596- "history.go(0);"))
597+ (xwidget-webkit-goto-history (xwidget-webkit-current-session) 0))
598
599 (defun xwidget-webkit-current-url ()
600- "Get the webkit url and place it on the kill-ring."
601+ "Get the current xwidget webkit URL."
602 (interactive)
603- (xwidget-webkit-execute-script
604- (xwidget-webkit-current-session)
605- "document.URL" (lambda (rv)
606- (let ((url (kill-new (or rv ""))))
607- (message "url: %s" url)))))
608+ (xwidget-webkit-uri (xwidget-webkit-current-session)))
609+
610+(defun xwidget-webkit-current-url-message-kill ()
611+ "Display the current xwidget webkit URL and place it on the `kill-ring'."
612+ (interactive)
613+ (message "URL: %s" (kill-new (or (xwidget-webkit-current-url) ""))))
614
615 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
616 (defun xwidget-webkit-get-selection (proc)
617@@ -533,10 +714,9 @@ For example, use this to display an anchor."
618 proc))
619
620 (defun xwidget-webkit-copy-selection-as-kill ()
621- "Get the webkit selection and put it on the kill-ring."
622+ "Get the webkit selection and put it on the `kill-ring'."
623 (interactive)
624- (xwidget-webkit-get-selection (lambda (selection) (kill-new selection))))
625-
626+ (xwidget-webkit-get-selection #'kill-new))
627
628 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
629 ;; Xwidget plist management (similar to the process plist functions)
630diff --git a/nextstep/templates/Info.plist.in b/nextstep/templates/Info.plist.in
631index c1e50a8409e..b1e38c9de00 100644
632--- a/nextstep/templates/Info.plist.in
633+++ b/nextstep/templates/Info.plist.in
634@@ -677,5 +677,13 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
635 <string>YES</string>
636 <key>NSAppleEventsUsageDescription</key>
637 <string>Emacs requires permission to send AppleEvents to other applications.</string>
638+ <!-- For xwidget webkit to browse remote url,
639+ but this set no restriction at all. Consult apple's documentation
640+ for detail information about `NSApplicationDefinedMask'. -->
641+ <key>NSAppTransportSecurity</key>
642+ <dict>
643+ <key>NSAllowsArbitraryLoads</key>
644+ <true/>
645+ </dict>
646 </dict>
647 </plist>
648diff --git a/src/Makefile.in b/src/Makefile.in
649index fd05a45df54..0af650244dc 100644
650--- a/src/Makefile.in
651+++ b/src/Makefile.in
652@@ -434,6 +434,7 @@ SOME_MACHINE_OBJECTS = dosfns.o msdos.o \
653 xterm.o xfns.o xmenu.o xselect.o xrdb.o xsmfns.o fringe.o image.o \
654 fontset.o dbusbind.o cygw32.o \
655 nsterm.o nsfns.o nsmenu.o nsselect.o nsimage.o nsfont.o macfont.o \
656+ nsxwidget.o \
657 w32.o w32console.o w32cygwinx.o w32fns.o w32heap.o w32inevt.o w32notify.o \
658 w32menu.o w32proc.o w32reg.o w32select.o w32term.o w32xfns.o \
659 w16select.o widget.o xfont.o ftfont.o xftfont.o ftxfont.o gtkutil.o \
660diff --git a/src/emacs.c b/src/emacs.c
661index fd46540ce22..e568724ac21 100644
662--- a/src/emacs.c
663+++ b/src/emacs.c
664@@ -1778,7 +1778,6 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
665 syms_of_xfns ();
666 syms_of_xmenu ();
667 syms_of_fontset ();
668- syms_of_xwidget ();
669 syms_of_xsettings ();
670 #ifdef HAVE_X_SM
671 syms_of_xsmfns ();
672@@ -1855,6 +1854,7 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
673 #endif /* HAVE_W32NOTIFY */
674 #endif /* WINDOWSNT */
675
676+ syms_of_xwidget ();
677 syms_of_threads ();
678 syms_of_profiler ();
679 syms_of_pdumper ();
680diff --git a/src/nsterm.m b/src/nsterm.m
681index 0ab03b46df1..2cd1052ccd3 100644
682--- a/src/nsterm.m
683+++ b/src/nsterm.m
684@@ -49,6 +49,7 @@ Updated by Christian Limpach (chris@nice.ch)
685 #include "nsterm.h"
686 #include "systime.h"
687 #include "character.h"
688+#include "xwidget.h"
689 #include "fontset.h"
690 #include "composite.h"
691 #include "ccl.h"
692@@ -2411,7 +2412,7 @@ so some key presses (TAB) are swallowed by the system. */
693 }
694
695 static int
696-ns_note_mouse_movement (struct frame *frame, CGFloat x, CGFloat y)
697+ns_note_mouse_movement (struct frame *frame, CGFloat x, CGFloat y, BOOL dragging)
698 /* ------------------------------------------------------------------------
699 Called by EmacsView on mouseMovement events. Passes on
700 to emacs mainstream code if we moved off of a rect of interest
701@@ -2420,17 +2421,23 @@ so some key presses (TAB) are swallowed by the system. */
702 {
703 struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (frame);
704 NSRect *r;
705+ BOOL force_update = NO;
706
707 // NSTRACE ("note_mouse_movement");
708
709 dpyinfo->last_mouse_motion_frame = frame;
710 r = &dpyinfo->last_mouse_glyph;
711
712+ /* If the last rect is too large (ex, xwidget webkit), update at
713+ every move, or resizing by dragging modeline or vertical split is
714+ very hard to make its way. */
715+ if (dragging && (r->size.width > 32 || r->size.height > 32))
716+ force_update = YES;
717+
718 /* Note, this doesn't get called for enter/leave, since we don't have a
719 position. Those are taken care of in the corresponding NSView methods. */
720
721- /* Has movement gone beyond last rect we were tracking? */
722- if (x < r->origin.x || x >= r->origin.x + r->size.width
723+ if (force_update || x < r->origin.x || x >= r->origin.x + r->size.width
724 || y < r->origin.y || y >= r->origin.y + r->size.height)
725 {
726 ns_update_begin (frame);
727@@ -4182,6 +4189,10 @@ overwriting cursor (usually when cursor on a tab). */
728 }
729 break;
730
731+ case XWIDGET_GLYPH:
732+ x_draw_xwidget_glyph_string (s);
733+ break;
734+
735 case STRETCH_GLYPH:
736 ns_dumpglyphs_stretch (s);
737 break;
738@@ -6835,6 +6846,7 @@ - (void)mouseMoved: (NSEvent *)e
739 struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (emacsframe);
740 Lisp_Object frame;
741 NSPoint pt;
742+ BOOL dragging;
743
744 NSTRACE_WHEN (NSTRACE_GROUP_EVENTS, "[EmacsView mouseMoved:]");
745
746@@ -6877,7 +6889,8 @@ - (void)mouseMoved: (NSEvent *)e
747 last_mouse_window = window;
748 }
749
750- if (!ns_note_mouse_movement (emacsframe, pt.x, pt.y))
751+ dragging = (e.type == NSEventTypeLeftMouseDragged);
752+ if (!ns_note_mouse_movement (emacsframe, pt.x, pt.y, dragging))
753 help_echo_string = previous_help_echo_string;
754
755 XSETFRAME (frame, emacsframe);
756diff --git a/src/nsxwidget.h b/src/nsxwidget.h
757new file mode 100644
758index 00000000000..6af5fe5a4d0
759--- /dev/null
760+++ b/src/nsxwidget.h
761@@ -0,0 +1,80 @@
762+/* Header for NS Cocoa part of xwidget and webkit widget.
763+
764+Copyright (C) 2011-2017 Free Software Foundation, Inc.
765+
766+This file is part of GNU Emacs.
767+
768+GNU Emacs is free software: you can redistribute it and/or modify
769+it under the terms of the GNU General Public License as published by
770+the Free Software Foundation, either version 3 of the License, or (at
771+your option) any later version.
772+
773+GNU Emacs is distributed in the hope that it will be useful,
774+but WITHOUT ANY WARRANTY; without even the implied warranty of
775+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
776+GNU General Public License for more details.
777+
778+You should have received a copy of the GNU General Public License
779+along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
780+
781+#ifndef NSXWIDGET_H_INCLUDED
782+#define NSXWIDGET_H_INCLUDED
783+
784+/* This file can be included from non-objc files through 'xwidget.h'. */
785+#ifdef __OBJC__
786+#import <AppKit/NSView.h>
787+#endif
788+
789+#include "dispextern.h"
790+#include "lisp.h"
791+#include "xwidget.h"
792+
793+/* Functions for xwidget webkit. */
794+
795+bool nsxwidget_is_web_view (struct xwidget *xw);
796+Lisp_Object nsxwidget_webkit_uri (struct xwidget *xw);
797+Lisp_Object nsxwidget_webkit_title (struct xwidget *xw);
798+void nsxwidget_webkit_goto_uri (struct xwidget *xw, const char *uri);
799+void nsxwidget_webkit_goto_history (struct xwidget *xw, int rel_pos);
800+void nsxwidget_webkit_zoom (struct xwidget *xw, double zoom_change);
801+void nsxwidget_webkit_execute_script (struct xwidget *xw, const char *script,
802+ Lisp_Object fun);
803+
804+/* Functions for xwidget model. */
805+
806+#ifdef __OBJC__
807+@interface XwWindow : NSView
808+@property struct xwidget *xw;
809+@end
810+#endif
811+
812+void nsxwidget_init (struct xwidget *xw);
813+void nsxwidget_kill (struct xwidget *xw);
814+void nsxwidget_resize (struct xwidget *xw);
815+Lisp_Object nsxwidget_get_size (struct xwidget *xw);
816+
817+/* Functions for xwidget view. */
818+
819+#ifdef __OBJC__
820+@interface XvWindow : NSView
821+@property struct xwidget *xw;
822+@property struct xwidget_view *xv;
823+@end
824+#endif
825+
826+void nsxwidget_init_view (struct xwidget_view *xv,
827+ struct xwidget *xww,
828+ struct glyph_string *s,
829+ int x, int y);
830+void nsxwidget_delete_view (struct xwidget_view *xv);
831+
832+void nsxwidget_show_view (struct xwidget_view *xv);
833+void nsxwidget_hide_view (struct xwidget_view *xv);
834+void nsxwidget_resize_view (struct xwidget_view *xv,
835+ int widget, int height);
836+
837+void nsxwidget_move_view (struct xwidget_view *xv, int x, int y);
838+void nsxwidget_move_widget_in_view (struct xwidget_view *xv, int x, int y);
839+void nsxwidget_set_needsdisplay (struct xwidget_view *xv);
840+
841+#endif /* NSXWIDGET_H_INCLUDED */
842diff --git a/src/nsxwidget.m b/src/nsxwidget.m
843new file mode 100644
844index 00000000000..0119087f471
845--- /dev/null
846+++ b/src/nsxwidget.m
847@@ -0,0 +1,611 @@
848+/* NS Cocoa part implementation of xwidget and webkit widget.
849+
850+Copyright (C) 1989, 1992-1994, 2005-2006, 2008-2017 Free Software
851+Foundation, Inc.
852+
853+This file is part of GNU Emacs.
854+
855+GNU Emacs is free software: you can redistribute it and/or modify
856+it under the terms of the GNU General Public License as published by
857+the Free Software Foundation, either version 3 of the License, or (at
858+your option) any later version.
859+
860+GNU Emacs is distributed in the hope that it will be useful,
861+but WITHOUT ANY WARRANTY; without even the implied warranty of
862+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
863+GNU General Public License for more details.
864+
865+You should have received a copy of the GNU General Public License
866+along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
867+
868+#include <config.h>
869+
870+#include "lisp.h"
871+#include "blockinput.h"
872+#include "dispextern.h"
873+#include "buffer.h"
874+#include "frame.h"
875+#include "nsterm.h"
876+#include "xwidget.h"
877+
878+#import <AppKit/AppKit.h>
879+#import <WebKit/WebKit.h>
880+
881+/* Thoughts on NS Cocoa xwidget and webkit2:
882+
883+ Webkit2 process architecture seems to be very hostile for offscreen
884+ rendering techniques, which is used by GTK xwiget implementation;
885+ Specifically NSView level view sharing / copying is not working.
886+
887+ *** So only one view can be associcated with a model. ***
888+
889+ With this decision, implementation is plain and can expect best out
890+ of webkit2's rationale. But process and session structures will
891+ diverge from GTK xwiget. Though, cosmetically similar usages can
892+ be presented and will be preferred, if agreeable.
893+
894+ For other widget types, OSR seems possible, but will not care for a
895+ while. */
896+
897+/* Xwidget webkit. */
898+
899+@interface XwWebView : WKWebView
900+<WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler>
901+@property struct xwidget *xw;
902+/* Map url to whether javascript is blocked by
903+ 'Content-Security-Policy' sandbox without allow-scripts. */
904+@property(retain) NSMutableDictionary *urlScriptBlocked;
905+@end
906+@implementation XwWebView : WKWebView
907+
908+- (id)initWithFrame:(CGRect)frame
909+ configuration:(WKWebViewConfiguration *)configuration
910+ xwidget:(struct xwidget *)xw
911+{
912+ /* Script controller to add script message handler and user script. */
913+ WKUserContentController *scriptor = [[WKUserContentController alloc] init];
914+ configuration.userContentController = scriptor;
915+
916+ /* Enable inspect element context menu item for debugging. */
917+ [configuration.preferences setValue:@YES
918+ forKey:@"developerExtrasEnabled"];
919+
920+ Lisp_Object enablePlugins =
921+ Fintern (build_string ("xwidget-webkit-enable-plugins"), Qnil);
922+ if (!EQ (Fsymbol_value (enablePlugins), Qnil))
923+ configuration.preferences.plugInsEnabled = YES;
924+
925+ self = [super initWithFrame:frame configuration:configuration];
926+ if (self)
927+ {
928+ self.xw = xw;
929+ self.urlScriptBlocked = [[NSMutableDictionary alloc] init];
930+ self.navigationDelegate = self;
931+ self.UIDelegate = self;
932+ self.customUserAgent =
933+ @"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6)"
934+ @" AppleWebKit/603.3.8 (KHTML, like Gecko)"
935+ @" Version/11.0.1 Safari/603.3.8";
936+ [scriptor addScriptMessageHandler:self name:@"keyDown"];
937+ [scriptor addUserScript:[[WKUserScript alloc]
938+ initWithSource:xwScript
939+ injectionTime:
940+ WKUserScriptInjectionTimeAtDocumentStart
941+ forMainFrameOnly:NO]];
942+ }
943+ return self;
944+}
945+
946+#if 0
947+/* Non ARC - just to check lifecycle. */
948+- (void)dealloc
949+{
950+ NSLog (@"XwWebView dealloc");
951+ [super dealloc];
952+}
953+#endif
954+
955+- (void)webView:(WKWebView *)webView
956+didFinishNavigation:(WKNavigation *)navigation
957+{
958+ if (EQ (Fbuffer_live_p (self.xw->buffer), Qt))
959+ store_xwidget_event_string (self.xw, "load-changed", "");
960+}
961+
962+- (void)webView:(WKWebView *)webView
963+decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
964+decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
965+{
966+ switch (navigationAction.navigationType) {
967+ case WKNavigationTypeLinkActivated:
968+ decisionHandler (WKNavigationActionPolicyAllow);
969+ break;
970+ default:
971+ // decisionHandler (WKNavigationActionPolicyCancel);
972+ decisionHandler (WKNavigationActionPolicyAllow);
973+ break;
974+ }
975+}
976+
977+- (void)webView:(WKWebView *)webView
978+decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
979+decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
980+{
981+ if (!navigationResponse.canShowMIMEType)
982+ {
983+ NSString *url = navigationResponse.response.URL.absoluteString;
984+ NSString *mimetype = navigationResponse.response.MIMEType;
985+ NSString *filename = navigationResponse.response.suggestedFilename;
986+ decisionHandler (WKNavigationResponsePolicyCancel);
987+ store_xwidget_response_callback_event (self.xw,
988+ url.UTF8String,
989+ mimetype.UTF8String,
990+ filename.UTF8String);
991+ return;
992+ }
993+ decisionHandler (WKNavigationResponsePolicyAllow);
994+
995+ self.urlScriptBlocked[navigationResponse.response.URL] =
996+ [NSNumber numberWithBool:NO];
997+ if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]])
998+ {
999+ NSDictionary *headers =
1000+ ((NSHTTPURLResponse *) navigationResponse.response).allHeaderFields;
1001+ NSString *value = headers[@"Content-Security-Policy"];
1002+ if (value)
1003+ {
1004+ /* TODO: Sloppy parsing of 'Content-Security-Policy' value. */
1005+ NSRange sandbox = [value rangeOfString:@"sandbox"];
1006+ if (sandbox.location != NSNotFound
1007+ && (sandbox.location == 0
1008+ || [value characterAtIndex:(sandbox.location - 1)] == ' '
1009+ || [value characterAtIndex:(sandbox.location - 1)] == ';'))
1010+ {
1011+ NSRange allowScripts = [value rangeOfString:@"allow-scripts"];
1012+ if (allowScripts.location == NSNotFound
1013+ || allowScripts.location < sandbox.location)
1014+ self.urlScriptBlocked[navigationResponse.response.URL] =
1015+ [NSNumber numberWithBool:YES];
1016+ }
1017+ }
1018+ }
1019+}
1020+
1021+/* No additional new webview or emacs window will be created
1022+ for <a ... target="_blank">. */
1023+- (WKWebView *)webView:(WKWebView *)webView
1024+createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
1025+ forNavigationAction:(WKNavigationAction *)navigationAction
1026+ windowFeatures:(WKWindowFeatures *)windowFeatures
1027+{
1028+ if (!navigationAction.targetFrame.isMainFrame)
1029+ [webView loadRequest:navigationAction.request];
1030+ return nil;
1031+}
1032+
1033+/* Open panel for file upload. */
1034+- (void)webView:(WKWebView *)webView
1035+runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters
1036+initiatedByFrame:(WKFrameInfo *)frame
1037+completionHandler:(void (^)(NSArray<NSURL *> *URLs))completionHandler
1038+{
1039+ NSOpenPanel *openPanel = [NSOpenPanel openPanel];
1040+ openPanel.canChooseFiles = YES;
1041+ openPanel.canChooseDirectories = NO;
1042+ openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection;
1043+ if ([openPanel runModal] == NSModalResponseOK)
1044+ completionHandler (openPanel.URLs);
1045+ else
1046+ completionHandler (nil);
1047+}
1048+
1049+/* By forwarding mouse events to emacs view (frame)
1050+ - Mouse click in webview selects the window contains the webview.
1051+ - Correct mouse hand/arrow/I-beam is displayed (TODO: not perfect yet).
1052+*/
1053+
1054+- (void)mouseDown:(NSEvent *)event
1055+{
1056+ [self.xw->xv->emacswindow mouseDown:event];
1057+ [super mouseDown:event];
1058+}
1059+
1060+- (void)mouseUp:(NSEvent *)event
1061+{
1062+ [self.xw->xv->emacswindow mouseUp:event];
1063+ [super mouseUp:event];
1064+}
1065+
1066+/* Basically we want keyboard events handled by emacs unless an input
1067+ element has focus. Especially, while incremental search, we set
1068+ emacs as first responder to avoid focus held in an input element
1069+ with matching text. */
1070+
1071+- (void)keyDown:(NSEvent *)event
1072+{
1073+ Lisp_Object var = Fintern (build_string ("isearch-mode"), Qnil);
1074+ Lisp_Object val = buffer_local_value (var, Fcurrent_buffer ());
1075+ if (!EQ (val, Qunbound) && !EQ (val, Qnil))
1076+ {
1077+ [self.window makeFirstResponder:self.xw->xv->emacswindow];
1078+ [self.xw->xv->emacswindow keyDown:event];
1079+ return;
1080+ }
1081+
1082+ /* Emacs handles keyboard events when javascript is blocked. */
1083+ if ([self.urlScriptBlocked[self.URL] boolValue])
1084+ {
1085+ [self.xw->xv->emacswindow keyDown:event];
1086+ return;
1087+ }
1088+
1089+ [self evaluateJavaScript:@"xwHasFocus()"
1090+ completionHandler:^(id result, NSError *error) {
1091+ if (error)
1092+ {
1093+ NSLog (@"xwHasFocus: %@", error);
1094+ [self.xw->xv->emacswindow keyDown:event];
1095+ }
1096+ else if (result)
1097+ {
1098+ NSNumber *hasFocus = result; /* __NSCFBoolean */
1099+ if (!hasFocus.boolValue)
1100+ [self.xw->xv->emacswindow keyDown:event];
1101+ else
1102+ [super keyDown:event];
1103+ }
1104+ }];
1105+}
1106+
1107+- (void)interpretKeyEvents:(NSArray<NSEvent *> *)eventArray
1108+{
1109+ /* We should do nothing and do not forward (default implementation
1110+ if we not override here) to let emacs collect key events and ask
1111+ interpretKeyEvents to its superclass. */
1112+}
1113+
1114+static NSString *xwScript;
1115++ (void)initialize
1116+{
1117+ /* Find out if an input element has focus.
1118+ Message to script message handler when 'C-g' key down. */
1119+ if (!xwScript)
1120+ xwScript =
1121+ @"function xwHasFocus() {"
1122+ @" var ae = document.activeElement;"
1123+ @" if (ae) {"
1124+ @" var name = ae.nodeName;"
1125+ @" return name == 'INPUT' || name == 'TEXTAREA';"
1126+ @" } else {"
1127+ @" return false;"
1128+ @" }"
1129+ @"}"
1130+ @"function xwKeyDown(event) {"
1131+ @" if (event.ctrlKey && event.key == 'g') {"
1132+ @" window.webkit.messageHandlers.keyDown.postMessage('C-g');"
1133+ @" }"
1134+ @"}"
1135+ @"document.addEventListener('keydown', xwKeyDown);"
1136+ ;
1137+}
1138+
1139+/* Confirming to WKScriptMessageHandler, listens concerning keyDown in
1140+ webkit. Currently 'C-g'. */
1141+- (void)userContentController:(WKUserContentController *)userContentController
1142+ didReceiveScriptMessage:(WKScriptMessage *)message
1143+{
1144+ if ([message.body isEqualToString:@"C-g"])
1145+ {
1146+ /* Just give up focus, no relay "C-g" to emacs, another "C-g"
1147+ follows will be handled by emacs. */
1148+ [self.window makeFirstResponder:self.xw->xv->emacswindow];
1149+ }
1150+}
1151+
1152+@end
1153+
1154+/* Xwidget webkit commands. */
1155+
1156+static Lisp_Object build_string_with_nsstr (NSString *nsstr);
1157+
1158+bool
1159+nsxwidget_is_web_view (struct xwidget *xw)
1160+{
1161+ return xw->xwWidget != NULL &&
1162+ [xw->xwWidget isKindOfClass:WKWebView.class];
1163+}
1164+
1165+Lisp_Object
1166+nsxwidget_webkit_uri (struct xwidget *xw)
1167+{
1168+ XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
1169+ return build_string_with_nsstr (xwWebView.URL.absoluteString);
1170+}
1171+
1172+Lisp_Object
1173+nsxwidget_webkit_title (struct xwidget *xw)
1174+{
1175+ XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
1176+ return build_string_with_nsstr (xwWebView.title);
1177+}
1178+
1179+/* @Note ATS - Need application transport security in 'Info.plist' or
1180+ remote pages will not loaded. */
1181+void
1182+nsxwidget_webkit_goto_uri (struct xwidget *xw, const char *uri)
1183+{
1184+ XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
1185+ NSString *urlString = [NSString stringWithUTF8String:uri];
1186+ NSURL *url = [NSURL URLWithString:urlString];
1187+ NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
1188+ [xwWebView loadRequest:urlRequest];
1189+}
1190+
1191+void
1192+nsxwidget_webkit_goto_history (struct xwidget *xw, int rel_pos)
1193+{
1194+ XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
1195+ switch (rel_pos) {
1196+ case -1: [xwWebView goBack]; break;
1197+ case 0: [xwWebView reload]; break;
1198+ case 1: [xwWebView goForward]; break;
1199+ }
1200+}
1201+
1202+void
1203+nsxwidget_webkit_zoom (struct xwidget *xw, double zoom_change)
1204+{
1205+ XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
1206+ xwWebView.magnification += zoom_change;
1207+ /* TODO: setMagnification:centeredAtPoint. */
1208+}
1209+
1210+/* Build lisp string */
1211+static Lisp_Object
1212+build_string_with_nsstr (NSString *nsstr)
1213+{
1214+ const char *utfstr = [nsstr UTF8String];
1215+ NSUInteger bytes = [nsstr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1216+ return make_string (utfstr, bytes);
1217+}
1218+
1219+/* Recursively convert an objc native type JavaScript value to a Lisp
1220+ value. Mostly copied from GTK xwidget 'webkit_js_to_lisp'. */
1221+static Lisp_Object
1222+js_to_lisp (id value)
1223+{
1224+ if (value == nil || [value isKindOfClass:NSNull.class])
1225+ return Qnil;
1226+ else if ([value isKindOfClass:NSString.class])
1227+ return build_string_with_nsstr ((NSString *) value);
1228+ else if ([value isKindOfClass:NSNumber.class])
1229+ {
1230+ NSNumber *nsnum = (NSNumber *) value;
1231+ char type = nsnum.objCType[0];
1232+ if (type == 'c') /* __NSCFBoolean has type character 'c'. */
1233+ return nsnum.boolValue? Qt : Qnil;
1234+ else
1235+ {
1236+ if (type == 'i' || type == 'l')
1237+ return make_int (nsnum.longValue);
1238+ else if (type == 'f' || type == 'd')
1239+ return make_float (nsnum.doubleValue);
1240+ /* else fall through. */
1241+ }
1242+ }
1243+ else if ([value isKindOfClass:NSArray.class])
1244+ {
1245+ NSArray *nsarr = (NSArray *) value;
1246+ EMACS_INT n = nsarr.count;
1247+ Lisp_Object obj;
1248+ struct Lisp_Vector *p = allocate_vector (n);
1249+
1250+ for (ptrdiff_t i = 0; i < n; ++i)
1251+ p->contents[i] = js_to_lisp ([nsarr objectAtIndex:i]);
1252+ XSETVECTOR (obj, p);
1253+ return obj;
1254+ }
1255+ else if ([value isKindOfClass:NSDictionary.class])
1256+ {
1257+ NSDictionary *nsdict = (NSDictionary *) value;
1258+ NSArray *keys = nsdict.allKeys;
1259+ ptrdiff_t n = keys.count;
1260+ Lisp_Object obj;
1261+ struct Lisp_Vector *p = allocate_vector (n);
1262+
1263+ for (ptrdiff_t i = 0; i < n; ++i)
1264+ {
1265+ NSString *prop_key = (NSString *) [keys objectAtIndex:i];
1266+ id prop_value = [nsdict valueForKey:prop_key];
1267+ p->contents[i] = Fcons (build_string_with_nsstr (prop_key),
1268+ js_to_lisp (prop_value));
1269+ }
1270+ XSETVECTOR (obj, p);
1271+ return obj;
1272+ }
1273+ NSLog (@"Unhandled type in javascript result");
1274+ return Qnil;
1275+}
1276+
1277+void
1278+nsxwidget_webkit_execute_script (struct xwidget *xw, const char *script,
1279+ Lisp_Object fun)
1280+{
1281+ XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
1282+ if ([xwWebView.urlScriptBlocked[xwWebView.URL] boolValue])
1283+ {
1284+ message ("Javascript is blocked by 'CSP: sandbox'.");
1285+ return;
1286+ }
1287+
1288+ NSString *javascriptString = [NSString stringWithUTF8String:script];
1289+ [xwWebView evaluateJavaScript:javascriptString
1290+ completionHandler:^(id result, NSError *error) {
1291+ if (error)
1292+ {
1293+ NSLog (@"evaluateJavaScript error : %@", error.localizedDescription);
1294+ NSLog (@"error script=%@", javascriptString);
1295+ }
1296+ else if (result && FUNCTIONP (fun))
1297+ {
1298+ // NSLog (@"result=%@, type=%@", result, [result class]);
1299+ Lisp_Object lisp_value = js_to_lisp (result);
1300+ store_xwidget_js_callback_event (xw, fun, lisp_value);
1301+ }
1302+ }];
1303+}
1304+
1305+/* Window containing an xwidget. */
1306+
1307+@implementation XwWindow
1308+- (BOOL)isFlipped { return YES; }
1309+@end
1310+
1311+/* Xwidget model, macOS Cocoa part. */
1312+
1313+void
1314+nsxwidget_init(struct xwidget *xw)
1315+{
1316+ block_input ();
1317+ NSRect rect = NSMakeRect (0, 0, xw->width, xw->height);
1318+ xw->xwWidget = [[XwWebView alloc]
1319+ initWithFrame:rect
1320+ configuration:[[WKWebViewConfiguration alloc] init]
1321+ xwidget:xw];
1322+ xw->xwWindow = [[XwWindow alloc]
1323+ initWithFrame:rect];
1324+ [xw->xwWindow addSubview:xw->xwWidget];
1325+ xw->xv = NULL; /* for 1 to 1 relationship of webkit2. */
1326+ unblock_input ();
1327+}
1328+
1329+void
1330+nsxwidget_kill (struct xwidget *xw)
1331+{
1332+ if (xw)
1333+ {
1334+ WKUserContentController *scriptor =
1335+ ((XwWebView *) xw->xwWidget).configuration.userContentController;
1336+ [scriptor removeAllUserScripts];
1337+ [scriptor removeScriptMessageHandlerForName:@"keyDown"];
1338+ [scriptor release];
1339+ if (xw->xv)
1340+ xw->xv->model = Qnil; /* Make sure related view stale. */
1341+
1342+ /* This stops playing audio when a xwidget-webkit buffer is
1343+ killed. I could not find other solution. */
1344+ nsxwidget_webkit_goto_uri (xw, "about:blank");
1345+
1346+ [((XwWebView *) xw->xwWidget).urlScriptBlocked release];
1347+ [xw->xwWidget removeFromSuperviewWithoutNeedingDisplay];
1348+ [xw->xwWidget release];
1349+ [xw->xwWindow removeFromSuperviewWithoutNeedingDisplay];
1350+ [xw->xwWindow release];
1351+ xw->xwWidget = nil;
1352+ }
1353+}
1354+
1355+void
1356+nsxwidget_resize (struct xwidget *xw)
1357+{
1358+ if (xw->xwWidget)
1359+ {
1360+ [xw->xwWindow setFrameSize:NSMakeSize(xw->width, xw->height)];
1361+ [xw->xwWidget setFrameSize:NSMakeSize(xw->width, xw->height)];
1362+ }
1363+}
1364+
1365+Lisp_Object
1366+nsxwidget_get_size (struct xwidget *xw)
1367+{
1368+ return list2i (xw->xwWidget.frame.size.width,
1369+ xw->xwWidget.frame.size.height);
1370+}
1371+
1372+/* Xwidget view, macOS Cocoa part. */
1373+
1374+@implementation XvWindow : NSView
1375+- (BOOL)isFlipped { return YES; }
1376+@end
1377+
1378+void
1379+nsxwidget_init_view (struct xwidget_view *xv,
1380+ struct xwidget *xw,
1381+ struct glyph_string *s,
1382+ int x, int y)
1383+{
1384+ /* 'x_draw_xwidget_glyph_string' will calculate correct position and
1385+ size of clip to draw in emacs buffer window. Thus, just begin at
1386+ origin with no crop. */
1387+ xv->x = x;
1388+ xv->y = y;
1389+ xv->clip_left = 0;
1390+ xv->clip_right = xw->width;
1391+ xv->clip_top = 0;
1392+ xv->clip_bottom = xw->height;
1393+
1394+ xv->xvWindow = [[XvWindow alloc]
1395+ initWithFrame:NSMakeRect (x, y, xw->width, xw->height)];
1396+ xv->xvWindow.xw = xw;
1397+ xv->xvWindow.xv = xv;
1398+
1399+ xw->xv = xv; /* For 1 to 1 relationship of webkit2. */
1400+ [xv->xvWindow addSubview:xw->xwWindow];
1401+
1402+ xv->emacswindow = FRAME_NS_VIEW (s->f);
1403+ [xv->emacswindow addSubview:xv->xvWindow];
1404+}
1405+
1406+void
1407+nsxwidget_delete_view (struct xwidget_view *xv)
1408+{
1409+ if (!EQ (xv->model, Qnil))
1410+ {
1411+ struct xwidget *xw = XXWIDGET (xv->model);
1412+ [xw->xwWindow removeFromSuperviewWithoutNeedingDisplay];
1413+ xw->xv = NULL; /* Now model has no view. */
1414+ }
1415+ [xv->xvWindow removeFromSuperviewWithoutNeedingDisplay];
1416+ [xv->xvWindow release];
1417+}
1418+
1419+void
1420+nsxwidget_show_view (struct xwidget_view *xv)
1421+{
1422+ xv->hidden = NO;
1423+ [xv->xvWindow setFrameOrigin:NSMakePoint(xv->x + xv->clip_left,
1424+ xv->y + xv->clip_top)];
1425+}
1426+
1427+void
1428+nsxwidget_hide_view (struct xwidget_view *xv)
1429+{
1430+ xv->hidden = YES;
1431+ [xv->xvWindow setFrameOrigin:NSMakePoint(10000, 10000)];
1432+}
1433+
1434+void
1435+nsxwidget_resize_view (struct xwidget_view *xv, int width, int height)
1436+{
1437+ [xv->xvWindow setFrameSize:NSMakeSize(width, height)];
1438+}
1439+
1440+void
1441+nsxwidget_move_view (struct xwidget_view *xv, int x, int y)
1442+{
1443+ [xv->xvWindow setFrameOrigin:NSMakePoint (x, y)];
1444+}
1445+
1446+/* Move model window in container (view window). */
1447+void
1448+nsxwidget_move_widget_in_view (struct xwidget_view *xv, int x, int y)
1449+{
1450+ struct xwidget *xww = xv->xvWindow.xw;
1451+ [xww->xwWindow setFrameOrigin:NSMakePoint (x, y)];
1452+}
1453+
1454+void
1455+nsxwidget_set_needsdisplay (struct xwidget_view *xv)
1456+{
1457+ xv->xvWindow.needsDisplay = YES;
1458+}
1459diff --git a/src/xwidget.c b/src/xwidget.c
1460index 2486a2d4da8..b8c0b5e220d 100644
1461--- a/src/xwidget.c
1462+++ b/src/xwidget.c
1463@@ -23,20 +23,30 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
1464
1465 #include "lisp.h"
1466 #include "blockinput.h"
1467+#include "dispextern.h"
1468 #include "frame.h"
1469 #include "keyboard.h"
1470 #include "gtkutil.h"
1471+#include "termhooks.h"
1472+#include "window.h"
1473
1474+/* Include xwidget bottom end headers. */
1475+#if defined (USE_GTK)
1476 #include <webkit2/webkit2.h>
1477 #include <JavaScriptCore/JavaScript.h>
1478+#elif defined (NS_IMPL_COCOA)
1479+#include "nsxwidget.h"
1480+#endif
1481
1482 /* Suppress GCC deprecation warnings starting in WebKitGTK+ 2.21.1 for
1483 webkit_javascript_result_get_global_context and
1484 webkit_javascript_result_get_value (Bug#33679).
1485 FIXME: Use the JavaScriptCore GLib API instead, and remove this hack. */
1486+#if defined (USE_GTK)
1487 #if WEBKIT_CHECK_VERSION (2, 21, 1) && GNUC_PREREQ (4, 2, 0)
1488 # pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1489 #endif
1490+#endif
1491
1492 static struct xwidget *
1493 allocate_xwidget (void)
1494@@ -55,6 +65,7 @@ allocate_xwidget_view (void)
1495
1496 static struct xwidget_view *xwidget_view_lookup (struct xwidget *,
1497 struct window *);
1498+#if defined (USE_GTK)
1499 static void webkit_view_load_changed_cb (WebKitWebView *,
1500 WebKitLoadEvent,
1501 gpointer);
1502@@ -68,6 +79,7 @@ webkit_decide_policy_cb (WebKitWebView *,
1503 WebKitPolicyDecision *,
1504 WebKitPolicyDecisionType,
1505 gpointer);
1506+#endif
1507
1508
1509 DEFUN ("make-xwidget",
1510@@ -85,8 +97,10 @@ Returns the newly constructed xwidget, or nil if construction fails. */)
1511 Lisp_Object title, Lisp_Object width, Lisp_Object height,
1512 Lisp_Object arguments, Lisp_Object buffer)
1513 {
1514+#if defined (USE_GTK)
1515 if (!xg_gtk_initialized)
1516 error ("make-xwidget: GTK has not been initialized");
1517+#endif
1518 CHECK_SYMBOL (type);
1519 CHECK_FIXNAT (width);
1520 CHECK_FIXNAT (height);
1521@@ -101,10 +115,11 @@ Returns the newly constructed xwidget, or nil if construction fails. */)
1522 xw->kill_without_query = false;
1523 XSETXWIDGET (val, xw);
1524 Vxwidget_list = Fcons (val, Vxwidget_list);
1525- xw->widgetwindow_osr = NULL;
1526- xw->widget_osr = NULL;
1527 xw->plist = Qnil;
1528
1529+#if defined (USE_GTK)
1530+ xw->widgetwindow_osr = NULL;
1531+ xw->widget_osr = NULL;
1532 if (EQ (xw->type, Qwebkit))
1533 {
1534 block_input ();
1535@@ -159,6 +174,9 @@ Returns the newly constructed xwidget, or nil if construction fails. */)
1536
1537 unblock_input ();
1538 }
1539+#elif defined (NS_IMPL_COCOA)
1540+ nsxwidget_init (xw);
1541+#endif
1542
1543 return val;
1544 }
1545@@ -194,6 +212,7 @@ xwidget_hidden (struct xwidget_view *xv)
1546 return xv->hidden;
1547 }
1548
1549+#if defined (USE_GTK)
1550 static void
1551 xwidget_show_view (struct xwidget_view *xv)
1552 {
1553@@ -227,13 +246,14 @@ offscreen_damage_event (GtkWidget *widget, GdkEvent *event,
1554 if (GTK_IS_WIDGET (xv_widget))
1555 gtk_widget_queue_draw (GTK_WIDGET (xv_widget));
1556 else
1557- printf ("Warning, offscreen_damage_event received invalid xv pointer:%p\n",
1558- xv_widget);
1559+ message ("Warning, offscreen_damage_event received invalid xv pointer:%p\n",
1560+ xv_widget);
1561
1562 return FALSE;
1563 }
1564+#endif /* USE_GTK */
1565
1566-static void
1567+void
1568 store_xwidget_event_string (struct xwidget *xw, const char *eventname,
1569 const char *eventstr)
1570 {
1571@@ -247,7 +267,27 @@ store_xwidget_event_string (struct xwidget *xw, const char *eventname,
1572 kbd_buffer_store_event (&event);
1573 }
1574
1575-static void
1576+void
1577+store_xwidget_response_callback_event (struct xwidget *xw,
1578+ const char *url,
1579+ const char *mimetype,
1580+ const char *filename)
1581+{
1582+ struct input_event event;
1583+ Lisp_Object xwl;
1584+ XSETXWIDGET (xwl, xw);
1585+ EVENT_INIT (event);
1586+ event.kind = XWIDGET_EVENT;
1587+ event.frame_or_window = Qnil;
1588+ event.arg = list5 (intern ("response-callback"),
1589+ xwl,
1590+ build_string (url),
1591+ build_string (mimetype),
1592+ build_string (filename));
1593+ kbd_buffer_store_event (&event);
1594+}
1595+
1596+void
1597 store_xwidget_js_callback_event (struct xwidget *xw,
1598 Lisp_Object proc,
1599 Lisp_Object argument)
1600@@ -263,6 +303,7 @@ store_xwidget_js_callback_event (struct xwidget *xw,
1601 }
1602
1603
1604+#if defined (USE_GTK)
1605 void
1606 webkit_view_load_changed_cb (WebKitWebView *webkitwebview,
1607 WebKitLoadEvent load_event,
1608@@ -520,6 +561,7 @@ xwidget_osr_event_set_embedder (GtkWidget *widget, GdkEvent *event,
1609 gtk_widget_get_window (xv->widget));
1610 return FALSE;
1611 }
1612+#endif /* USE_GTK */
1613
1614
1615 /* Initializes and does initial placement of an xwidget view on screen. */
1616@@ -529,8 +571,10 @@ xwidget_init_view (struct xwidget *xww,
1617 int x, int y)
1618 {
1619
1620+#if defined (USE_GTK)
1621 if (!xg_gtk_initialized)
1622 error ("xwidget_init_view: GTK has not been initialized");
1623+#endif
1624
1625 struct xwidget_view *xv = allocate_xwidget_view ();
1626 Lisp_Object val;
1627@@ -541,6 +585,7 @@ xwidget_init_view (struct xwidget *xww,
1628 XSETWINDOW (xv->w, s->w);
1629 XSETXWIDGET (xv->model, xww);
1630
1631+#if defined (USE_GTK)
1632 if (EQ (xww->type, Qwebkit))
1633 {
1634 xv->widget = gtk_drawing_area_new ();
1635@@ -598,6 +643,10 @@ xwidget_init_view (struct xwidget *xww,
1636 xv->x = x;
1637 xv->y = y;
1638 gtk_widget_show_all (xv->widgetwindow);
1639+#elif defined (NS_IMPL_COCOA)
1640+ nsxwidget_init_view (xv, xww, s, x, y);
1641+ nsxwidget_resize_view(xv, xww->width, xww->height);
1642+#endif
1643
1644 return xv;
1645 }
1646@@ -610,24 +659,59 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
1647 initialization. */
1648 struct xwidget *xww = s->xwidget;
1649 struct xwidget_view *xv = xwidget_view_lookup (xww, s->w);
1650+ int text_area_x, text_area_y, text_area_width, text_area_height;
1651 int clip_right;
1652 int clip_bottom;
1653 int clip_top;
1654 int clip_left;
1655
1656 int x = s->x;
1657- int y = s->y + (s->height / 2) - (xww->height / 2);
1658+ int y = s->y;
1659
1660 /* Do initialization here in the display loop because there is no
1661 other time to know things like window placement etc. Do not
1662 create a new view if we have found one that is usable. */
1663+#if defined (USE_GTK)
1664 if (!xv)
1665 xv = xwidget_init_view (xww, s, x, y);
1666-
1667- int text_area_x, text_area_y, text_area_width, text_area_height;
1668+#elif defined (NS_IMPL_COCOA)
1669+ if (!xv)
1670+ {
1671+ /* Enforce 1 to 1, model and view for macOS Cocoa webkit2. */
1672+ if (xww->xv)
1673+ {
1674+ if (xwidget_hidden (xww->xv))
1675+ {
1676+ Lisp_Object xvl;
1677+ XSETXWIDGET_VIEW (xvl, xww->xv);
1678+ Fdelete_xwidget_view (xvl);
1679+ }
1680+ else
1681+ {
1682+ message ("You can't share an xwidget (webkit2) among windows.");
1683+ return;
1684+ }
1685+ }
1686+ xv = xwidget_init_view (xww, s, x, y);
1687+ }
1688+#endif
1689
1690 window_box (s->w, TEXT_AREA, &text_area_x, &text_area_y,
1691 &text_area_width, &text_area_height);
1692+
1693+ /* Resize xwidget webkit if its container window size is changed in
1694+ some ways, for example, a buffer became hidden in small split
1695+ window, then it can appear front in merged whole window. */
1696+ if (EQ (xww->type, Qwebkit)
1697+ && (xww->width != text_area_width || xww->height != text_area_height))
1698+ {
1699+ Lisp_Object xwl;
1700+ XSETXWIDGET (xwl, xww);
1701+ Fxwidget_resize (xwl,
1702+ make_int (text_area_width),
1703+ make_int (text_area_height));
1704+ }
1705+
1706 clip_left = max (0, text_area_x - x);
1707 clip_right = max (clip_left,
1708 min (xww->width, text_area_x + text_area_width - x));
1709@@ -650,8 +734,14 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
1710
1711 /* Has it moved? */
1712 if (moved)
1713- gtk_fixed_move (GTK_FIXED (FRAME_GTK_WIDGET (s->f)),
1714- xv->widgetwindow, x + clip_left, y + clip_top);
1715+ {
1716+#if defined (USE_GTK)
1717+ gtk_fixed_move (GTK_FIXED (FRAME_GTK_WIDGET (s->f)),
1718+ xv->widgetwindow, x + clip_left, y + clip_top);
1719+#elif defined (NS_IMPL_COCOA)
1720+ nsxwidget_move_view (xv, x + clip_left, y + clip_top);
1721+#endif
1722+ }
1723
1724 /* Clip the widget window if some parts happen to be outside
1725 drawable area. An Emacs window is not a gtk window. A gtk window
1726@@ -662,10 +752,16 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
1727 || xv->clip_bottom != clip_bottom
1728 || xv->clip_top != clip_top || xv->clip_left != clip_left)
1729 {
1730+#if defined (USE_GTK)
1731 gtk_widget_set_size_request (xv->widgetwindow, clip_right - clip_left,
1732 clip_bottom - clip_top);
1733 gtk_fixed_move (GTK_FIXED (xv->widgetwindow), xv->widget, -clip_left,
1734 -clip_top);
1735+#elif defined (NS_IMPL_COCOA)
1736+ nsxwidget_resize_view (xv, clip_right - clip_left,
1737+ clip_bottom - clip_top);
1738+ nsxwidget_move_widget_in_view (xv, -clip_left, -clip_top);
1739+#endif
1740
1741 xv->clip_right = clip_right;
1742 xv->clip_bottom = clip_bottom;
1743@@ -679,21 +775,65 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
1744 xwidgets background. It's just a visual glitch though. */
1745 if (!xwidget_hidden (xv))
1746 {
1747+#if defined (USE_GTK)
1748 gtk_widget_queue_draw (xv->widgetwindow);
1749 gtk_widget_queue_draw (xv->widget);
1750+#elif defined (NS_IMPL_COCOA)
1751+ nsxwidget_set_needsdisplay (xv);
1752+#endif
1753 }
1754 }
1755
1756-/* Macro that checks WEBKIT_IS_WEB_VIEW (xw->widget_osr) first. */
1757+static bool
1758+xwidget_is_web_view (struct xwidget *xw)
1759+{
1760+#if defined (USE_GTK)
1761+ return xw->widget_osr != NULL && WEBKIT_IS_WEB_VIEW (xw->widget_osr);
1762+#elif defined (NS_IMPL_COCOA)
1763+ return nsxwidget_is_web_view (xw);
1764+#endif
1765+}
1766+
1767+/* Macro that checks xwidget hold webkit web view first. */
1768 #define WEBKIT_FN_INIT() \
1769 CHECK_XWIDGET (xwidget); \
1770 struct xwidget *xw = XXWIDGET (xwidget); \
1771- if (!xw->widget_osr || !WEBKIT_IS_WEB_VIEW (xw->widget_osr)) \
1772+ if (!xwidget_is_web_view (xw)) \
1773 { \
1774- printf ("ERROR xw->widget_osr does not hold a webkit instance\n"); \
1775+ message ("ERROR xwidget does not hold a webkit instance\n"); \
1776 return Qnil; \
1777 }
1778
1779+DEFUN ("xwidget-webkit-uri",
1780+ Fxwidget_webkit_uri, Sxwidget_webkit_uri,
1781+ 1, 1, 0,
1782+ doc: /* Get the current URL of XWIDGET webkit. */)
1783+ (Lisp_Object xwidget)
1784+{
1785+ WEBKIT_FN_INIT ();
1786+#if defined (USE_GTK)
1787+ WebKitWebView *wkwv = WEBKIT_WEB_VIEW (xw->widget_osr);
1788+ return build_string (webkit_web_view_get_uri (wkwv));
1789+#elif defined (NS_IMPL_COCOA)
1790+ return nsxwidget_webkit_uri (xw);
1791+#endif
1792+}
1793+
1794+DEFUN ("xwidget-webkit-title",
1795+ Fxwidget_webkit_title, Sxwidget_webkit_title,
1796+ 1, 1, 0,
1797+ doc: /* Get the current title of XWIDGET webkit. */)
1798+ (Lisp_Object xwidget)
1799+{
1800+ WEBKIT_FN_INIT ();
1801+#if defined (USE_GTK)
1802+ WebKitWebView *wkwv = WEBKIT_WEB_VIEW (xw->widget_osr);
1803+ return build_string (webkit_web_view_get_title (wkwv));
1804+#elif defined (NS_IMPL_COCOA)
1805+ return nsxwidget_webkit_title (xw);
1806+#endif
1807+}
1808+
1809 DEFUN ("xwidget-webkit-goto-uri",
1810 Fxwidget_webkit_goto_uri, Sxwidget_webkit_goto_uri,
1811 2, 2, 0,
1812@@ -703,7 +843,32 @@ DEFUN ("xwidget-webkit-goto-uri",
1813 WEBKIT_FN_INIT ();
1814 CHECK_STRING (uri);
1815 uri = ENCODE_FILE (uri);
1816+#if defined (USE_GTK)
1817 webkit_web_view_load_uri (WEBKIT_WEB_VIEW (xw->widget_osr), SSDATA (uri));
1818+#elif defined (NS_IMPL_COCOA)
1819+ nsxwidget_webkit_goto_uri (xw, SSDATA (uri));
1820+#endif
1821+ return Qnil;
1822+}
1823+
1824+DEFUN ("xwidget-webkit-goto-history",
1825+ Fxwidget_webkit_goto_history, Sxwidget_webkit_goto_history,
1826+ 2, 2, 0,
1827+ doc: /* Make the XWIDGET webkit load REL-POS (-1, 0, 1) page in browse history. */)
1828+ (Lisp_Object xwidget, Lisp_Object rel_pos)
1829+{
1830+ WEBKIT_FN_INIT ();
1831+ CHECK_RANGED_INTEGER (rel_pos, -1, 1); /* -1, 0, 1 */
1832+#if defined (USE_GTK)
1833+ WebKitWebView *wkwv = WEBKIT_WEB_VIEW (xw->widget_osr);
1834+ switch (XFIXNAT (rel_pos)) {
1835+ case -1: webkit_web_view_go_back (wkwv); break;
1836+ case 0: webkit_web_view_reload (wkwv); break;
1837+ case 1: webkit_web_view_go_forward (wkwv); break;
1838+ }
1839+#elif defined (NS_IMPL_COCOA)
1840+ nsxwidget_webkit_goto_history (xw, XFIXNAT (rel_pos));
1841+#endif
1842 return Qnil;
1843 }
1844
1845@@ -717,14 +882,19 @@ DEFUN ("xwidget-webkit-zoom",
1846 if (FLOATP (factor))
1847 {
1848 double zoom_change = XFLOAT_DATA (factor);
1849+#if defined (USE_GTK)
1850 webkit_web_view_set_zoom_level
1851 (WEBKIT_WEB_VIEW (xw->widget_osr),
1852 webkit_web_view_get_zoom_level
1853 (WEBKIT_WEB_VIEW (xw->widget_osr)) + zoom_change);
1854+#elif defined (NS_IMPL_COCOA)
1855+ nsxwidget_webkit_zoom (xw, zoom_change);
1856+#endif
1857 }
1858 return Qnil;
1859 }
1860
1861+#if defined(USE_GTK)
1862 /* Save script and fun in the script/callback save vector and return
1863 its index. */
1864 static ptrdiff_t
1865@@ -746,6 +916,7 @@ save_script_callback (struct xwidget *xw, Lisp_Object script, Lisp_Object fun)
1866 ASET (cbs, idx, Fcons (make_mint_ptr (xlispstrdup (script)), fun));
1867 return idx;
1868 }
1869+#endif
1870
1871 DEFUN ("xwidget-webkit-execute-script",
1872 Fxwidget_webkit_execute_script, Sxwidget_webkit_execute_script,
1873@@ -757,11 +928,15 @@ argument procedure FUN.*/)
1874 {
1875 WEBKIT_FN_INIT ();
1876 CHECK_STRING (script);
1877- if (!NILP (fun) && !FUNCTIONP (fun))
1878+ /* FUN will not be garbage collected if it is defined with `defun'
1879+ instead of `lambda'. If it is garbage collected even though it
1880+ is `defun', we can counter by pinning the FUN's symbol. */
1881+ if (!NILP (fun) && !SYMBOLP (fun) && !NILP (Ffboundp (fun)))
1882 wrong_type_argument (Qinvalid_function, fun);
1883
1884 script = ENCODE_SYSTEM (script);
1885
1886+#if defined (USE_GTK)
1887 /* Protect script and fun during GC. */
1888 intptr_t idx = save_script_callback (xw, script, fun);
1889
1890@@ -775,6 +950,9 @@ argument procedure FUN.*/)
1891 NULL, /* cancelable */
1892 webkit_javascript_finished_cb,
1893 (gpointer) idx);
1894+#elif defined (NS_IMPL_COCOA)
1895+ nsxwidget_webkit_execute_script (xw, SSDATA (script), fun);
1896+#endif
1897 return Qnil;
1898 }
1899
1900@@ -793,6 +971,7 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
1901 xw->height = h;
1902
1903 /* If there is an offscreen widget resize it first. */
1904+#if defined (USE_GTK)
1905 if (xw->widget_osr)
1906 {
1907 gtk_window_resize (GTK_WINDOW (xw->widgetwindow_osr), xw->width,
1908@@ -801,6 +980,9 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
1909 gtk_widget_set_size_request (GTK_WIDGET (xw->widget_osr), xw->width,
1910 xw->height);
1911 }
1912+#elif defined (NS_IMPL_COCOA)
1913+ nsxwidget_resize (xw);
1914+#endif
1915
1916 for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail); tail = XCDR (tail))
1917 {
1918@@ -808,8 +990,14 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
1919 {
1920 struct xwidget_view *xv = XXWIDGET_VIEW (XCAR (tail));
1921 if (XXWIDGET (xv->model) == xw)
1922+ {
1923+#if defined (USE_GTK)
1924 gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xw->width,
1925 xw->height);
1926+#elif defined (NS_IMPL_COCOA)
1927+ nsxwidget_resize_view(xv, xw->width, xw->height);
1928+#endif
1929+ }
1930 }
1931 }
1932
1933@@ -828,9 +1016,13 @@ Emacs allocated area accordingly. */)
1934 (Lisp_Object xwidget)
1935 {
1936 CHECK_XWIDGET (xwidget);
1937+#if defined (USE_GTK)
1938 GtkRequisition requisition;
1939 gtk_widget_size_request (XXWIDGET (xwidget)->widget_osr, &requisition);
1940 return list2i (requisition.width, requisition.height);
1941+#elif defined (NS_IMPL_COCOA)
1942+ return nsxwidget_get_size(XXWIDGET (xwidget));
1943+#endif
1944 }
1945
1946 DEFUN ("xwidgetp",
1947@@ -907,14 +1099,19 @@ DEFUN ("delete-xwidget-view",
1948 {
1949 CHECK_XWIDGET_VIEW (xwidget_view);
1950 struct xwidget_view *xv = XXWIDGET_VIEW (xwidget_view);
1951- gtk_widget_destroy (xv->widgetwindow);
1952 Vxwidget_view_list = Fdelq (xwidget_view, Vxwidget_view_list);
1953+#if defined (USE_GTK)
1954+ gtk_widget_destroy (xv->widgetwindow);
1955 /* xv->model still has signals pointing to the view. There can be
1956 several views. Find the matching signals and delete them all. */
1957 g_signal_handlers_disconnect_matched (XXWIDGET (xv->model)->widgetwindow_osr,
1958 G_SIGNAL_MATCH_DATA,
1959 0, 0, 0, 0,
1960 xv->widget);
1961+#elif defined (NS_IMPL_COCOA)
1962+ nsxwidget_delete_view (xv);
1963+#endif
1964+
1965 return Qnil;
1966 }
1967
1968@@ -1020,7 +1217,10 @@ syms_of_xwidget (void)
1969 defsubr (&Sxwidget_query_on_exit_flag);
1970 defsubr (&Sset_xwidget_query_on_exit_flag);
1971
1972+ defsubr (&Sxwidget_webkit_uri);
1973+ defsubr (&Sxwidget_webkit_title);
1974 defsubr (&Sxwidget_webkit_goto_uri);
1975+ defsubr (&Sxwidget_webkit_goto_history);
1976 defsubr (&Sxwidget_webkit_zoom);
1977 defsubr (&Sxwidget_webkit_execute_script);
1978 DEFSYM (Qwebkit, "webkit");
1979@@ -1191,11 +1391,19 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
1980 xwidget_end_redisplay (w->current_matrix); */
1981 struct xwidget_view *xv
1982 = xwidget_view_lookup (glyph->u.xwidget, w);
1983+#if defined (USE_GTK)
1984 /* FIXME: Is it safe to assume xwidget_view_lookup
1985 always succeeds here? If so, this comment can be removed.
1986 If not, the code probably needs fixing. */
1987 eassume (xv);
1988 xwidget_touch (xv);
1989+#elif defined (NS_IMPL_COCOA)
1990+ /* In NS xwidget, xv can be NULL for the second or
1991+ later views for a model, the result of 1 to 1
1992+ model view relation enforcement. */
1993+ if (xv)
1994+ xwidget_touch (xv);
1995+#endif
1996 }
1997 }
1998 }
1999@@ -1212,9 +1420,21 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
2000 if (XWINDOW (xv->w) == w)
2001 {
2002 if (xwidget_touched (xv))
2003- xwidget_show_view (xv);
2004+ {
2005+#if defined (USE_GTK)
2006+ xwidget_show_view (xv);
2007+#elif defined (NS_IMPL_COCOA)
2008+ nsxwidget_show_view (xv);
2009+#endif
2010+ }
2011 else
2012- xwidget_hide_view (xv);
2013+ {
2014+#if defined (USE_GTK)
2015+ xwidget_hide_view (xv);
2016+#elif defined (NS_IMPL_COCOA)
2017+ nsxwidget_hide_view (xv);
2018+#endif
2019+ }
2020 }
2021 }
2022 }
2023@@ -1233,6 +1453,7 @@ kill_buffer_xwidgets (Lisp_Object buffer)
2024 {
2025 CHECK_XWIDGET (xwidget);
2026 struct xwidget *xw = XXWIDGET (xwidget);
2027+#if defined (USE_GTK)
2028 if (xw->widget_osr && xw->widgetwindow_osr)
2029 {
2030 gtk_widget_destroy (xw->widget_osr);
2031@@ -1246,6 +1467,9 @@ kill_buffer_xwidgets (Lisp_Object buffer)
2032 xfree (xmint_pointer (XCAR (cb)));
2033 ASET (xw->script_callbacks, idx, Qnil);
2034 }
2035+#elif defined (NS_IMPL_COCOA)
2036+ nsxwidget_kill (xw);
2037+#endif
2038 }
2039 }
2040 }
2041diff --git a/src/xwidget.h b/src/xwidget.h
2042index 1b6368daabf..0042912fc67 100644
2043--- a/src/xwidget.h
2044+++ b/src/xwidget.h
2045@@ -29,7 +29,13 @@ struct xwidget_view;
2046 struct window;
2047
2048 #ifdef HAVE_XWIDGETS
2049-# include <gtk/gtk.h>
2050+
2051+#if defined (USE_GTK)
2052+#include <gtk/gtk.h>
2053+#elif defined (NS_IMPL_COCOA) && defined (__OBJC__)
2054+#import <AppKit/NSView.h>
2055+#import "nsxwidget.h"
2056+#endif
2057
2058 struct xwidget
2059 {
2060@@ -54,9 +60,25 @@ struct xwidget
2061 int height;
2062 int width;
2063
2064+#if defined (USE_GTK)
2065 /* For offscreen widgets, unused if not osr. */
2066 GtkWidget *widget_osr;
2067 GtkWidget *widgetwindow_osr;
2068+#elif defined (NS_IMPL_COCOA)
2069+# ifdef __OBJC__
2070+ /* For offscreen widgets, unused if not osr. */
2071+ NSView *xwWidget;
2072+ XwWindow *xwWindow;
2073+
2074+ /* Used only for xwidget types (such as webkit2) enforcing 1 to 1
2075+ relationship between model and view. */
2076+ struct xwidget_view *xv;
2077+# else
2078+ void *xwWidget;
2079+ void *xwWindow;
2080+ struct xwidget_view *xv;
2081+# endif
2082+#endif
2083
2084 /* Kill silently if Emacs is exited. */
2085 bool_bf kill_without_query : 1;
2086@@ -75,9 +97,20 @@ struct xwidget_view
2087 /* The "live" instance isn't drawn. */
2088 bool hidden;
2089
2090+#if defined (USE_GTK)
2091 GtkWidget *widget;
2092 GtkWidget *widgetwindow;
2093 GtkWidget *emacswindow;
2094+#elif defined (NS_IMPL_COCOA)
2095+# ifdef __OBJC__
2096+ XvWindow *xvWindow;
2097+ NSView *emacswindow;
2098+# else
2099+ void *xvWindow;
2100+ void *emacswindow;
2101+# endif
2102+#endif
2103+
2104 int x;
2105 int y;
2106 int clip_right;
2107@@ -116,6 +149,21 @@ void x_draw_xwidget_glyph_string (struct glyph_string *);
2108 struct xwidget *lookup_xwidget (Lisp_Object spec);
2109 void xwidget_end_redisplay (struct window *, struct glyph_matrix *);
2110 void kill_buffer_xwidgets (Lisp_Object);
2111+#ifdef NS_IMPL_COCOA
2112+/* Defined in 'xwidget.c'. */
2113+void store_xwidget_event_string (struct xwidget *xw,
2114+ const char *eventname,
2115+ const char *eventstr);
2116+
2117+void store_xwidget_response_callback_event (struct xwidget *xw,
2118+ const char *url,
2119+ const char *mimetype,
2120+ const char *filename);
2121+
2122+void store_xwidget_js_callback_event (struct xwidget *xw,
2123+ Lisp_Object proc,
2124+ Lisp_Object argument);
2125+#endif
2126 #else
2127 INLINE_HEADER_BEGIN
2128 INLINE void syms_of_xwidget (void) {}
2129The ultimate GitHub extension