blob: e1b0bdb43d9bcf2136004ec126e6fd83a63f1663 [file] [log] [blame] [edit]
/*
* Copyright (C) 2018, 2024 Igalia S.L.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cmakeconfig.h"
#include "BuildRevision.h"
#include <memory>
#include <wpe/webkit.h>
#include <wtf/Compiler.h>
#if defined(USE_LIBWPE) && USE_LIBWPE
#include <WPEToolingBackends/HeadlessViewBackend.h>
#include <WPEToolingBackends/WindowViewBackend.h>
#endif
#if ENABLE_WPE_PLATFORM_HEADLESS
#include <wpe/headless/wpe-headless.h>
#endif
#if USE_ATK
#include <atk/atk.h>
#endif
#if !USE_GSTREAMER_FULL && (ENABLE_WEB_AUDIO || ENABLE_VIDEO)
IGNORE_WARNINGS_BEGIN("cast-align")
#include <gst/gst.h>
IGNORE_WARNINGS_END
#endif
static const char** uriArguments;
static const char** ignoreHosts;
static gboolean headlessMode;
static gboolean privateMode;
static gboolean automationMode;
static gboolean ignoreTLSErrors;
static const char* contentFilter;
static const char* cookiesFile;
static const char* cookiesPolicy;
static const char* proxy;
const char* bgColor;
static char* timeZone;
static const char* featureList = nullptr;
static gboolean enableITP;
static gboolean printVersion;
static guint windowWidth = 0;
static guint windowHeight = 0;
#if defined(USE_LIBWPE) && USE_LIBWPE
static guint defaultWindowWidthLegacyAPI = 1280;
static guint defaultWindowHeightLegacyAPI = 720;
#endif
static GHashTable* openViews;
static gboolean windowMaximized;
static gboolean windowFullscreen;
#if ENABLE_WPE_PLATFORM
#if defined(USE_LIBWPE) && USE_LIBWPE
static gboolean useLegacyAPI;
#endif
static const char* defaultWindowTitle = "WPEWebKit MiniBrowser";
static const char* configFile;
#endif
static gboolean parseWindowSize(const char*, const char* value, gpointer, GError** error)
{
if (!value)
return FALSE;
g_auto(GStrv) windowSizes = g_strsplit(value, "x", 2);
if (!windowSizes || !windowSizes[0] || !windowSizes[1]) {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Failed to parse --size command line argument \"%s\" - use \"<size>x<height>\" without any additional whitespace.", value);
return FALSE;
}
guint64 parsedWidth = 0;
if (!g_ascii_string_to_unsigned(windowSizes[0], 10, 0, G_MAXUINT, &parsedWidth, nullptr)) {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Failed to parse window width as unsigned integer from string \"%s\"", windowSizes[0]);
return FALSE;
}
guint64 parsedHeight = 0;
if (!g_ascii_string_to_unsigned(windowSizes[1], 10, 0, G_MAXUINT, &parsedHeight, nullptr)) {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Failed to parse window height as unsigned integer from string \"%s\"", windowSizes[1]);
return FALSE;
}
if (!parsedWidth || !parsedHeight) {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Window width/height needs to be larger than zero.");
return FALSE;
}
windowWidth = static_cast<guint>(parsedWidth);
windowHeight = static_cast<guint>(parsedHeight);
return TRUE;
}
static const GOptionEntry commandLineOptions[] =
{
{ "headless", 'h', 0, G_OPTION_ARG_NONE, &headlessMode, "Run in headless mode", nullptr },
{ "private", 'p', 0, G_OPTION_ARG_NONE, &privateMode, "Run in private browsing mode", nullptr },
{ "automation", 0, 0, G_OPTION_ARG_NONE, &automationMode, "Run in automation mode", nullptr },
{ "cookies-file", 'c', 0, G_OPTION_ARG_FILENAME, &cookiesFile, "Persistent cookie storage database file", "FILE" },
{ "cookies-policy", 0, 0, G_OPTION_ARG_STRING, &cookiesPolicy, "Cookies accept policy (always, never, no-third-party). Default: no-third-party", "POLICY" },
{ "proxy", 0, 0, G_OPTION_ARG_STRING, &proxy, "Set proxy", "PROXY" },
{ "ignore-host", 0, 0, G_OPTION_ARG_STRING_ARRAY, &ignoreHosts, "Set proxy ignore hosts", "HOSTS" },
{ "ignore-tls-errors", 0, 0, G_OPTION_ARG_NONE, &ignoreTLSErrors, "Ignore TLS errors", nullptr },
{ "content-filter", 0, 0, G_OPTION_ARG_FILENAME, &contentFilter, "JSON with content filtering rules", "FILE" },
{ "bg-color", 0, 0, G_OPTION_ARG_STRING, &bgColor, "Window background color. Default: white", "COLOR" },
{ "enable-itp", 0, 0, G_OPTION_ARG_NONE, &enableITP, "Enable Intelligent Tracking Prevention (ITP)", nullptr },
{ "time-zone", 't', 0, G_OPTION_ARG_STRING, &timeZone, "Set time zone", "TIMEZONE" },
{ "features", 'F', 0, G_OPTION_ARG_STRING, &featureList, "Enable or disable WebKit features (hint: pass 'help' for a list)", "FEATURE-LIST" },
{ "maximized", 'm', 0, G_OPTION_ARG_NONE, &windowMaximized, "Start with maximized window", nullptr },
{ "fullscreen", 'f', 0, G_OPTION_ARG_NONE, &windowFullscreen, "Start with fullscreen window", nullptr },
#if ENABLE_WPE_PLATFORM
#if defined(USE_LIBWPE) && USE_LIBWPE
{ "use-legacy-api", 0, 0, G_OPTION_ARG_NONE, &useLegacyAPI, "Use the WPE legacy API (libwpe)", nullptr },
#endif
{ "config-file", 0, 0, G_OPTION_ARG_FILENAME, &configFile, "Config file to load for settings", "FILE" },
#endif
{ "size", 's', 0, G_OPTION_ARG_CALLBACK, reinterpret_cast<gpointer>(parseWindowSize), "Specify the window size to use, e.g. --size=\"800x600\"", nullptr },
{ "version", 'v', 0, G_OPTION_ARG_NONE, &printVersion, "Print the WPE version", nullptr },
{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &uriArguments, nullptr, "[URL]" },
{ nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr }
};
#if defined(USE_LIBWPE) && USE_LIBWPE
class InputClient final : public WPEToolingBackends::ViewBackend::InputClient {
public:
InputClient(GApplication* application, WebKitWebView* webView, WPEToolingBackends::ViewBackend* backend)
: m_application(application)
, m_webView(webView)
, m_backend(backend)
{
}
bool dispatchKeyboardEvent(struct wpe_input_keyboard_event* event) override
{
if (!event->pressed)
return false;
if (event->modifiers & wpe_input_keyboard_modifier_control && event->key_code == WPE_KEY_q) {
g_application_quit(m_application);
return true;
}
if (event->modifiers & wpe_input_keyboard_modifier_alt) {
if ((event->key_code == WPE_KEY_Left || event->key_code == WPE_KEY_KP_Left) && webkit_web_view_can_go_back(m_webView)) {
webkit_web_view_go_back(m_webView);
return true;
}
if ((event->key_code == WPE_KEY_Right || event->key_code == WPE_KEY_KP_Right) && webkit_web_view_can_go_forward(m_webView)) {
webkit_web_view_go_forward(m_webView);
return true;
}
}
if (event->key_code == WPE_KEY_F11)
m_backend->setFullscreen(!m_backend->isFullscreen());
return false;
}
private:
GApplication* m_application { nullptr };
WebKitWebView* m_webView { nullptr };
WPEToolingBackends::ViewBackend* m_backend { nullptr };
};
#endif // USE_LIBWPE
#if ENABLE_WPE_PLATFORM
static gboolean wpeViewEventCallback(WPEView* view, WPEEvent* event, WebKitWebView* webView)
{
if (wpe_event_get_event_type(event) != WPE_EVENT_KEYBOARD_KEY_DOWN)
return FALSE;
auto modifiers = wpe_event_get_modifiers(event);
auto keyval = wpe_event_keyboard_get_keyval(event);
if (modifiers & WPE_MODIFIER_KEYBOARD_CONTROL) {
if (keyval == WPE_KEY_q) {
g_application_quit(g_application_get_default());
return TRUE;
}
if (keyval == WPE_KEY_r) {
webkit_web_view_reload(webView);
return TRUE;
}
if ((modifiers & WPE_MODIFIER_KEYBOARD_SHIFT) && keyval == WPE_KEY_I) {
webkit_web_view_toggle_inspector(webView);
return TRUE;
}
}
if (modifiers & WPE_MODIFIER_KEYBOARD_ALT) {
if ((keyval == WPE_KEY_Left || keyval == WPE_KEY_KP_Left) && webkit_web_view_can_go_back(webView)) {
webkit_web_view_go_back(webView);
return TRUE;
}
if ((keyval == WPE_KEY_Right || keyval == WPE_KEY_KP_Right) && webkit_web_view_can_go_forward(webView)) {
webkit_web_view_go_forward(webView);
return TRUE;
}
if (keyval == WPE_KEY_Up) {
if (auto* toplevel = wpe_view_get_toplevel(view)) {
if (wpe_toplevel_get_state(toplevel) & WPE_TOPLEVEL_STATE_MAXIMIZED)
wpe_toplevel_unmaximize(toplevel);
else
wpe_toplevel_maximize(toplevel);
return TRUE;
}
}
if (keyval == WPE_KEY_Down) {
if (auto* toplevel = wpe_view_get_toplevel(view)) {
wpe_toplevel_minimize(toplevel);
return TRUE;
}
}
}
if (keyval == WPE_KEY_F11) {
if (auto* toplevel = wpe_view_get_toplevel(view)) {
if (wpe_toplevel_get_state(toplevel) & WPE_TOPLEVEL_STATE_FULLSCREEN)
wpe_toplevel_unfullscreen(toplevel);
else
wpe_toplevel_fullscreen(toplevel);
return TRUE;
}
}
if (keyval == WPE_KEY_Escape) {
if (webkit_web_view_is_immersive_mode_enabled(webView)) {
webkit_web_view_leave_immersive_mode(webView);
return TRUE;
}
}
return FALSE;
}
static void webViewTitleChanged(WebKitWebView* webView, GParamSpec*, WPEView* view)
{
const char* title = webkit_web_view_get_title(webView);
if (!title)
title = defaultWindowTitle;
char* privateTitle = nullptr;
if (webkit_web_view_is_controlled_by_automation(webView))
privateTitle = g_strdup_printf("[Automation] %s", title);
else if (webkit_network_session_is_ephemeral(webkit_web_view_get_network_session(webView)))
privateTitle = g_strdup_printf("[Private] %s", title);
wpe_toplevel_set_title(wpe_view_get_toplevel(view), privateTitle ? privateTitle : title);
g_free(privateTitle);
}
static void displayDisconnected(WPEDisplay*, GError* error)
{
if (error)
g_warning("WPE display disconnected: %s", error->message);
else
g_warning("WPE display disconnected");
_exit(1);
}
#endif
static gboolean decidePermissionRequest(WebKitWebView *, WebKitPermissionRequest *request, gpointer)
{
g_print("Accepting %s request\n", G_OBJECT_TYPE_NAME(request));
if (WEBKIT_IS_XR_PERMISSION_REQUEST(request)) {
WebKitXRPermissionRequest* xrRequest = WEBKIT_XR_PERMISSION_REQUEST(request);
/* Grant all optional features */
webkit_xr_permission_request_set_granted_optional_features(xrRequest, webkit_xr_permission_request_get_consent_optional_features(xrRequest));
}
webkit_permission_request_allow(request);
return TRUE;
}
#if defined(USE_LIBWPE) && USE_LIBWPE
static std::unique_ptr<WPEToolingBackends::ViewBackend> createViewBackend(uint32_t width, uint32_t height)
{
#if ENABLE_WPE_PLATFORM
if (!useLegacyAPI)
return nullptr;
#endif
if (headlessMode)
return std::make_unique<WPEToolingBackends::HeadlessViewBackend>(width, height);
return std::make_unique<WPEToolingBackends::WindowViewBackend>(width, height);
}
#endif
struct FilterSaveData {
GMainLoop* mainLoop { nullptr };
WebKitUserContentFilter* filter { nullptr };
GError* error { nullptr };
};
static void filterSavedCallback(WebKitUserContentFilterStore *store, GAsyncResult *result, FilterSaveData *data)
{
data->filter = webkit_user_content_filter_store_save_finish(store, result, &data->error);
g_main_loop_quit(data->mainLoop);
}
static void webViewClose(WebKitWebView* webView, gpointer user_data)
{
// Hash table key delete func takes care of unref'ing the view
g_hash_table_remove(openViews, webView);
if (!g_hash_table_size(openViews))
g_application_quit(G_APPLICATION(user_data));
}
static WebKitWebView* createWebView(WebKitWebView* webView, WebKitNavigationAction*, gpointer user_data)
{
#if defined(USE_LIBWPE) && USE_LIBWPE
auto backend = createViewBackend(defaultWindowWidthLegacyAPI, defaultWindowHeightLegacyAPI);
WebKitWebViewBackend* viewBackend = nullptr;
if (backend) {
struct wpe_view_backend* wpeBackend = backend->backend();
if (!wpeBackend)
return nullptr;
viewBackend = webkit_web_view_backend_new(wpeBackend,
[](gpointer data) {
delete static_cast<WPEToolingBackends::ViewBackend*>(data);
}, backend.release());
}
#endif
auto* newWebView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
#if defined(USE_LIBWPE) && USE_LIBWPE
"backend", viewBackend,
#endif
"related-view", webView,
"settings", webkit_web_view_get_settings(webView),
"user-content-manager", webkit_web_view_get_user_content_manager(webView),
nullptr));
#if ENABLE_WPE_PLATFORM
if (auto* wpeView = webkit_web_view_get_wpe_view(newWebView)) {
g_signal_connect(wpeView, "event", G_CALLBACK(wpeViewEventCallback), newWebView);
wpe_toplevel_set_title(wpe_view_get_toplevel(wpeView), defaultWindowTitle);
g_signal_connect(newWebView, "notify::title", G_CALLBACK(webViewTitleChanged), wpeView);
}
#endif
g_signal_connect(newWebView, "create", G_CALLBACK(createWebView), user_data);
g_signal_connect(newWebView, "close", G_CALLBACK(webViewClose), user_data);
g_hash_table_add(openViews, newWebView);
return newWebView;
}
static WebKitWebView* createWebViewForAutomationCallback(WebKitAutomationSession*, WebKitWebView* view)
{
#if ENABLE_WPE_PLATFORM
// The original view might have been closed, so we need to find a valid view to clone
if (!g_hash_table_lookup(openViews, view)) {
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init(&iter, openViews);
if (!g_hash_table_iter_next(&iter, &key, &value))
return nullptr;
view = WEBKIT_WEB_VIEW(value);
}
#if defined(USE_LIBWPE) && USE_LIBWPE
// Creating new views in the old API through automation is not supported by WPE's MiniBrowser,
// so we just return the same view as before
if (useLegacyAPI)
return view;
#endif
if (g_hash_table_size(openViews) == 1 && !webkit_web_view_get_uri(view)) {
webkit_web_view_load_uri(view, "about:blank");
return view;
}
auto* newWebView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
"settings", webkit_web_view_get_settings(view),
"web-context", webkit_web_view_get_context(view),
"display", webkit_web_view_get_display(view),
"is-controlled-by-automation", TRUE,
"user-content-manager", webkit_web_view_get_user_content_manager(view),
"website-policies", webkit_web_view_get_website_policies(view),
nullptr));
auto* application = g_application_get_default();
g_signal_connect(newWebView, "create", G_CALLBACK(createWebView), application);
g_signal_connect(newWebView, "close", G_CALLBACK(webViewClose), application);
webkit_web_view_load_uri(newWebView, "about:blank");
g_hash_table_add(openViews, newWebView);
return newWebView;
#else
return view;
#endif
}
static void automationStartedCallback(WebKitWebContext*, WebKitAutomationSession* session, WebKitWebView* view)
{
auto* info = webkit_application_info_new();
webkit_application_info_set_version(info, WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION);
webkit_automation_session_set_application_info(session, info);
webkit_application_info_unref(info);
g_signal_connect(session, "create-web-view", G_CALLBACK(createWebViewForAutomationCallback), view);
}
static WebKitFeature* findFeature(WebKitFeatureList* featureList, const char* identifier)
{
for (gsize i = 0; i < webkit_feature_list_get_length(featureList); i++) {
WebKitFeature* feature = webkit_feature_list_get(featureList, i);
if (!g_ascii_strcasecmp(identifier, webkit_feature_get_identifier(feature)))
return feature;
}
return nullptr;
}
#if ENABLE_WPE_PLATFORM
void loadConfigFile(WPESettings* settings)
{
GError* error = nullptr;
GKeyFile* keyFile = g_key_file_new();
if (!g_key_file_load_from_file(keyFile, configFile, G_KEY_FILE_NONE, &error)) {
g_warning("Error loading key file '%s': %s", configFile, error->message);
g_clear_error(&error);
return;
}
if (!wpe_settings_load_from_keyfile(settings, keyFile, &error)) {
g_warning("Error parsing config file '%s': %s", configFile, error->message);
g_clear_error(&error);
}
}
#endif
#if defined(USE_LIBWPE) && USE_LIBWPE
static void activate(GApplication* application, WPEToolingBackends::ViewBackend* backend)
#else
static void activate(GApplication* application, gpointer)
#endif
{
g_application_hold(application);
#if ENABLE_2022_GLIB_API
WebKitNetworkSession* networkSession = nullptr;
if (!automationMode) {
networkSession = privateMode ? webkit_network_session_new_ephemeral() : webkit_network_session_new(nullptr, nullptr);
webkit_network_session_set_itp_enabled(networkSession, enableITP);
if (proxy) {
auto* webkitProxySettings = webkit_network_proxy_settings_new(proxy, ignoreHosts);
webkit_network_session_set_proxy_settings(networkSession, WEBKIT_NETWORK_PROXY_MODE_CUSTOM, webkitProxySettings);
webkit_network_proxy_settings_free(webkitProxySettings);
}
if (ignoreTLSErrors)
webkit_network_session_set_tls_errors_policy(networkSession, WEBKIT_TLS_ERRORS_POLICY_IGNORE);
if (cookiesPolicy) {
auto* cookieManager = webkit_network_session_get_cookie_manager(networkSession);
auto* enumClass = static_cast<GEnumClass*>(g_type_class_ref(WEBKIT_TYPE_COOKIE_ACCEPT_POLICY));
GEnumValue* enumValue = g_enum_get_value_by_nick(enumClass, cookiesPolicy);
if (enumValue)
webkit_cookie_manager_set_accept_policy(cookieManager, static_cast<WebKitCookieAcceptPolicy>(enumValue->value));
g_type_class_unref(enumClass);
}
if (cookiesFile && !webkit_network_session_is_ephemeral(networkSession)) {
auto* cookieManager = webkit_network_session_get_cookie_manager(networkSession);
auto storageType = g_str_has_suffix(cookiesFile, ".txt") ? WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT : WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE;
webkit_cookie_manager_set_persistent_storage(cookieManager, cookiesFile, storageType);
}
}
auto* webContext = WEBKIT_WEB_CONTEXT(g_object_new(WEBKIT_TYPE_WEB_CONTEXT, "time-zone-override", timeZone, nullptr));
#else
auto* manager = (privateMode || automationMode) ? webkit_website_data_manager_new_ephemeral() : webkit_website_data_manager_new(nullptr);
webkit_website_data_manager_set_itp_enabled(manager, enableITP);
if (proxy) {
auto* webkitProxySettings = webkit_network_proxy_settings_new(proxy, ignoreHosts);
webkit_website_data_manager_set_network_proxy_settings(manager, WEBKIT_NETWORK_PROXY_MODE_CUSTOM, webkitProxySettings);
webkit_network_proxy_settings_free(webkitProxySettings);
}
if (ignoreTLSErrors)
webkit_website_data_manager_set_tls_errors_policy(manager, WEBKIT_TLS_ERRORS_POLICY_IGNORE);
auto* webContext = WEBKIT_WEB_CONTEXT(g_object_new(WEBKIT_TYPE_WEB_CONTEXT, "website-data-manager", manager, "time-zone-override", timeZone, nullptr));
g_object_unref(manager);
if (cookiesPolicy) {
auto* cookieManager = webkit_web_context_get_cookie_manager(webContext);
auto* enumClass = static_cast<GEnumClass*>(g_type_class_ref(WEBKIT_TYPE_COOKIE_ACCEPT_POLICY));
GEnumValue* enumValue = g_enum_get_value_by_nick(enumClass, cookiesPolicy);
if (enumValue)
webkit_cookie_manager_set_accept_policy(cookieManager, static_cast<WebKitCookieAcceptPolicy>(enumValue->value));
g_type_class_unref(enumClass);
}
if (cookiesFile && !webkit_web_context_is_ephemeral(webContext)) {
auto* cookieManager = webkit_web_context_get_cookie_manager(webContext);
auto storageType = g_str_has_suffix(cookiesFile, ".txt") ? WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT : WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE;
webkit_cookie_manager_set_persistent_storage(cookieManager, cookiesFile, storageType);
}
#endif
WebKitUserContentManager* userContentManager = nullptr;
if (contentFilter) {
GFile* contentFilterFile = g_file_new_for_commandline_arg(contentFilter);
FilterSaveData saveData = { nullptr, };
gchar* filtersPath = g_build_filename(g_get_user_cache_dir(), g_get_prgname(), "filters", nullptr);
WebKitUserContentFilterStore* store = webkit_user_content_filter_store_new(filtersPath);
g_free(filtersPath);
webkit_user_content_filter_store_save_from_file(store, "WPEMiniBrowserFilter", contentFilterFile, nullptr, (GAsyncReadyCallback)filterSavedCallback, &saveData);
saveData.mainLoop = g_main_loop_new(nullptr, FALSE);
g_main_loop_run(saveData.mainLoop);
g_object_unref(store);
if (saveData.filter) {
userContentManager = webkit_user_content_manager_new();
webkit_user_content_manager_add_filter(userContentManager, saveData.filter);
} else
g_printerr("Cannot save filter '%s': %s\n", contentFilter, saveData.error->message);
g_clear_pointer(&saveData.error, g_error_free);
g_clear_pointer(&saveData.filter, webkit_user_content_filter_unref);
g_main_loop_unref(saveData.mainLoop);
g_object_unref(contentFilterFile);
}
auto* settings = webkit_settings_new_with_settings(
"enable-developer-extras", TRUE,
"enable-webgl", TRUE,
"enable-media-stream", TRUE,
"enable-webrtc", TRUE,
"enable-encrypted-media", TRUE,
nullptr);
if (featureList) {
g_autoptr(WebKitFeatureList) features = webkit_settings_get_all_features();
g_auto(GStrv) items = g_strsplit(featureList, ",", -1);
for (gsize i = 0; items[i]; i++) {
char* item = g_strchomp(items[i]);
gboolean enabled = TRUE;
switch (item[0]) {
case '!':
case '-':
enabled = FALSE;
[[fallthrough]];
case '+':
item++;
[[fallthrough]];
default:
break;
}
if (item[0] == '\0') {
g_printerr("Empty feature name specified, skipped.");
continue;
}
if (auto* feature = findFeature(features, item))
webkit_settings_set_feature_enabled(settings, feature, enabled);
else
g_printerr("Feature '%s' is not available.", item);
}
}
#if defined(USE_LIBWPE) && USE_LIBWPE
auto* viewBackend = backend ? webkit_web_view_backend_new(backend->backend(), [](gpointer data) {
delete static_cast<WPEToolingBackends::ViewBackend*>(data);
}, backend) : nullptr;
#endif
#if ENABLE_WPE_PLATFORM_HEADLESS
#if defined(USE_LIBWPE) && USE_LIBWPE
const bool useHeadlessMode = headlessMode && !useLegacyAPI;
#else
const bool useHeadlessMode = headlessMode;
#endif
WPEDisplay* wpeDisplay = useHeadlessMode ? wpe_display_headless_new() : nullptr;
#endif
webkit_web_context_set_automation_allowed(webContext, automationMode);
auto* defaultWebsitePolicies = webkit_website_policies_new_with_policies(
"autoplay", WEBKIT_AUTOPLAY_ALLOW,
nullptr);
auto* webView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
#if defined(USE_LIBWPE) && USE_LIBWPE
"backend", viewBackend,
#endif
"web-context", webContext,
#if ENABLE_2022_GLIB_API
"network-session", networkSession,
#endif
"settings", settings,
"user-content-manager", userContentManager,
"is-controlled-by-automation", automationMode,
"website-policies", defaultWebsitePolicies,
#if ENABLE_WPE_PLATFORM_HEADLESS
"display", wpeDisplay,
#endif
nullptr));
g_object_unref(settings);
g_object_unref(defaultWebsitePolicies);
#if ENABLE_WPE_PLATFORM_HEADLESS
g_clear_object(&wpeDisplay);
#endif
#if defined(USE_LIBWPE) && USE_LIBWPE
if (backend) {
backend->setInputClient(std::make_unique<InputClient>(application, webView, backend));
backend->setMaximized(windowMaximized);
backend->setFullscreen(windowFullscreen);
#if USE_ATK
auto* accessible = wpe_view_backend_dispatch_get_accessible(backend->backend());
if (ATK_IS_OBJECT(accessible))
backend->setAccessibleChild(ATK_OBJECT(accessible));
#endif
}
#endif
#if ENABLE_WPE_PLATFORM
if (auto* wpeView = webkit_web_view_get_wpe_view(webView)) {
g_signal_connect(wpe_view_get_display(wpeView), "disconnected", G_CALLBACK(displayDisconnected), nullptr);
auto* wpeToplevel = wpe_view_get_toplevel(wpeView);
if (windowWidth > 0 && windowHeight > 0)
wpe_toplevel_resize(wpeToplevel, windowWidth, windowHeight);
if (windowMaximized)
wpe_toplevel_maximize(wpeToplevel);
if (windowFullscreen)
wpe_toplevel_fullscreen(wpeToplevel);
g_signal_connect(wpeView, "event", G_CALLBACK(wpeViewEventCallback), webView);
wpe_toplevel_set_title(wpeToplevel, defaultWindowTitle);
g_signal_connect(webView, "notify::title", G_CALLBACK(webViewTitleChanged), wpeView);
if (configFile)
loadConfigFile(wpe_display_get_settings(wpe_view_get_display(wpeView)));
}
#endif
openViews = g_hash_table_new_full(nullptr, nullptr, g_object_unref, nullptr);
g_signal_connect(webContext, "automation-started", G_CALLBACK(automationStartedCallback), webView);
g_signal_connect(webView, "permission-request", G_CALLBACK(decidePermissionRequest), nullptr);
g_signal_connect(webView, "create", G_CALLBACK(createWebView), application);
g_signal_connect(webView, "close", G_CALLBACK(webViewClose), application);
g_hash_table_add(openViews, webView);
WebKitColor color;
if (bgColor && webkit_color_parse(&color, bgColor))
webkit_web_view_set_background_color(webView, &color);
if (uriArguments) {
const char* uri = uriArguments[0];
if (g_str_equal(uri, "about:gpu"))
uri = "webkit://gpu";
GFile* file = g_file_new_for_commandline_arg(uri);
char* url = g_file_get_uri(file);
g_object_unref(file);
webkit_web_view_load_uri(webView, url);
g_free(url);
} else if (!automationMode)
webkit_web_view_load_uri(webView, "https://wpewebkit.org");
g_object_unref(webContext);
#if ENABLE_2022_GLIB_API
g_clear_object(&networkSession);
#endif
}
int main(int argc, char *argv[])
{
#if ENABLE_DEVELOPER_MODE
g_setenv("WEBKIT_INJECTED_BUNDLE_PATH", WEBKIT_INJECTED_BUNDLE_PATH, FALSE);
g_setenv("WEBKIT_INSPECTOR_RESOURCES_PATH", WEBKIT_INSPECTOR_RESOURCES_PATH, FALSE);
#endif
GOptionContext* context = g_option_context_new(nullptr);
g_option_context_add_main_entries(context, commandLineOptions, nullptr);
#if !USE_GSTREAMER_FULL && (ENABLE_WEB_AUDIO || ENABLE_VIDEO)
g_option_context_add_group(context, gst_init_get_option_group());
#endif
GError* error = nullptr;
if (!g_option_context_parse(context, &argc, &argv, &error)) {
g_printerr("Cannot parse arguments: %s\n", error->message);
g_error_free(error);
g_option_context_free(context);
return 1;
}
g_option_context_free(context);
if (printVersion) {
g_print("WPE WebKit %u.%u.%u",
webkit_get_major_version(),
webkit_get_minor_version(),
webkit_get_micro_version());
if (g_strcmp0(BUILD_REVISION, "tarball"))
g_print(" (%s)", BUILD_REVISION);
g_print("\n");
return 0;
}
if (!g_strcmp0(featureList, "help")) {
g_print("Multiple feature names may be specified separated by commas. No prefix or '+' enable\n"
"features, prefixes '-' and '!' disable features. Names are case-insensitive. Example:\n"
"\n %s --features='!DirPseudo,+WebAnimationsCustomEffects,webgl'\n\n"
"Available features (+/- = enabled/disabled by default):\n\n", g_get_prgname());
g_autoptr(GEnumClass) statusEnum = static_cast<GEnumClass*>(g_type_class_ref(WEBKIT_TYPE_FEATURE_STATUS));
g_autoptr(WebKitFeatureList) features = webkit_settings_get_all_features();
for (gsize i = 0; i < webkit_feature_list_get_length(features); i++) {
WebKitFeature* feature = webkit_feature_list_get(features, i);
g_print(" %c %s (%s)",
webkit_feature_get_default_value(feature) ? '+' : '-',
webkit_feature_get_identifier(feature),
g_enum_get_value(statusEnum, webkit_feature_get_status(feature))->value_nick);
if (webkit_feature_get_name(feature))
g_print(": %s", webkit_feature_get_name(feature));
g_print("\n");
}
return 0;
}
#if defined(USE_LIBWPE) && USE_LIBWPE
bool setDefaultWindowSize = true;
#if ENABLE_WPE_PLATFORM
if (!useLegacyAPI)
setDefaultWindowSize = false;
#endif
#endif
if (windowMaximized && windowFullscreen) {
g_printerr("You cannot specify both --maximized and --fullscreen, these options are mutually exclusive.");
return 1;
}
#if defined(USE_LIBWPE) && USE_LIBWPE
if (setDefaultWindowSize) {
// Default values used in old API, for legacy reasons.
windowWidth = defaultWindowWidthLegacyAPI;
windowHeight = defaultWindowHeightLegacyAPI;
}
auto backend = createViewBackend(windowWidth, windowHeight);
if (backend) {
struct wpe_view_backend* wpeBackend = backend->backend();
if (!wpeBackend) {
g_printerr("Failed to create WPE view backend");
return 1;
}
}
#endif
GApplication* application = g_application_new("org.wpewebkit.MiniBrowser", G_APPLICATION_NON_UNIQUE);
#if defined(USE_LIBWPE) && USE_LIBWPE
g_signal_connect(application, "activate", G_CALLBACK(activate), backend.release());
#else
g_signal_connect(application, "activate", G_CALLBACK(activate), nullptr);
#endif
g_application_run(application, 0, nullptr);
g_object_unref(application);
g_hash_table_destroy(openViews);
return 0;
}