/* WebkitGTK xwidget for embedding a graphical web browser.
Copyright (C) 2011-2024 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 . */
#include
#include "coding.h"
#include "xwidget.h"
#include "xwidget-webkit.h"
#include "lisp.h"
#include "blockinput.h"
#include "frame.h"
#include "process.h"
/* Include xwidget bottom end headers. */
#ifdef USE_GTK
#include
#include
#endif
#ifdef USE_GTK
static gboolean webkit_script_dialog_cb (WebKitWebView *, WebKitScriptDialog *,
gpointer);
static void webkit_view_load_changed_cb (WebKitWebView *,
WebKitLoadEvent,
gpointer);
static void webkit_javascript_finished_cb (GObject *,
GAsyncResult *,
gpointer);
static gboolean webkit_download_cb (WebKitWebContext *, WebKitDownload *, gpointer);
static GtkWidget *webkit_create_cb (WebKitWebView *, WebKitNavigationAction *, gpointer);
static gboolean
webkit_decide_policy_cb (WebKitWebView *,
WebKitPolicyDecision *,
WebKitPolicyDecisionType,
gpointer);
static gboolean run_file_chooser_cb (WebKitWebView *,
WebKitFileChooserRequest *,
gpointer);
#endif
#ifdef HAVE_PGTK
static void mouse_target_changed (WebKitWebView *, WebKitHitTestResult *, guint,
gpointer);
#endif
void
xwidget_webkit_make_widget (struct xwidget *xw, Lisp_Object related)
{
WebKitWebView *related_view;
WebKitSettings *settings;
WebKitWebContext *webkit_context = webkit_web_context_get_default ();
# if WEBKIT_CHECK_VERSION (2, 26, 0)
if (!webkit_web_context_get_sandbox_enabled (webkit_context))
webkit_web_context_set_sandbox_enabled (webkit_context, TRUE);
# endif
if (NILP (related)
|| !XWIDGETP (related)
|| !EQ (XXWIDGET (related)->type, Qwebkit))
{
WebKitWebContext *ctx = webkit_web_context_new ();
xw->widget_osr = webkit_web_view_new_with_context (ctx);
g_object_unref (ctx);
g_signal_connect (G_OBJECT (ctx),
"download-started",
G_CALLBACK (webkit_download_cb), xw);
webkit_web_view_load_uri (WEBKIT_WEB_VIEW (xw->widget_osr),
"about:blank");
/* webkitgtk uses GSubprocess which sets sigaction causing
Emacs to not catch SIGCHLD with its usual handle setup in
'catch_child_signal'. This resets the SIGCHLD sigaction. */
catch_child_signal ();
}
else
{
related_view = WEBKIT_WEB_VIEW (XXWIDGET (related)->widget_osr);
xw->widget_osr = webkit_web_view_new_with_related_view (related_view);
}
/* Enable the developer extras. */
settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (xw->widget_osr));
g_object_set (G_OBJECT (settings), "enable-developer-extras", TRUE, NULL);
g_object_set (G_OBJECT (settings), "enable-javascript",
(gboolean) (!xwidget_webkit_disable_javascript), NULL);
}
void
xwidget_webkit_connect_signals (struct xwidget *xw)
{
g_signal_connect (G_OBJECT (xw->widget_osr),
"load-changed",
G_CALLBACK (webkit_view_load_changed_cb), xw);
g_signal_connect (G_OBJECT (xw->widget_osr),
"decide-policy",
G_CALLBACK
(webkit_decide_policy_cb),
xw);
#ifdef HAVE_PGTK
g_signal_connect (G_OBJECT (xw->widget_osr),
"mouse-target-changed",
G_CALLBACK (mouse_target_changed),
xw);
#endif
g_signal_connect (G_OBJECT (xw->widget_osr),
"create",
G_CALLBACK (webkit_create_cb),
xw);
g_signal_connect (G_OBJECT (xw->widget_osr),
"script-dialog",
G_CALLBACK (webkit_script_dialog_cb),
NULL);
g_signal_connect (G_OBJECT (xw->widget_osr),
"run-file-chooser",
G_CALLBACK (run_file_chooser_cb),
NULL);
}
#ifdef HAVE_PGTK
Emacs_Cursor
xwidget_webkit_cursor_for_hit (guint result, struct frame *frame)
{
Emacs_Cursor cursor = FRAME_OUTPUT_DATA (frame)->nontext_cursor;
if ((result & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)
|| (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION)
|| (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT))
cursor = FRAME_X_OUTPUT (frame)->text_cursor;
if (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR)
cursor = FRAME_X_OUTPUT (frame)->vertical_drag_cursor;
if (result & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK)
cursor = FRAME_X_OUTPUT (frame)->hand_cursor;
return cursor;
}
static void
define_cursors (struct xwidget *xw, WebKitHitTestResult *res)
{
struct xwidget_view *xvw;
GdkWindow *wdesc;
xw->hit_result = webkit_hit_test_result_get_context (res);
for (Lisp_Object tem = internal_xwidget_view_list; CONSP (tem);
tem = XCDR (tem))
{
if (XWIDGET_VIEW_P (XCAR (tem)))
{
xvw = XXWIDGET_VIEW (XCAR (tem));
if (XXWIDGET (xvw->model) == xw)
{
xvw->cursor = xwidget_webkit_cursor_for_hit (xw->hit_result, xvw->frame);
if (gtk_widget_get_realized (xvw->widget))
{
wdesc = gtk_widget_get_window (xvw->widget);
gdk_window_set_cursor (wdesc, xvw->cursor);
}
}
}
}
}
static void
mouse_target_changed (WebKitWebView *webview,
WebKitHitTestResult *hitresult,
guint modifiers, gpointer xw)
{
define_cursors (xw, hitresult);
}
#endif
static gboolean
run_file_chooser_cb (WebKitWebView *webview,
WebKitFileChooserRequest *request,
gpointer user_data)
{
struct frame *f = SELECTED_FRAME ();
GtkFileChooserNative *chooser;
GtkFileFilter *filter;
bool select_multiple_p;
guint response;
GSList *filenames;
GSList *tem;
int i, len;
gchar **files;
/* Return TRUE to prevent WebKit from showing the default script
dialog in the offscreen window, which runs a nested main loop
Emacs can't respond to, and as such can't pass X events to. */
if (!FRAME_WINDOW_P (f))
return TRUE;
chooser = gtk_file_chooser_native_new ("Select file",
GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
GTK_FILE_CHOOSER_ACTION_OPEN, "Select",
"Cancel");
filter = webkit_file_chooser_request_get_mime_types_filter (request);
select_multiple_p = webkit_file_chooser_request_get_select_multiple (request);
gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (chooser),
select_multiple_p);
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
response = gtk_native_dialog_run (GTK_NATIVE_DIALOG (chooser));
if (response != GTK_RESPONSE_ACCEPT)
{
gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (chooser));
webkit_file_chooser_request_cancel (request);
return TRUE;
}
filenames = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (chooser));
len = g_slist_length (filenames);
files = alloca (sizeof *files * (len + 1));
for (tem = filenames, i = 0; tem; tem = tem->next, ++i)
files[i] = tem->data;
files[len] = NULL;
g_slist_free (filenames);
webkit_file_chooser_request_select_files (request, (const gchar **) files);
for (i = 0; i < len; ++i)
g_free (files[i]);
gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (chooser));
return TRUE;
}
#ifdef USE_GTK
static void
webkit_ready_to_show (WebKitWebView *new_view,
gpointer user_data)
{
Lisp_Object tem;
struct xwidget *xw;
struct xwidget *src;
src = find_xwidget_for_offscreen_window (GDK_WINDOW (user_data));
for (tem = internal_xwidget_list; CONSP (tem); tem = XCDR (tem))
{
if (XWIDGETP (XCAR (tem)))
{
xw = XXWIDGET (XCAR (tem));
if (EQ (xw->type, Qwebkit)
&& WEBKIT_WEB_VIEW (xw->widget_osr) == new_view)
{
/* The source widget was destroyed before we had a
chance to display the new widget. */
if (!src)
kill_xwidget (xw);
else
store_xwidget_display_event (xw, src);
}
}
}
}
static GtkWidget *
webkit_create_cb_1 (WebKitWebView *webview,
struct xwidget *xv)
{
Lisp_Object related;
Lisp_Object xwidget;
GtkWidget *widget;
XSETXWIDGET (related, xv);
xwidget = Fmake_xwidget (Qwebkit, Qnil, make_fixnum (0),
make_fixnum (0), Qnil,
build_string (" *detached xwidget buffer*"),
related);
if (NILP (xwidget))
return NULL;
widget = XXWIDGET (xwidget)->widget_osr;
g_signal_connect (G_OBJECT (widget), "ready-to-show",
G_CALLBACK (webkit_ready_to_show),
gtk_widget_get_window (xv->widgetwindow_osr));
return widget;
}
static GtkWidget *
webkit_create_cb (WebKitWebView *webview,
WebKitNavigationAction *nav_action,
gpointer user_data)
{
switch (webkit_navigation_action_get_navigation_type (nav_action))
{
case WEBKIT_NAVIGATION_TYPE_OTHER:
return webkit_create_cb_1 (webview, user_data);
case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD:
case WEBKIT_NAVIGATION_TYPE_RELOAD:
case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED:
case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED:
default:
return NULL;
}
}
void
webkit_view_load_changed_cb (WebKitWebView *webkitwebview,
WebKitLoadEvent load_event,
gpointer data)
{
struct xwidget *xw = g_object_get_data (G_OBJECT (webkitwebview),
XG_XWIDGET);
switch (load_event)
{
case WEBKIT_LOAD_FINISHED:
store_xwidget_event_string (xw, "load-changed", "load-finished");
break;
case WEBKIT_LOAD_STARTED:
store_xwidget_event_string (xw, "load-changed", "load-started");
break;
case WEBKIT_LOAD_REDIRECTED:
store_xwidget_event_string (xw, "load-changed", "load-redirected");
break;
case WEBKIT_LOAD_COMMITTED:
store_xwidget_event_string (xw, "load-changed", "load-committed");
break;
}
}
/* Recursively convert a JavaScript value to a Lisp value. */
static Lisp_Object
webkit_js_to_lisp (JSCValue *value)
{
if (jsc_value_is_string (value))
{
gchar *str_value = jsc_value_to_string (value);
Lisp_Object ret = build_string (str_value);
g_free (str_value);
return ret;
}
else if (jsc_value_is_boolean (value))
{
return (jsc_value_to_boolean (value)) ? Qt : Qnil;
}
else if (jsc_value_is_number (value))
{
return make_fixnum (jsc_value_to_int32 (value));
}
else if (jsc_value_is_array (value))
{
JSCValue *len = jsc_value_object_get_property (value, "length");
const gint32 dlen = jsc_value_to_int32 (len);
Lisp_Object obj;
if (! (0 <= dlen && dlen < G_MAXINT32))
memory_full (SIZE_MAX);
ptrdiff_t n = dlen;
struct Lisp_Vector *p = allocate_nil_vector (n);
for (ptrdiff_t i = 0; i < n; ++i)
{
p->contents[i] =
webkit_js_to_lisp (jsc_value_object_get_property_at_index (value, i));
}
XSETVECTOR (obj, p);
return obj;
}
else if (jsc_value_is_object (value))
{
char **properties_names = jsc_value_object_enumerate_properties (value);
guint n = g_strv_length (properties_names);
Lisp_Object obj;
if (PTRDIFF_MAX < n)
memory_full (n);
struct Lisp_Vector *p = allocate_nil_vector (n);
for (ptrdiff_t i = 0; i < n; ++i)
{
const char *name = properties_names[i];
JSCValue *property = jsc_value_object_get_property (value, name);
p->contents[i] =
Fcons (build_string (name), webkit_js_to_lisp (property));
}
g_strfreev (properties_names);
XSETVECTOR (obj, p);
return obj;
}
return Qnil;
}
static void
webkit_javascript_finished_cb (GObject *webview,
GAsyncResult *result,
gpointer arg)
{
GError *error = NULL;
struct xwidget *xw = g_object_get_data (G_OBJECT (webview), XG_XWIDGET);
ptrdiff_t script_idx = (intptr_t) arg;
Lisp_Object script_callback = AREF (xw->script_callbacks, script_idx);
ASET (xw->script_callbacks, script_idx, Qnil);
if (!NILP (script_callback))
xfree (xmint_pointer (XCAR (script_callback)));
WebKitJavascriptResult *js_result =
webkit_web_view_run_javascript_finish
(WEBKIT_WEB_VIEW (webview), result, &error);
if (!js_result)
{
if (error)
g_error_free (error);
return;
}
if (!NILP (script_callback) && !NILP (XCDR (script_callback)))
{
JSCValue *value = webkit_javascript_result_get_js_value (js_result);
Lisp_Object lisp_value = webkit_js_to_lisp (value);
/* Register an xwidget event here, which then runs the callback.
This ensures that the callback runs in sync with the Emacs
event loop. */
store_xwidget_js_callback_event (xw, XCDR (script_callback), lisp_value);
}
webkit_javascript_result_unref (js_result);
}
gboolean
webkit_download_cb (WebKitWebContext *webkitwebcontext,
WebKitDownload *arg1,
gpointer data)
{
WebKitWebView *view = webkit_download_get_web_view(arg1);
WebKitURIRequest *request = webkit_download_get_request(arg1);
struct xwidget *xw = g_object_get_data (G_OBJECT (view),
XG_XWIDGET);
store_xwidget_event_string (xw, "download-started",
webkit_uri_request_get_uri(request));
return FALSE;
}
static gboolean
webkit_decide_policy_cb (WebKitWebView *webView,
WebKitPolicyDecision *decision,
WebKitPolicyDecisionType type,
gpointer user_data)
{
switch (type) {
case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
/* This function makes webkit send a download signal for all unknown
mime types. TODO: Defer the decision to Lisp, so that it's
possible to make Emacs handle mime text for instance. */
{
WebKitResponsePolicyDecision *response =
WEBKIT_RESPONSE_POLICY_DECISION (decision);
if (!webkit_response_policy_decision_is_mime_type_supported (response))
{
webkit_policy_decision_download (decision);
return TRUE;
}
else
return FALSE;
break;
}
case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
{
WebKitNavigationPolicyDecision *navigation_decision =
WEBKIT_NAVIGATION_POLICY_DECISION (decision);
WebKitNavigationAction *navigation_action =
webkit_navigation_policy_decision_get_navigation_action (navigation_decision);
WebKitURIRequest *request =
webkit_navigation_action_get_request (navigation_action);
WebKitWebView *newview;
struct xwidget *xw = g_object_get_data (G_OBJECT (webView), XG_XWIDGET);
Lisp_Object val, new_xwidget;
XSETXWIDGET (val, xw);
new_xwidget = Fmake_xwidget (Qwebkit, Qnil, make_fixnum (0),
make_fixnum (0), Qnil,
build_string (" *detached xwidget buffer*"),
val);
if (NILP (new_xwidget))
return FALSE;
newview = WEBKIT_WEB_VIEW (XXWIDGET (new_xwidget)->widget_osr);
webkit_web_view_load_request (newview, request);
store_xwidget_display_event (XXWIDGET (new_xwidget), xw);
return TRUE;
}
case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
{
WebKitNavigationPolicyDecision *navigation_decision =
WEBKIT_NAVIGATION_POLICY_DECISION (decision);
WebKitNavigationAction *navigation_action =
webkit_navigation_policy_decision_get_navigation_action (navigation_decision);
WebKitURIRequest *request =
webkit_navigation_action_get_request (navigation_action);
struct xwidget *xw = g_object_get_data (G_OBJECT (webView), XG_XWIDGET);
store_xwidget_event_string (xw, "decide-policy",
webkit_uri_request_get_uri (request));
return FALSE;
break;
}
default:
return FALSE;
}
}
static gboolean
webkit_script_dialog_cb (WebKitWebView *webview,
WebKitScriptDialog *script_dialog,
gpointer user)
{
struct frame *f = SELECTED_FRAME ();
WebKitScriptDialogType type;
GtkWidget *widget;
GtkWidget *dialog;
GtkWidget *entry;
GtkWidget *content_area;
GtkWidget *box;
GtkWidget *label;
const gchar *content;
const gchar *message;
gint result;
/* Return TRUE to prevent WebKit from showing the default script
dialog in the offscreen window, which runs a nested main loop
Emacs can't respond to, and as such can't pass X events to. */
if (!FRAME_WINDOW_P (f))
return TRUE;
type = webkit_script_dialog_get_dialog_type (script_dialog);;
widget = FRAME_GTK_OUTER_WIDGET (f);
content = webkit_script_dialog_get_message (script_dialog);
if (type == WEBKIT_SCRIPT_DIALOG_ALERT)
dialog = gtk_dialog_new_with_buttons ("Alert", GTK_WINDOW (widget),
GTK_DIALOG_MODAL,
"Dismiss", 1, NULL);
else
dialog = gtk_dialog_new_with_buttons ("Question", GTK_WINDOW (widget),
GTK_DIALOG_MODAL,
"OK", 0, "Cancel", 1, NULL);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
label = gtk_label_new (content);
content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
gtk_container_add (GTK_CONTAINER (content_area), box);
gtk_widget_show (box);
gtk_widget_show (label);
gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 0);
if (type == WEBKIT_SCRIPT_DIALOG_PROMPT)
{
entry = gtk_entry_new ();
message = webkit_script_dialog_prompt_get_default_text (script_dialog);
gtk_widget_show (entry);
gtk_entry_set_text (GTK_ENTRY (entry), message);
gtk_box_pack_end (GTK_BOX (box), entry, TRUE, TRUE, 0);
}
result = gtk_dialog_run (GTK_DIALOG (dialog));
if (type == WEBKIT_SCRIPT_DIALOG_CONFIRM
|| type == WEBKIT_SCRIPT_DIALOG_BEFORE_UNLOAD_CONFIRM)
webkit_script_dialog_confirm_set_confirmed (script_dialog, result == 0);
if (type == WEBKIT_SCRIPT_DIALOG_PROMPT)
webkit_script_dialog_prompt_set_text (script_dialog,
gtk_entry_get_text (GTK_ENTRY (entry)));
gtk_widget_destroy (GTK_WIDGET (dialog));
return TRUE;
}
#endif /* USE_GTK */
#define CHECK_WEBKIT_WIDGET(xw) \
if (NILP (xw->buffer) || !EQ (xw->type, Qwebkit)) \
error ("Not a WebKit widget")
/* Macro that checks xwidget hold webkit web view first. */
#define WEBKIT_FN_INIT() \
CHECK_LIVE_XWIDGET (xwidget); \
struct xwidget *xw = XXWIDGET (xwidget); \
CHECK_WEBKIT_WIDGET (xw)
DEFUN ("xwidget-webkit-uri",
Fxwidget_webkit_uri, Sxwidget_webkit_uri,
1, 1, 0,
doc: /* Get the current URL of XWIDGET webkit. */)
(Lisp_Object xwidget)
{
WEBKIT_FN_INIT ();
#ifdef USE_GTK
WebKitWebView *wkwv = WEBKIT_WEB_VIEW (xw->widget_osr);
const gchar *uri = webkit_web_view_get_uri (wkwv);
if (!uri)
return build_string ("");
return build_string (uri);
#elif defined NS_IMPL_COCOA
return nsxwidget_webkit_uri (xw);
#endif
}
DEFUN ("xwidget-webkit-title",
Fxwidget_webkit_title, Sxwidget_webkit_title,
1, 1, 0,
doc: /* Get the current title of XWIDGET webkit. */)
(Lisp_Object xwidget)
{
WEBKIT_FN_INIT ();
#ifdef USE_GTK
WebKitWebView *wkwv = WEBKIT_WEB_VIEW (xw->widget_osr);
const gchar *title = webkit_web_view_get_title (wkwv);
return build_string (title ? title : "");
#elif defined NS_IMPL_COCOA
return nsxwidget_webkit_title (xw);
#endif
}
DEFUN ("xwidget-webkit-estimated-load-progress",
Fxwidget_webkit_estimated_load_progress, Sxwidget_webkit_estimated_load_progress,
1, 1, 0, doc: /* Get the estimated load progress of XWIDGET, a WebKit widget.
Return a value ranging from 0.0 to 1.0, based on how close XWIDGET
is to completely loading its page. */)
(Lisp_Object xwidget)
{
struct xwidget *xw;
#ifdef USE_GTK
WebKitWebView *webview;
#endif
double value;
CHECK_LIVE_XWIDGET (xwidget);
xw = XXWIDGET (xwidget);
CHECK_WEBKIT_WIDGET (xw);
block_input ();
#ifdef USE_GTK
webview = WEBKIT_WEB_VIEW (xw->widget_osr);
value = webkit_web_view_get_estimated_load_progress (webview);
#elif defined NS_IMPL_COCOA
value = nsxwidget_webkit_estimated_load_progress (xw);
#endif
unblock_input ();
return make_float (value);
}
DEFUN ("xwidget-webkit-goto-uri",
Fxwidget_webkit_goto_uri, Sxwidget_webkit_goto_uri,
2, 2, 0,
doc: /* Make the xwidget webkit instance referenced by XWIDGET browse URI. */)
(Lisp_Object xwidget, Lisp_Object 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));
catch_child_signal ();
#elif defined NS_IMPL_COCOA
nsxwidget_webkit_goto_uri (xw, SSDATA (uri));
#endif
return Qnil;
}
DEFUN ("xwidget-webkit-goto-history",
Fxwidget_webkit_goto_history, Sxwidget_webkit_goto_history,
2, 2, 0,
doc: /* Make the XWIDGET webkit the REL-POSth element in load history.
If REL-POS is 0, the widget will be just reload the current element in
history. If REL-POS is more or less than 0, the widget will load the
REL-POSth element around the current spot in the load history. */)
(Lisp_Object xwidget, Lisp_Object rel_pos)
{
WEBKIT_FN_INIT ();
CHECK_FIXNUM (rel_pos);
#ifdef USE_GTK
WebKitWebView *wkwv = WEBKIT_WEB_VIEW (xw->widget_osr);
WebKitBackForwardList *list;
WebKitBackForwardListItem *it;
if (XFIXNUM (rel_pos) == 0)
webkit_web_view_reload (wkwv);
else
{
list = webkit_web_view_get_back_forward_list (wkwv);
it = webkit_back_forward_list_get_nth_item (list, XFIXNUM (rel_pos));
if (!it)
error ("There is no item at this index");
webkit_web_view_go_to_back_forward_list_item (wkwv, it);
}
#elif defined NS_IMPL_COCOA
nsxwidget_webkit_goto_history (xw, XFIXNAT (rel_pos));
#endif
return Qnil;
}
DEFUN ("xwidget-webkit-zoom",
Fxwidget_webkit_zoom, Sxwidget_webkit_zoom,
2, 2, 0,
doc: /* Change the zoom factor of the xwidget webkit instance referenced by XWIDGET. */)
(Lisp_Object xwidget, Lisp_Object factor)
{
WEBKIT_FN_INIT ();
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
save_script_callback (struct xwidget *xw, Lisp_Object script, Lisp_Object fun)
{
Lisp_Object cbs = xw->script_callbacks;
if (NILP (cbs))
xw->script_callbacks = cbs = make_nil_vector (32);
/* Find first free index. */
ptrdiff_t idx;
for (idx = 0; !NILP (AREF (cbs, idx)); idx++)
if (idx + 1 == ASIZE (cbs))
{
xw->script_callbacks = cbs = larger_vector (cbs, 1, -1);
break;
}
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,
2, 3, 0,
doc: /* Make the Webkit XWIDGET execute JavaScript SCRIPT.
If FUN is provided, feed the JavaScript return value to the single
argument procedure FUN.*/)
(Lisp_Object xwidget, Lisp_Object script, Lisp_Object fun)
{
WEBKIT_FN_INIT ();
CHECK_STRING (script);
if (!NILP (fun) && !FUNCTIONP (fun))
wrong_type_argument (Qinvalid_function, fun);
script = ENCODE_SYSTEM (script);
#ifdef USE_GTK
/* Protect script and fun during GC. */
intptr_t idx = save_script_callback (xw, script, fun);
/* JavaScript execution happens asynchronously. If an elisp
callback function is provided we pass it to the C callback
procedure that retrieves the return value. */
gchar *script_string
= xmint_pointer (XCAR (AREF (xw->script_callbacks, idx)));
webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (xw->widget_osr),
script_string,
NULL, /* cancelable */
webkit_javascript_finished_cb,
(gpointer) idx);
#elif defined NS_IMPL_COCOA
nsxwidget_webkit_execute_script (xw, SSDATA (script), fun);
#endif
return Qnil;
}
DEFUN ("xwidget-webkit-search", Fxwidget_webkit_search, Sxwidget_webkit_search,
2, 5, 0,
doc: /* Begin an incremental search operation in an xwidget.
QUERY should be a string containing the text to search for. XWIDGET
should be a WebKit xwidget where the search will take place. When the
search operation is complete, callers should also call
`xwidget-webkit-finish-search' to complete the search operation.
CASE-INSENSITIVE, when non-nil, will cause the search to ignore the
case of characters inside QUERY. BACKWARDS, when non-nil, will cause
the search to proceed towards the beginning of the widget's contents.
WRAP-AROUND, when nil, will cause the search to stop upon hitting the
end of the widget's contents.
It is OK to call this function even when a search is already in
progress. In that case, the previous search query will be replaced
with QUERY. */)
(Lisp_Object query, Lisp_Object xwidget, Lisp_Object case_insensitive,
Lisp_Object backwards, Lisp_Object wrap_around)
{
#ifdef USE_GTK
WebKitWebView *webview;
WebKitFindController *controller;
WebKitFindOptions opt;
struct xwidget *xw;
gchar *g_query;
#endif
CHECK_STRING (query);
CHECK_LIVE_XWIDGET (xwidget);
#ifdef USE_GTK
xw = XXWIDGET (xwidget);
CHECK_WEBKIT_WIDGET (xw);
webview = WEBKIT_WEB_VIEW (xw->widget_osr);
query = ENCODE_UTF_8 (query);
opt = WEBKIT_FIND_OPTIONS_NONE;
g_query = xstrdup (SSDATA (query));
if (!NILP (case_insensitive))
opt |= WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE;
if (!NILP (backwards))
opt |= WEBKIT_FIND_OPTIONS_BACKWARDS;
if (!NILP (wrap_around))
opt |= WEBKIT_FIND_OPTIONS_WRAP_AROUND;
if (xw->find_text)
xfree (xw->find_text);
xw->find_text = g_query;
block_input ();
controller = webkit_web_view_get_find_controller (webview);
webkit_find_controller_search (controller, g_query, opt, G_MAXUINT);
unblock_input ();
#endif
return Qnil;
}
DEFUN ("xwidget-webkit-next-result", Fxwidget_webkit_next_result,
Sxwidget_webkit_next_result, 1, 1, 0,
doc: /* Show the next result matching the current search query.
XWIDGET should be an xwidget that currently has a search query.
Before calling this function, you should start a search operation
using `xwidget-webkit-search'. */)
(Lisp_Object xwidget)
{
struct xwidget *xw;
#ifdef USE_GTK
WebKitWebView *webview;
WebKitFindController *controller;
#endif
CHECK_LIVE_XWIDGET (xwidget);
xw = XXWIDGET (xwidget);
CHECK_WEBKIT_WIDGET (xw);
if (!xw->find_text)
error ("Widget has no ongoing search operation");
#ifdef USE_GTK
block_input ();
webview = WEBKIT_WEB_VIEW (xw->widget_osr);
controller = webkit_web_view_get_find_controller (webview);
webkit_find_controller_search_next (controller);
unblock_input ();
#endif
return Qnil;
}
DEFUN ("xwidget-webkit-previous-result", Fxwidget_webkit_previous_result,
Sxwidget_webkit_previous_result, 1, 1, 0,
doc: /* Show the previous result matching the current search query.
XWIDGET should be an xwidget that currently has a search query.
Before calling this function, you should start a search operation
using `xwidget-webkit-search'. */)
(Lisp_Object xwidget)
{
struct xwidget *xw;
#ifdef USE_GTK
WebKitWebView *webview;
WebKitFindController *controller;
#endif
CHECK_LIVE_XWIDGET (xwidget);
xw = XXWIDGET (xwidget);
CHECK_WEBKIT_WIDGET (xw);
if (!xw->find_text)
error ("Widget has no ongoing search operation");
#ifdef USE_GTK
block_input ();
webview = WEBKIT_WEB_VIEW (xw->widget_osr);
controller = webkit_web_view_get_find_controller (webview);
webkit_find_controller_search_previous (controller);
unblock_input ();
#endif
return Qnil;
}
DEFUN ("xwidget-webkit-finish-search", Fxwidget_webkit_finish_search,
Sxwidget_webkit_finish_search, 1, 1, 0,
doc: /* Finish XWIDGET's search operation.
XWIDGET should be an xwidget that currently has a search query.
Before calling this function, you should start a search operation
using `xwidget-webkit-search'. */)
(Lisp_Object xwidget)
{
struct xwidget *xw;
#ifdef USE_GTK
WebKitWebView *webview;
WebKitFindController *controller;
#endif
CHECK_LIVE_XWIDGET (xwidget);
xw = XXWIDGET (xwidget);
CHECK_WEBKIT_WIDGET (xw);
if (!xw->find_text)
error ("Widget has no ongoing search operation");
#ifdef USE_GTK
block_input ();
webview = WEBKIT_WEB_VIEW (xw->widget_osr);
controller = webkit_web_view_get_find_controller (webview);
webkit_find_controller_search_finish (controller);
if (xw->find_text)
{
xfree (xw->find_text);
xw->find_text = NULL;
}
unblock_input ();
#endif
return Qnil;
}
#ifdef USE_GTK
DEFUN ("xwidget-webkit-load-html", Fxwidget_webkit_load_html,
Sxwidget_webkit_load_html, 2, 3, 0,
doc: /* Make XWIDGET's WebKit widget render TEXT.
XWIDGET should be a WebKit xwidget, that will receive TEXT. TEXT
should be a string that will be displayed by XWIDGET as HTML markup.
BASE-URI should be a string containing a URI that is used to locate
resources with relative URLs, and if not specified, defaults
to "about:blank". */)
(Lisp_Object xwidget, Lisp_Object text, Lisp_Object base_uri)
{
struct xwidget *xw;
WebKitWebView *webview;
char *data, *uri;
CHECK_LIVE_XWIDGET (xwidget);
CHECK_STRING (text);
if (NILP (base_uri))
base_uri = build_string ("about:blank");
else
CHECK_STRING (base_uri);
base_uri = ENCODE_UTF_8 (base_uri);
text = ENCODE_UTF_8 (text);
xw = XXWIDGET (xwidget);
CHECK_WEBKIT_WIDGET (xw);
data = SSDATA (text);
uri = SSDATA (base_uri);
webview = WEBKIT_WEB_VIEW (xw->widget_osr);
block_input ();
webkit_web_view_load_html (webview, data, uri);
unblock_input ();
return Qnil;
}
DEFUN ("xwidget-webkit-back-forward-list", Fxwidget_webkit_back_forward_list,
Sxwidget_webkit_back_forward_list, 1, 2, 0,
doc: /* Return the navigation history of XWIDGET, a WebKit xwidget.
Return the history as a list of the form (BACK HERE FORWARD), where
HERE is the current navigation item, while BACK and FORWARD are lists
of history items of the form (IDX TITLE URI). Here, IDX is an index
that can be passed to `xwidget-webkit-goto-history', TITLE is a string
containing the human-readable title of the history item, and URI is
the URI of the history item.
BACK, HERE, and FORWARD can all be nil depending on the state of the
navigation history.
BACK and FORWARD will each not contain more elements than LIMIT. If
LIMIT is not specified or nil, it is treated as `50'. */)
(Lisp_Object xwidget, Lisp_Object limit)
{
struct xwidget *xw;
Lisp_Object back, here, forward;
WebKitWebView *webview;
WebKitBackForwardList *list;
WebKitBackForwardListItem *item;
GList *parent, *tem;
int i;
unsigned int lim;
Lisp_Object title, uri;
const gchar *item_title, *item_uri;
back = Qnil;
here = Qnil;
forward = Qnil;
if (NILP (limit))
limit = make_fixnum (50);
else
CHECK_FIXNAT (limit);
CHECK_LIVE_XWIDGET (xwidget);
xw = XXWIDGET (xwidget);
webview = WEBKIT_WEB_VIEW (xw->widget_osr);
list = webkit_web_view_get_back_forward_list (webview);
item = webkit_back_forward_list_get_current_item (list);
lim = XFIXNAT (limit);
if (item)
{
item_title = webkit_back_forward_list_item_get_title (item);
item_uri = webkit_back_forward_list_item_get_uri (item);
here = list3 (make_fixnum (0),
build_string_from_utf8 (item_title ? item_title : ""),
build_string_from_utf8 (item_uri ? item_uri : ""));
}
parent = webkit_back_forward_list_get_back_list_with_limit (list, lim);
if (parent)
{
for (i = 1, tem = parent; tem; tem = tem->next, ++i)
{
item = tem->data;
item_title = webkit_back_forward_list_item_get_title (item);
item_uri = webkit_back_forward_list_item_get_uri (item);
title = build_string_from_utf8 (item_title ? item_title : "");
uri = build_string_from_utf8 (item_uri ? item_uri : "");
back = Fcons (list3 (make_fixnum (-i), title, uri), back);
}
}
back = Fnreverse (back);
g_list_free (parent);
parent = webkit_back_forward_list_get_forward_list_with_limit (list, lim);
if (parent)
{
for (i = 1, tem = parent; tem; tem = tem->next, ++i)
{
item = tem->data;
item_title = webkit_back_forward_list_item_get_title (item);
item_uri = webkit_back_forward_list_item_get_uri (item);
title = build_string_from_utf8 (item_title ? item_title : "");
uri = build_string_from_utf8 (item_uri ? item_uri : "");
forward = Fcons (list3 (make_fixnum (i), title, uri), forward);
}
}
forward = Fnreverse (forward);
g_list_free (parent);
return list3 (back, here, forward);
}
#endif
DEFUN ("xwidget-webkit-set-cookie-storage-file",
Fxwidget_webkit_set_cookie_storage_file, Sxwidget_webkit_set_cookie_storage_file,
2, 2, 0, doc: /* Make the WebKit widget XWIDGET load and store cookies in FILE.
Cookies will be stored as plain text in FILE, which must be an
absolute file name. All xwidgets related to XWIDGET will also
store cookies in FILE and load them from there. */)
(Lisp_Object xwidget, Lisp_Object file)
{
#ifdef USE_GTK
struct xwidget *xw;
WebKitWebView *webview;
WebKitWebContext *context;
WebKitCookieManager *manager;
CHECK_LIVE_XWIDGET (xwidget);
xw = XXWIDGET (xwidget);
CHECK_WEBKIT_WIDGET (xw);
CHECK_STRING (file);
block_input ();
webview = WEBKIT_WEB_VIEW (xw->widget_osr);
context = webkit_web_view_get_context (webview);
manager = webkit_web_context_get_cookie_manager (context);
webkit_cookie_manager_set_persistent_storage (manager,
SSDATA (ENCODE_UTF_8 (file)),
WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
unblock_input ();
#endif
return Qnil;
}
DEFUN ("xwidget-webkit-stop-loading", Fxwidget_webkit_stop_loading,
Sxwidget_webkit_stop_loading,
1, 1, 0, doc: /* Stop loading data in the WebKit widget XWIDGET.
This will stop any data transfer that may still be in progress inside
XWIDGET as part of loading a page. */)
(Lisp_Object xwidget)
{
struct xwidget *xw;
#ifdef USE_GTK
WebKitWebView *webview;
#endif
CHECK_LIVE_XWIDGET (xwidget);
xw = XXWIDGET (xwidget);
CHECK_WEBKIT_WIDGET (xw);
block_input ();
#ifdef USE_GTK
webview = WEBKIT_WEB_VIEW (xw->widget_osr);
webkit_web_view_stop_loading (webview);
#elif defined NS_IMPL_COCOA
nsxwidget_webkit_stop_loading (xw);
#endif
unblock_input ();
return Qnil;
}
void
syms_of_xwidget_webkit (void)
{
defsubr (&Sxwidget_webkit_uri);
defsubr (&Sxwidget_webkit_title);
defsubr (&Sxwidget_webkit_goto_uri);
defsubr (&Sxwidget_webkit_goto_history);
defsubr (&Sxwidget_webkit_zoom);
defsubr (&Sxwidget_webkit_execute_script);
defsubr (&Sxwidget_webkit_search);
defsubr (&Sxwidget_webkit_finish_search);
defsubr (&Sxwidget_webkit_next_result);
defsubr (&Sxwidget_webkit_previous_result);
defsubr (&Sxwidget_webkit_set_cookie_storage_file);
defsubr (&Sxwidget_webkit_stop_loading);
#ifdef USE_GTK
defsubr (&Sxwidget_webkit_load_html);
defsubr (&Sxwidget_webkit_back_forward_list);
#endif
defsubr (&Sxwidget_webkit_estimated_load_progress);
DEFVAR_BOOL ("xwidget-webkit-disable-javascript", xwidget_webkit_disable_javascript,
doc: /* If non-nil, disable execution of JavaScript in xwidget WebKit widgets.
Modifications to this setting do not take effect in existing WebKit
widgets; kill all xwidget-webkit buffers for changes in this setting
to take effect. */);
xwidget_webkit_disable_javascript = false;
}