blob: d6c164fba8deef343b22d598030479e160841d68 [file] [log] [blame]
/*
* Copyright (C) 2018 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 "HeadlessViewBackend.h"
#include "WindowViewBackend.h"
#if ENABLE_WEB_AUDIO || ENABLE_VIDEO
#include <gst/gst.h>
#endif
#include <memory>
#include <wpe/webkit.h>
#if defined(HAVE_ACCESSIBILITY) && HAVE_ACCESSIBILITY
#include <atk/atk.h>
#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 gboolean enableITP;
static gboolean printVersion;
static GHashTable* openViews;
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 },
{ "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 }
};
class InputClient final : public WPEToolingBackends::ViewBackend::InputClient {
public:
InputClient(GMainLoop* loop, WebKitWebView* webView)
: m_loop(loop)
, m_webView(webView)
{
}
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_main_loop_quit(m_loop);
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;
}
}
return false;
}
private:
GMainLoop* m_loop { nullptr };
WebKitWebView* m_webView { nullptr };
};
static WebKitWebView* createWebViewForAutomationCallback(WebKitAutomationSession*, WebKitWebView* view)
{
return view;
}
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 gboolean decidePermissionRequest(WebKitWebView *, WebKitPermissionRequest *request, gpointer)
{
g_print("Accepting %s request\n", G_OBJECT_TYPE_NAME(request));
webkit_permission_request_allow(request);
return TRUE;
}
static std::unique_ptr<WPEToolingBackends::ViewBackend> createViewBackend(uint32_t width, uint32_t height)
{
if (headlessMode)
return std::make_unique<WPEToolingBackends::HeadlessViewBackend>(width, height);
return std::make_unique<WPEToolingBackends::WindowViewBackend>(width, height);
}
typedef struct {
GMainLoop* mainLoop { nullptr };
WebKitUserContentFilter* filter { nullptr };
GError* error { nullptr };
} FilterSaveData;
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)
{
// Hash table key delete func takes care of unref'ing the view
g_hash_table_remove(openViews, webView);
}
static WebKitWebView* createWebView(WebKitWebView* webView, WebKitNavigationAction*, gpointer)
{
auto backend = createViewBackend(1280, 720);
struct wpe_view_backend* wpeBackend = backend->backend();
if (!wpeBackend)
return nullptr;
auto* viewBackend = webkit_web_view_backend_new(wpeBackend,
[](gpointer data) {
delete static_cast<WPEToolingBackends::ViewBackend*>(data);
}, backend.release());
auto* newWebView = webkit_web_view_new_with_related_view(viewBackend, webView);
webkit_web_view_set_settings(newWebView, webkit_web_view_get_settings(webView));
g_signal_connect(newWebView, "create", G_CALLBACK(createWebView), nullptr);
g_signal_connect(newWebView, "close", G_CALLBACK(webViewClose), nullptr);
g_hash_table_add(openViews, newWebView);
return newWebView;
}
int main(int argc, char *argv[])
{
#if ENABLE_DEVELOPER_MODE
g_setenv("WEBKIT_INJECTED_BUNDLE_PATH", WEBKIT_INJECTED_BUNDLE_PATH, FALSE);
#endif
GOptionContext* context = g_option_context_new(nullptr);
g_option_context_add_main_entries(context, commandLineOptions, nullptr);
#if 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(SVN_REVISION, "tarball"))
g_print(" (%s)", SVN_REVISION);
g_print("\n");
return 0;
}
auto* loop = g_main_loop_new(nullptr, FALSE);
auto backend = createViewBackend(1280, 720);
struct wpe_view_backend* wpeBackend = backend->backend();
if (!wpeBackend) {
g_warning("Failed to create WPE view backend");
g_main_loop_unref(loop);
return 1;
}
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);
auto* webContext = webkit_web_context_new_with_website_data_manager(manager);
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);
}
if (proxy) {
auto* webkitProxySettings = webkit_network_proxy_settings_new(proxy, ignoreHosts);
webkit_web_context_set_network_proxy_settings(webContext, WEBKIT_NETWORK_PROXY_MODE_CUSTOM, webkitProxySettings);
webkit_network_proxy_settings_free(webkitProxySettings);
}
const char* singleprocess = g_getenv("MINIBROWSER_SINGLEPROCESS");
webkit_web_context_set_process_model(webContext, (singleprocess && *singleprocess) ?
WEBKIT_PROCESS_MODEL_SHARED_SECONDARY_PROCESS : WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
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-encrypted-media", TRUE,
nullptr);
auto* backendPtr = backend.get();
auto* viewBackend = webkit_web_view_backend_new(wpeBackend, [](gpointer data) {
delete static_cast<WPEToolingBackends::ViewBackend*>(data);
}, backend.release());
auto* webView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
"backend", viewBackend,
"web-context", webContext,
"settings", settings,
"user-content-manager", userContentManager,
"is-controlled-by-automation", automationMode,
nullptr));
g_object_unref(settings);
backendPtr->setInputClient(std::make_unique<InputClient>(loop, webView));
#if defined(HAVE_ACCESSIBILITY) && HAVE_ACCESSIBILITY
auto* accessible = wpe_view_backend_dispatch_get_accessible(wpeBackend);
if (ATK_IS_OBJECT(accessible))
backendPtr->setAccessibleChild(ATK_OBJECT(accessible));
#endif
openViews = g_hash_table_new_full(nullptr, nullptr, g_object_unref, nullptr);
webkit_web_context_set_automation_allowed(webContext, automationMode);
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), nullptr);
g_signal_connect(webView, "close", G_CALLBACK(webViewClose), nullptr);
g_hash_table_add(openViews, webView);
if (ignoreTLSErrors)
webkit_web_context_set_tls_errors_policy(webContext, WEBKIT_TLS_ERRORS_POLICY_IGNORE);
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, "about:blank");
else
webkit_web_view_load_uri(webView, "https://wpewebkit.org");
g_main_loop_run(loop);
g_hash_table_destroy(openViews);
if (privateMode || automationMode)
g_object_unref(webContext);
g_main_loop_unref(loop);
return 0;
}