]> git.eshelyaron.com Git - emacs.git/commitdiff
Add xwidget support for macOS
authorSungbin Jo <pcr910303@icloud.com>
Wed, 12 Aug 2020 10:12:34 +0000 (12:12 +0200)
committerLars Ingebrigtsen <larsi@gnus.org>
Wed, 12 Aug 2020 10:12:34 +0000 (12:12 +0200)
Co-authored-by: Jaesup Kwak <veshboo@gmail.com>
* configure.ac: Allow '--with-xwidgets' for "${NS_IMPL_COCOA}".
* etc/NEWS: Mention new feature.
* etc/TODO: Remove done TODO to implement xwidget in NeXTstep port.
* lisp/xwidget.el (xwidget-webkit-clone-and-split-below)
(xwidget-webkit-clone-and-split-right): New procedures.
(xwidget-webkit-callback): Remove call to
'xwidget-webkit-adjust-size-to-window' as adjusting xwidget size is
handled in 'x_draw_xwidget_glyph_string'.
(xwidget-webkit-enable-plugins): New variable.
* nextstep/templates/Info.plist.in: Add 'NSAppTransportSecurity'.
* src/Makefile.in: Add nsxwidget.o for compilation.
* src/emacs.c (main): Move conditional call to 'syms_of_xwidget'.
* src/nsterm.m (ns_draw_glyph_string): Add case for 'XWIDGET_GLYPH'.
(note_mouse_movement mouseMoved): Make it easy to resize window by
dragging mode-line or vertical separator adjacent to large glyph.
* src/nsxwidget.h src/nsxwidget.m: Newly added files, xwidget webkit
backend for macOS Cocoa.
* src/xwidget.c (Fmake_xwidget, xwidget_init_view)
(x_draw_xwidget_glyph_string, xwidget_is_web_view)
(Fxwidget_webkit_goto_uri, Fxwidget_webkit_zoom, Fxwidget_resize)
(Fxwidget_size_request, Fdelete_xwidget_view, xwidget_end_redisplay)
(kill_buffer_xwidgets): Add macOS Cocoa specific functions and code
with 'NS_IMPL_COCOA' and guard GTK specific functions and code with
'USE_GTK'.
(x_draw_xwidget_glyph_string): Handle adjusting xwidget size.
* src/xwidget.h (xwidget, xwidget_view): Add macOS Cocoa specific
fields with 'NS_IMPL_COCOA' and guard GTK specific fields with
USE_GTK.

configure.ac
etc/NEWS
lisp/xwidget.el
nextstep/templates/Info.plist.in
src/Makefile.in
src/emacs.c
src/nsterm.m
src/nsxwidget.h [new file with mode: 0644]
src/nsxwidget.m [new file with mode: 0644]
src/xwidget.c
src/xwidget.h

index c9aa076eb3bc9a13d1b8510f1f56633dc49fce33..7ce64f79ca40e1cc8df37dd258ccada7000eb8b0 100644 (file)
@@ -489,7 +489,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 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.
@@ -2754,20 +2754,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="-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)
@@ -5688,7 +5702,7 @@ 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}
   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}
index 5c44c970dde82d454088c746a6bcd6ac49200f43..39217897cc381581a9598fe6dd36d43c861aa69c 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -958,6 +958,18 @@ convert them to a list '(R G B)' of primary color values.
 \f
 * Changes in Emacs 28.1 on Non-Free Operating Systems
 
+---
+** On macOS, Xwidget is now supported.
+If Emacs was built with xwidget support, you can access the embedded
+webkit browser with 'M-x xwidget-webkit-browse-url'.  Viewing two
+instances of xwidget webkit is not supported.
+
+*** New functions for xwidget-webkit mode
+'xwidget-webkit-clone-and-split-below',
+'xwidget-webkit-clone-and-split-right'.
+
+*** New variable 'xwidget-webkit-enable-plugins'.
+
 +++
 ** On macOS, Emacs can now load dynamic modules with a ".dylib" suffix.
 'module-file-suffix' now has the value ".dylib" on macOS, but the
@@ -998,6 +1010,7 @@ 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 <https://www.gnu.org/licenses/>.
 
+
 \f
 Local variables:
 coding: utf-8
index a4c15a1e266c97d46c1eb4b8816bac2a25abd84f..f0940a92031a4a4db175f579feeb3928fc603df0 100644 (file)
@@ -99,6 +99,24 @@ 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-clone-and-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-clone-and-split-right ()
+  "Clone current URL into a new widget place in new window right.
+Get the URL of current session, then browse to the URL
+in `split-window-right' with a new xwidget webkit session."
+  (interactive)
+  (let ((url (xwidget-webkit-current-url)))
+    (with-selected-window (split-window-right)
+      (xwidget-webkit-new-session url))))
+
 ;;todo.
 ;; - check that the webkit support is compiled in
 (defvar xwidget-webkit-mode-map
@@ -222,9 +240,9 @@ XWIDGET instance, XWIDGET-EVENT-TYPE depends on the originating xwidget."
               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)
+                ;; Do not adjust webkit size to window here, the
+                ;; selected window can be the mini-buffer window
+                ;; unwantedly.
                 (rename-buffer (format "*xwidget webkit: %s *" title))))
              (pop-to-buffer (current-buffer)))
             ((eq xwidget-event-type 'decide-policy)
@@ -240,6 +258,11 @@ XWIDGET instance, XWIDGET-EVENT-TYPE depends on the originating xwidget."
             (t (xwidget-log "unhandled event:%s" xwidget-event-type))))))
 
 (defvar bookmark-make-record-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)
index f791ade7b972758a9f992dd43da87dbb6572a795..1f074b0457829f4ea67849a5960984dad523783c 100644 (file)
@@ -675,8 +675,16 @@ 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>
         <key>NSDesktopFolderUsageDescription</key>
         <string>Emacs requires permission to access the Desktop folder.</string>
         <key>NSDocumentsFolderUsageDescription</key>
index 7141f16ec25bda7fc17910fdee3d67269833fb87..c5fb2ea3ab2b915a33ac0669b682e93382a12212 100644 (file)
@@ -433,6 +433,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 gtkutil.o \
index d31fa2cb2875577c690707b817cd14393b811a2d..cb04de4aab621e3e16376c068e1e9e9a7102ed5f 100644 (file)
@@ -1860,7 +1860,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 ();
@@ -1937,6 +1936,7 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
 #endif /* HAVE_W32NOTIFY */
 #endif /* WINDOWSNT */
 
+      syms_of_xwidget ();
       syms_of_threads ();
       syms_of_profiler ();
       syms_of_pdumper ();
index 572b859a98212dadd8c398436e4691bd6d7647cb..9f5916d78edc0735a533aa83fc281c76b935890f 100644 (file)
@@ -49,6 +49,7 @@ GNUstep port and post-20 update by Adrian Robert (arobert@cogsci.ucsd.edu)
 #include "nsterm.h"
 #include "systime.h"
 #include "character.h"
+#include "xwidget.h"
 #include "fontset.h"
 #include "composite.h"
 #include "ccl.h"
@@ -2600,7 +2601,8 @@ frame_set_mouse_pixel_position (struct frame *f, int pix_x, int pix_y)
 }
 
 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
@@ -2609,17 +2611,24 @@ ns_note_mouse_movement (struct frame *frame, CGFloat x, CGFloat y)
 {
   struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (frame);
   NSRect *r;
+  BOOL force_update = NO;
 
   // NSTRACE ("note_mouse_movement");
 
   dpyinfo->last_mouse_motion_frame = frame;
   r = &dpyinfo->last_mouse_glyph;
 
+  /* If the last rect is too large (ex, xwidget webkit), update at
+     every move, or resizing by dragging modeline or vertical split is
+     very hard to make its way.  */
+  if (dragging && (r->size.width > 32 || r->size.height > 32))
+    force_update = YES;
+
   /* Note, this doesn't get called for enter/leave, since we don't have a
      position.  Those are taken care of in the corresponding NSView methods.  */
 
   /* Has movement gone beyond last rect we were tracking?  */
-  if (x < r->origin.x || x >= r->origin.x + r->size.width
+  if (force_update || x < r->origin.x || x >= r->origin.x + r->size.width
       || y < r->origin.y || y >= r->origin.y + r->size.height)
     {
       ns_update_begin (frame);
@@ -4368,6 +4377,10 @@ ns_draw_glyph_string (struct glyph_string *s)
       ns_unfocus (s->f);
       break;
 
+    case XWIDGET_GLYPH:
+      x_draw_xwidget_glyph_string (s);
+      break;
+
     case STRETCH_GLYPH:
       ns_dumpglyphs_stretch (s);
       break;
@@ -7065,6 +7078,7 @@ not_in_argv (NSString *arg)
   struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (emacsframe);
   Lisp_Object frame;
   NSPoint pt;
+  BOOL dragging;
 
   NSTRACE_WHEN (NSTRACE_GROUP_EVENTS, "[EmacsView mouseMoved:]");
 
@@ -7107,7 +7121,8 @@ not_in_argv (NSString *arg)
       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 (file)
index 0000000..7e2a3e0
--- /dev/null
@@ -0,0 +1,77 @@
+/* Header for NS Cocoa part of xwidget and webkit widget.
+
+Copyright (C) 2019 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef NSXWIDGET_H_INCLUDED
+#define NSXWIDGET_H_INCLUDED
+
+/* This file can be included from non-objc files through 'xwidget.h'.  */
+#ifdef __OBJC__
+#import <AppKit/NSView.h>
+#endif
+
+#include "dispextern.h"
+#include "lisp.h"
+#include "xwidget.h"
+
+/* Functions for xwidget webkit.  */
+
+bool nsxwidget_is_web_view (struct xwidget *xw);
+void nsxwidget_webkit_goto_uri (struct xwidget *xw, const char *uri);
+void nsxwidget_webkit_zoom (struct xwidget *xw, double zoom_change);
+void nsxwidget_webkit_execute_script (struct xwidget *xw, const char *script,
+                                      Lisp_Object fun);
+
+/* Functions for xwidget model.  */
+
+#ifdef __OBJC__
+@interface XwWindow : NSView
+@property struct xwidget *xw;
+@end
+#endif
+
+void nsxwidget_init (struct xwidget *xw);
+void nsxwidget_kill (struct xwidget *xw);
+void nsxwidget_resize (struct xwidget *xw);
+Lisp_Object nsxwidget_get_size (struct xwidget *xw);
+
+/* Functions for xwidget view.  */
+
+#ifdef __OBJC__
+@interface XvWindow : NSView
+@property struct xwidget *xw;
+@property struct xwidget_view *xv;
+@end
+#endif
+
+void nsxwidget_init_view (struct xwidget_view *xv,
+                          struct xwidget *xww,
+                          struct glyph_string *s,
+                          int x, int y);
+void nsxwidget_delete_view (struct xwidget_view *xv);
+
+void nsxwidget_show_view (struct xwidget_view *xv);
+void nsxwidget_hide_view (struct xwidget_view *xv);
+void nsxwidget_resize_view (struct xwidget_view *xv,
+                            int widget, int height);
+
+void nsxwidget_move_view (struct xwidget_view *xv, int x, int y);
+void nsxwidget_move_widget_in_view (struct xwidget_view *xv, int x, int y);
+void nsxwidget_set_needsdisplay (struct xwidget_view *xv);
+
+#endif /* NSXWIDGET_H_INCLUDED */
diff --git a/src/nsxwidget.m b/src/nsxwidget.m
new file mode 100644 (file)
index 0000000..c5376dd
--- /dev/null
@@ -0,0 +1,563 @@
+/* NS Cocoa part implementation of xwidget and webkit widget.
+
+Copyright (C) 2019 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;
+}
+
+- (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
+{
+  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];
+}
+/* @Note ATS - Need application transport security in 'Info.plist' or
+   remote pages will not loaded.  */
+void
+nsxwidget_webkit_goto_uri (struct xwidget *xw, const char *uri)
+{
+  XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
+  NSString *urlString = [NSString stringWithUTF8String:uri];
+  NSURL *url = [NSURL URLWithString:urlString];
+  NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
+  [xwWebView loadRequest:urlRequest];
+}
+
+void
+nsxwidget_webkit_zoom (struct xwidget *xw, double zoom_change)
+{
+  XwWebView *xwWebView = (XwWebView *) xw->xwWidget;
+  xwWebView.magnification += zoom_change;
+  /* TODO: setMagnification:centeredAtPoint.  */
+}
+
+/* Build lisp string */
+static Lisp_Object
+build_string_with_nsstr (NSString *nsstr)
+{
+  const char *utfstr = [nsstr UTF8String];
+  NSUInteger bytes = [nsstr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+  return make_string (utfstr, bytes);
+}
+
+/* Recursively convert an objc native type JavaScript value to a Lisp
+   value.  Mostly copied from GTK xwidget 'webkit_js_to_lisp'.  */
+static Lisp_Object
+js_to_lisp (id value)
+{
+  if (value == nil || [value isKindOfClass:NSNull.class])
+    return Qnil;
+  else if ([value isKindOfClass:NSString.class])
+    return build_string_with_nsstr ((NSString *) value);
+  else if ([value isKindOfClass:NSNumber.class])
+    {
+      NSNumber *nsnum = (NSNumber *) value;
+      char type = nsnum.objCType[0];
+      if (type == 'c') /* __NSCFBoolean has type character 'c'.  */
+        return nsnum.boolValue? Qt : Qnil;
+      else
+        {
+          if (type == 'i' || type == 'l')
+            return make_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;
+}
index 0347f1e6483772c25374403cf444ec236ec489c8..a3a3cd8d5bc47393ed8d1643b3e7ba6f78ecb5bf 100644 (file)
@@ -23,13 +23,21 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
 #include "lisp.h"
 #include "blockinput.h"
+#include "dispextern.h"
 #include "frame.h"
 #include "keyboard.h"
 #include "gtkutil.h"
 #include "sysstdio.h"
+#include "termhooks.h"
+#include "window.h"
 
+/* Include xwidget bottom end headers.  */
+#ifdef USE_GTK
 #include <webkit2/webkit2.h>
 #include <JavaScriptCore/JavaScript.h>
+#elif defined NS_IMPL_COCOA
+#include "nsxwidget.h"
+#endif
 
 static struct xwidget *
 allocate_xwidget (void)
@@ -48,6 +56,7 @@ allocate_xwidget_view (void)
 
 static struct xwidget_view *xwidget_view_lookup (struct xwidget *,
                                                 struct window *);
+#ifdef USE_GTK
 static void webkit_view_load_changed_cb (WebKitWebView *,
                                          WebKitLoadEvent,
                                          gpointer);
@@ -61,6 +70,7 @@ webkit_decide_policy_cb (WebKitWebView *,
                          WebKitPolicyDecision *,
                          WebKitPolicyDecisionType,
                          gpointer);
+#endif
 
 
 DEFUN ("make-xwidget",
@@ -78,8 +88,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)
 {
+#ifdef USE_GTK
   if (!xg_gtk_initialized)
     error ("make-xwidget: GTK has not been initialized");
+#endif
   CHECK_SYMBOL (type);
   CHECK_FIXNAT (width);
   CHECK_FIXNAT (height);
@@ -94,10 +106,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;
 
+#ifdef USE_GTK
+  xw->widgetwindow_osr = NULL;
+  xw->widget_osr = NULL;
   if (EQ (xw->type, Qwebkit))
     {
       block_input ();
@@ -152,6 +165,9 @@ Returns the newly constructed xwidget, or nil if construction fails.  */)
 
       unblock_input ();
     }
+#elif defined NS_IMPL_COCOA
+  nsxwidget_init (xw);
+#endif
 
   return val;
 }
@@ -187,6 +203,7 @@ xwidget_hidden (struct xwidget_view *xv)
   return xv->hidden;
 }
 
+#ifdef USE_GTK
 static void
 xwidget_show_view (struct xwidget_view *xv)
 {
@@ -220,13 +237,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)
 {
@@ -240,7 +258,7 @@ store_xwidget_event_string (struct xwidget *xw, const char *eventname,
   kbd_buffer_store_event (&event);
 }
 
-static void
+void
 store_xwidget_js_callback_event (struct xwidget *xw,
                                  Lisp_Object proc,
                                  Lisp_Object argument)
@@ -256,6 +274,7 @@ store_xwidget_js_callback_event (struct xwidget *xw,
 }
 
 
+#ifdef USE_GTK
 void
 webkit_view_load_changed_cb (WebKitWebView *webkitwebview,
                              WebKitLoadEvent load_event,
@@ -486,6 +505,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.  */
@@ -495,8 +515,10 @@ xwidget_init_view (struct xwidget *xww,
                    int x, int y)
 {
 
+#ifdef USE_GTK
   if (!xg_gtk_initialized)
     error ("xwidget_init_view: GTK has not been initialized");
+#endif
 
   struct xwidget_view *xv = allocate_xwidget_view ();
   Lisp_Object val;
@@ -507,6 +529,7 @@ xwidget_init_view (struct xwidget *xww,
   XSETWINDOW (xv->w, s->w);
   XSETXWIDGET (xv->model, xww);
 
+#ifdef USE_GTK
   if (EQ (xww->type, Qwebkit))
     {
       xv->widget = gtk_drawing_area_new ();
@@ -564,6 +587,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;
 }
@@ -576,6 +603,7 @@ 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;
@@ -587,13 +615,47 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
   /* Do initialization here in the display loop because there is no
      other time to know things like window placement etc.  Do not
      create a new view if we have found one that is usable.  */
+#ifdef USE_GTK
   if (!xv)
     xv = xwidget_init_view (xww, s, x, y);
-
-  int text_area_x, text_area_y, text_area_width, text_area_height;
+#elif defined NS_IMPL_COCOA
+  if (!xv)
+    {
+      /* Enforce 1 to 1, model and view for macOS Cocoa webkit2.  */
+      if (xww->xv)
+        {
+          if (xwidget_hidden (xww->xv))
+            {
+              Lisp_Object xvl;
+              XSETXWIDGET_VIEW (xvl, xww->xv);
+              Fdelete_xwidget_view (xvl);
+            }
+          else
+            {
+              message ("You can't share an xwidget (webkit2) among windows.");
+              return;
+            }
+        }
+      xv = xwidget_init_view (xww, s, x, y);
+    }
+#endif
 
   window_box (s->w, TEXT_AREA, &text_area_x, &text_area_y,
               &text_area_width, &text_area_height);
+
+  /* Resize xwidget webkit if its container window size is changed in
+     some ways, for example, a buffer became hidden in small split
+     window, then it can appear front in merged whole window.  */
+  if (EQ (xww->type, Qwebkit)
+      && (xww->width != text_area_width || xww->height != text_area_height))
+    {
+      Lisp_Object xwl;
+      XSETXWIDGET (xwl, xww);
+      Fxwidget_resize (xwl,
+                       make_int (text_area_width),
+                       make_int (text_area_height));
+    }
+
   clip_left = max (0, text_area_x - x);
   clip_right = max (clip_left,
                    min (xww->width, text_area_x + text_area_width - x));
@@ -616,8 +678,14 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
 
   /* Has it moved?  */
   if (moved)
-    gtk_fixed_move (GTK_FIXED (FRAME_GTK_WIDGET (s->f)),
-                   xv->widgetwindow, x + clip_left, y + clip_top);
+    {
+#ifdef USE_GTK
+      gtk_fixed_move (GTK_FIXED (FRAME_GTK_WIDGET (s->f)),
+                      xv->widgetwindow, x + clip_left, y + clip_top);
+#elif defined NS_IMPL_COCOA
+      nsxwidget_move_view (xv, x + clip_left, y + clip_top);
+#endif
+    }
 
   /* Clip the widget window if some parts happen to be outside
      drawable area.  An Emacs window is not a gtk window.  A gtk window
@@ -628,10 +696,16 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
       || xv->clip_bottom != clip_bottom
       || xv->clip_top != clip_top || xv->clip_left != clip_left)
     {
+#ifdef USE_GTK
       gtk_widget_set_size_request (xv->widgetwindow, clip_right - clip_left,
                                    clip_bottom - clip_top);
       gtk_fixed_move (GTK_FIXED (xv->widgetwindow), xv->widget, -clip_left,
                       -clip_top);
+#elif defined NS_IMPL_COCOA
+      nsxwidget_resize_view (xv, clip_right - clip_left,
+                             clip_bottom - clip_top);
+      nsxwidget_move_widget_in_view (xv, -clip_left, -clip_top);
+#endif
 
       xv->clip_right = clip_right;
       xv->clip_bottom = clip_bottom;
@@ -645,16 +719,30 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)
      xwidgets background.  It's just a visual glitch though.  */
   if (!xwidget_hidden (xv))
     {
+#ifdef USE_GTK
       gtk_widget_queue_draw (xv->widgetwindow);
       gtk_widget_queue_draw (xv->widget);
+#elif defined NS_IMPL_COCOA
+      nsxwidget_set_needsdisplay (xv);
+#endif
     }
 }
 
-/* Macro that checks WEBKIT_IS_WEB_VIEW (xw->widget_osr) first.  */
+static bool
+xwidget_is_web_view (struct xwidget *xw)
+{
+#ifdef USE_GTK
+  return xw->widget_osr != NULL && WEBKIT_IS_WEB_VIEW (xw->widget_osr);
+#elif defined NS_IMPL_COCOA
+  return nsxwidget_is_web_view (xw);
+#endif
+}
+
+/* Macro that checks xwidget hold webkit web view first.  */
 #define WEBKIT_FN_INIT()                                               \
   CHECK_XWIDGET (xwidget);                                             \
   struct xwidget *xw = XXWIDGET (xwidget);                             \
-  if (!xw->widget_osr || !WEBKIT_IS_WEB_VIEW (xw->widget_osr))         \
+  if (!xwidget_is_web_view (xw))                                       \
     {                                                                  \
       fputs ("ERROR xw->widget_osr does not hold a webkit instance\n", \
             stdout);                                                   \
@@ -670,7 +758,11 @@ DEFUN ("xwidget-webkit-goto-uri",
   WEBKIT_FN_INIT ();
   CHECK_STRING (uri);
   uri = ENCODE_FILE (uri);
+#ifdef USE_GTK
   webkit_web_view_load_uri (WEBKIT_WEB_VIEW (xw->widget_osr), SSDATA (uri));
+#elif defined NS_IMPL_COCOA
+  nsxwidget_webkit_goto_uri (xw, SSDATA (uri));
+#endif
   return Qnil;
 }
 
@@ -684,14 +776,19 @@ DEFUN ("xwidget-webkit-zoom",
   if (FLOATP (factor))
     {
       double zoom_change = XFLOAT_DATA (factor);
+#ifdef USE_GTK
       webkit_web_view_set_zoom_level
         (WEBKIT_WEB_VIEW (xw->widget_osr),
          webkit_web_view_get_zoom_level
          (WEBKIT_WEB_VIEW (xw->widget_osr)) + zoom_change);
+#elif defined NS_IMPL_COCOA
+      nsxwidget_webkit_zoom (xw, zoom_change);
+#endif
     }
   return Qnil;
 }
 
+#ifdef USE_GTK
 /* Save script and fun in the script/callback save vector and return
    its index.  */
 static ptrdiff_t
@@ -713,6 +810,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,
@@ -729,6 +827,7 @@ argument procedure FUN.*/)
 
   script = ENCODE_SYSTEM (script);
 
+#ifdef USE_GTK
   /* Protect script and fun during GC.  */
   intptr_t idx = save_script_callback (xw, script, fun);
 
@@ -742,6 +841,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;
 }
 
@@ -758,6 +860,7 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
   xw->height = h;
 
   /* If there is an offscreen widget resize it first.  */
+#ifdef USE_GTK
   if (xw->widget_osr)
     {
       gtk_window_resize (GTK_WINDOW (xw->widgetwindow_osr), xw->width,
@@ -766,6 +869,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))
     {
@@ -773,8 +879,14 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
         {
           struct xwidget_view *xv = XXWIDGET_VIEW (XCAR (tail));
           if (XXWIDGET (xv->model) == xw)
+            {
+#ifdef USE_GTK
               gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xw->width,
                                            xw->height);
+#elif defined NS_IMPL_COCOA
+              nsxwidget_resize_view(xv, xw->width, xw->height);
+#endif
+            }
         }
     }
 
@@ -793,9 +905,13 @@ Emacs allocated area accordingly.  */)
   (Lisp_Object xwidget)
 {
   CHECK_XWIDGET (xwidget);
+#ifdef USE_GTK
   GtkRequisition requisition;
   gtk_widget_size_request (XXWIDGET (xwidget)->widget_osr, &requisition);
   return list2i (requisition.width, requisition.height);
+#elif defined NS_IMPL_COCOA
+  return nsxwidget_get_size (XXWIDGET (xwidget));
+#endif
 }
 
 DEFUN ("xwidgetp",
@@ -872,14 +988,19 @@ DEFUN ("delete-xwidget-view",
 {
   CHECK_XWIDGET_VIEW (xwidget_view);
   struct xwidget_view *xv = XXWIDGET_VIEW (xwidget_view);
+#ifdef USE_GTK
   gtk_widget_destroy (xv->widgetwindow);
-  Vxwidget_view_list = Fdelq (xwidget_view, Vxwidget_view_list);
   /* 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
+
+  Vxwidget_view_list = Fdelq (xwidget_view, Vxwidget_view_list);
   return Qnil;
 }
 
@@ -1156,11 +1277,19 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
                     xwidget_end_redisplay (w->current_matrix);  */
                  struct xwidget_view *xv
                    = xwidget_view_lookup (glyph->u.xwidget, w);
+#ifdef USE_GTK
                  /* FIXME: Is it safe to assume xwidget_view_lookup
                     always succeeds here?  If so, this comment can be removed.
                     If not, the code probably needs fixing.  */
                  eassume (xv);
                  xwidget_touch (xv);
+#elif defined NS_IMPL_COCOA
+                  /* In NS xwidget, xv can be NULL for the second or
+                     later views for a model, the result of 1 to 1
+                     model view relation enforcement.  */
+                  if (xv)
+                    xwidget_touch (xv);
+#endif
                }
          }
     }
@@ -1177,9 +1306,21 @@ xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
           if (XWINDOW (xv->w) == w)
             {
               if (xwidget_touched (xv))
-                xwidget_show_view (xv);
+                {
+#ifdef USE_GTK
+                  xwidget_show_view (xv);
+#elif defined NS_IMPL_COCOA
+                  nsxwidget_show_view (xv);
+#endif
+                }
               else
-                xwidget_hide_view (xv);
+                {
+#ifdef USE_GTK
+                  xwidget_hide_view (xv);
+#elif defined NS_IMPL_COCOA
+                  nsxwidget_hide_view (xv);
+#endif
+                }
             }
         }
     }
@@ -1198,6 +1339,7 @@ kill_buffer_xwidgets (Lisp_Object buffer)
       {
         CHECK_XWIDGET (xwidget);
         struct xwidget *xw = XXWIDGET (xwidget);
+#ifdef USE_GTK
         if (xw->widget_osr && xw->widgetwindow_osr)
           {
             gtk_widget_destroy (xw->widget_osr);
@@ -1211,6 +1353,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
       }
     }
 }
index 99fa8bbd612ff644f07710a1036cdc7eed91a254..29f1153206f1ab4e576ffea0338cadb0707b5c42 100644 (file)
@@ -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;
@@ -116,6 +149,14 @@ void x_draw_xwidget_glyph_string (struct glyph_string *);
 struct xwidget *lookup_xwidget (Lisp_Object spec);
 void xwidget_end_redisplay (struct window *, struct glyph_matrix *);
 void kill_buffer_xwidgets (Lisp_Object);
+/* Defined in 'xwidget.c'.  */
+void store_xwidget_event_string (struct xwidget *xw,
+                                 const char *eventname,
+                                 const char *eventstr);
+
+void store_xwidget_js_callback_event (struct xwidget *xw,
+                                      Lisp_Object proc,
+                                      Lisp_Object argument);
 #else
 INLINE_HEADER_BEGIN
 INLINE void syms_of_xwidget (void) {}