/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* * Copyright © 2020 Jan-Michael Brummer * * This file is part of Epiphany. * * Epiphany 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. * * Epiphany 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 Epiphany. If not, see . */ #include "ephy-webextension-common.h" #include typedef struct { WebKitWebPage *page; WebKitFrame *frame; const char *guid; } EphySendMessageData; typedef struct { JSCValue *resolve_callback; JSCValue *reject_callback; } EphyCallbackData; static void ephy_callback_data_free (EphyCallbackData *data) { g_object_unref (data->reject_callback); g_object_unref (data->resolve_callback); g_free (data); } static EphyCallbackData * ephy_callback_data_new (JSCValue *resolve_callback, JSCValue *reject_callback) { EphyCallbackData *data = g_new (EphyCallbackData, 1); data->resolve_callback = g_object_ref (resolve_callback); data->reject_callback = g_object_ref (reject_callback); return data; } static void on_send_message_finish (WebKitWebPage *page, GAsyncResult *result, gpointer user_data) { EphyCallbackData *callback_data = user_data; g_autoptr (GError) error = NULL; g_autoptr (WebKitUserMessage) response = NULL; g_autoptr (JSCValue) ret = NULL; response = webkit_web_page_send_message_to_view_finish (page, result, &error); if (error) { ret = jsc_value_function_call (callback_data->reject_callback, G_TYPE_STRING, error->message, G_TYPE_NONE); } else { const char *json = g_variant_get_string (webkit_user_message_get_parameters (response), NULL); g_autoptr (JSCValue) value = NULL; JSCContext *context = jsc_value_get_context (callback_data->resolve_callback); if (strcmp (json, "") == 0) { value = jsc_value_new_undefined (context); ret = jsc_value_function_call (callback_data->resolve_callback, JSC_TYPE_VALUE, value, G_TYPE_NONE); } else if (strcmp (webkit_user_message_get_name (response), "error") == 0) { ret = jsc_value_function_call (callback_data->reject_callback, G_TYPE_STRING, json, G_TYPE_NONE); } else { value = jsc_value_new_from_json (context, json); ret = jsc_value_function_call (callback_data->resolve_callback, JSC_TYPE_VALUE, value, G_TYPE_NONE); } } ephy_callback_data_free (callback_data); (void)ret; } static void ephy_send_message (const char *function_name, JSCValue *function_args, JSCValue *resolve_callback, JSCValue *reject_callback, gpointer user_data) { EphySendMessageData *send_message_data = user_data; WebKitUserMessage *message; g_autofree char *args_json = NULL; if (!jsc_value_is_function (reject_callback)) return; /* Can't reject in this case. */ if (!jsc_value_is_array (function_args) || !jsc_value_is_function (resolve_callback)) { g_autoptr (JSCValue) ret = jsc_value_function_call (reject_callback, G_TYPE_STRING, "ephy_send_message(): Invalid Arguments", G_TYPE_NONE); (void)ret; return; } args_json = jsc_value_to_json (function_args, 0); message = webkit_user_message_new (function_name, g_variant_new ("(sts)", send_message_data->guid, webkit_frame_get_id (send_message_data->frame), args_json)); webkit_web_page_send_message_to_view (send_message_data->page, message, NULL, (GAsyncReadyCallback)on_send_message_finish, ephy_callback_data_new (resolve_callback, reject_callback)); } static char * js_getmessage (const char *message, gpointer user_data) { JsonObject *translations = user_data; JsonObject *translation = NULL; if (!translations) return g_strdup (message); translation = json_object_get_object_member (translations, message); if (translation) { /* FIXME: Implement placeholders: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Internationalization */ const char *translated_message = json_object_get_string_member (translation, "message"); return g_strdup (translated_message); } return g_strdup (message); } static char * js_getuilanguage (void) { char *locale = setlocale (LC_MESSAGES, NULL); if (locale) { locale[2] = '\0'; return g_strdup (locale); } return g_strdup ("en"); } static char * js_geturl (const char *path, gpointer user_data) { const char *guid = user_data; return g_strdup_printf ("ephy-webextension://%s/%s", guid, path[0] == '/' ? path + 1 : path); } static void js_exception_handler (JSCContext *context, JSCException *exception) { g_autoptr (JSCValue) js_console = NULL; g_autoptr (JSCValue) js_result = NULL; g_autofree char *report = NULL; js_console = jsc_context_get_value (context, "console"); js_result = jsc_value_object_invoke_method (js_console, "error", JSC_TYPE_EXCEPTION, exception, G_TYPE_NONE); (void)js_result; report = jsc_exception_report (exception); g_warning ("%s", report); jsc_context_throw_exception (context, exception); } void ephy_webextension_install_common_apis (WebKitWebPage *page, WebKitFrame *frame, JSCContext *js_context, const char *guid, JsonObject *translations) { g_autoptr (JSCValue) js_browser = NULL; g_autoptr (JSCValue) js_i18n = NULL; g_autoptr (JSCValue) js_extension = NULL; g_autoptr (JSCValue) js_function = NULL; EphySendMessageData *send_message_data; jsc_context_push_exception_handler (js_context, (JSCExceptionHandler)js_exception_handler, NULL, NULL); /* APIs available in content scripts: https://developer.chrome.com/docs/extensions/mv3/content_scripts/ */ js_browser = jsc_context_get_value (js_context, "browser"); g_assert (jsc_value_is_object (js_browser)); /* i18n */ js_i18n = jsc_value_new_object (js_context, NULL, NULL); jsc_value_object_set_property (js_browser, "i18n", js_i18n); js_function = jsc_value_new_function (js_context, "getUILanguage", G_CALLBACK (js_getuilanguage), NULL, NULL, G_TYPE_STRING, 0); jsc_value_object_set_property (js_i18n, "getUILanguage", js_function); g_clear_object (&js_function); js_function = jsc_value_new_function (js_context, "getMessage", G_CALLBACK (js_getmessage), translations ? json_object_ref (translations) : NULL, translations ? (GDestroyNotify)json_object_unref : NULL, G_TYPE_STRING, 1, G_TYPE_STRING); jsc_value_object_set_property (js_i18n, "getMessage", js_function); g_clear_object (&js_function); /* extension */ js_extension = jsc_value_new_object (js_context, NULL, NULL); jsc_value_object_set_property (js_browser, "extension", js_extension); js_function = jsc_value_new_function (js_context, "getURL", G_CALLBACK (js_geturl), g_strdup (guid), g_free, G_TYPE_STRING, 1, G_TYPE_STRING); jsc_value_object_set_property (js_extension, "getURL", js_function); g_clear_object (&js_function); /* global functions */ send_message_data = g_new (EphySendMessageData, 1); send_message_data->page = page; send_message_data->frame = frame; send_message_data->guid = guid; js_function = jsc_value_new_function (js_context, NULL, G_CALLBACK (ephy_send_message), send_message_data, g_free, G_TYPE_NONE, 4, G_TYPE_STRING, JSC_TYPE_VALUE, JSC_TYPE_VALUE, JSC_TYPE_VALUE); jsc_context_set_value (js_context, "ephy_send_message", js_function); g_clear_object (&js_function); }