/* 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; }