blob: a43c93ea984c3a6fb5fb409a207bc7f86573cd7d [file] [log] [blame]
/*
* Copyright (C) 2011 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2,1 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "LoadTrackingTest.h"
#include "WebKitTestServer.h"
#include <WebCore/SoupVersioning.h>
#include <libsoup/soup.h>
#include <limits.h>
#include <stdlib.h>
#include <wtf/HashMap.h>
#include <wtf/glib/GRefPtr.h>
#include <wtf/glib/GUniquePtr.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/StringHash.h>
static WebKitTestServer* kServer;
static void testWebContextDefault(Test* test, gconstpointer)
{
// Check there's a single instance of the default web context.
g_assert_true(webkit_web_context_get_default() == webkit_web_context_get_default());
g_assert_true(webkit_web_context_get_default() != test->m_webContext.get());
}
static void testWebContextEphemeral(Test* test, gconstpointer)
{
// By default web contexts are not ephemeral.
g_assert_false(webkit_web_context_is_ephemeral(webkit_web_context_get_default()));
g_assert_false(webkit_web_context_is_ephemeral(test->m_webContext.get()));
WebKitWebsiteDataManager* manager = webkit_web_context_get_website_data_manager(webkit_web_context_get_default());
g_assert_true(WEBKIT_IS_WEBSITE_DATA_MANAGER(manager));
g_assert_false(webkit_website_data_manager_is_ephemeral(manager));
manager = webkit_web_context_get_website_data_manager(test->m_webContext.get());
g_assert_true(WEBKIT_IS_WEBSITE_DATA_MANAGER(manager));
g_assert_false(webkit_website_data_manager_is_ephemeral(manager));
auto webView = Test::adoptView(Test::createWebView());
g_assert_false(webkit_web_view_is_ephemeral(webView.get()));
g_assert_true(webkit_web_view_get_website_data_manager(webView.get()) == webkit_web_context_get_website_data_manager(webkit_web_context_get_default()));
webView = Test::adoptView(Test::createWebView(test->m_webContext.get()));
g_assert_false(webkit_web_view_is_ephemeral(webView.get()));
g_assert_true(webkit_web_view_get_website_data_manager(webView.get()) == manager);
GRefPtr<WebKitWebContext> context = adoptGRef(webkit_web_context_new_ephemeral());
g_assert_true(webkit_web_context_is_ephemeral(context.get()));
manager = webkit_web_context_get_website_data_manager(context.get());
g_assert_true(WEBKIT_IS_WEBSITE_DATA_MANAGER(manager));
g_assert_true(webkit_website_data_manager_is_ephemeral(manager));
g_assert_true(webkit_web_view_get_website_data_manager(webView.get()) != manager);
webView = Test::adoptView(Test::createWebView(context.get()));
g_assert_true(webkit_web_view_is_ephemeral(webView.get()));
g_assert_true(webkit_web_view_get_website_data_manager(webView.get()) == manager);
GRefPtr<WebKitWebsiteDataManager> ephemeralManager = adoptGRef(webkit_website_data_manager_new_ephemeral());
g_assert_true(webkit_website_data_manager_is_ephemeral(ephemeralManager.get()));
context = adoptGRef(webkit_web_context_new_with_website_data_manager(ephemeralManager.get()));
g_assert_true(webkit_web_context_is_ephemeral(context.get()));
}
static const char* kBarHTML = "<html><body>Bar</body></html>";
static const char* kEchoHTMLFormat = "<html><body>%s</body></html>";
static const char* errorDomain = "test";
static const int errorCode = 10;
static const char* genericErrorMessage = "Error message.";
static const char* beforeReceiveResponseErrorMessage = "Error before didReceiveResponse.";
class URISchemeTest: public LoadTrackingTest {
public:
MAKE_GLIB_TEST_FIXTURE(URISchemeTest);
struct URISchemeHandler {
URISchemeHandler()
: replyLength(0)
{
}
URISchemeHandler(const char* reply, int replyLength, const char* mimeType, int statusCode = 200)
: reply(reply)
, replyLength(replyLength)
, mimeType(mimeType)
, statusCode(statusCode)
{
}
CString reply;
int replyLength;
CString mimeType;
int statusCode;
};
static void uriSchemeRequestCallback(WebKitURISchemeRequest* request, gpointer userData)
{
URISchemeTest* test = static_cast<URISchemeTest*>(userData);
test->m_uriSchemeRequest = request;
test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(request));
if (test->m_uriSchemeRequestCallbackUsesTestWebView)
g_assert_true(webkit_uri_scheme_request_get_web_view(request) == test->m_webView);
const char* scheme = webkit_uri_scheme_request_get_scheme(request);
g_assert_nonnull(scheme);
g_assert_true(test->m_handlersMap.contains(String::fromUTF8(scheme)));
const char* method = webkit_uri_scheme_request_get_http_method(request);
g_assert_nonnull(method);
if (!g_strcmp0(scheme, "post"))
g_assert_cmpstr(method, ==, "POST");
else
g_assert_cmpstr(method, ==, "GET");
if (!g_strcmp0(scheme, "headers")) {
auto* headers = webkit_uri_scheme_request_get_http_headers(request);
g_assert_cmpstr(soup_message_headers_get_one(headers, "x-test"), ==, "A");
g_assert_cmpstr(soup_message_headers_get_list(headers, "x-test2"), ==, "1, 2, 3, 4");
}
const URISchemeHandler& handler = test->m_handlersMap.get(String::fromUTF8(scheme));
GRefPtr<GInputStream> inputStream = adoptGRef(g_memory_input_stream_new());
test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(inputStream.get()));
const gchar* requestPath = webkit_uri_scheme_request_get_path(request);
if (!g_strcmp0(scheme, "error")) {
if (!g_strcmp0(requestPath, "before-response")) {
GUniquePtr<GError> error(g_error_new_literal(g_quark_from_string(errorDomain), errorCode, beforeReceiveResponseErrorMessage));
// We call finish() and then finish_error() to make sure that not even
// the didReceiveResponse message is processed at the time of failing.
webkit_uri_scheme_request_finish(request, G_INPUT_STREAM(inputStream.get()), handler.replyLength, handler.mimeType.data());
webkit_uri_scheme_request_finish_error(request, error.get());
} else if (!g_strcmp0(requestPath, "after-first-chunk")) {
g_memory_input_stream_add_data(G_MEMORY_INPUT_STREAM(inputStream.get()), handler.reply.data(), handler.reply.length(), 0);
webkit_uri_scheme_request_finish(request, inputStream.get(), handler.replyLength, handler.mimeType.data());
// We need to wait until we reach the load-committed state before calling webkit_uri_scheme_request_finish_error(),
// so we rely on the test using finishOnCommittedAndWaitUntilLoadFinished() to actually call it from loadCommitted().
} else {
GUniquePtr<GError> error(g_error_new_literal(g_quark_from_string(errorDomain), errorCode, genericErrorMessage));
webkit_uri_scheme_request_finish_error(request, error.get());
}
return;
}
if (!g_strcmp0(scheme, "echo")) {
char* replyHTML = g_strdup_printf(handler.reply.data(), requestPath);
g_memory_input_stream_add_data(G_MEMORY_INPUT_STREAM(inputStream.get()), replyHTML, strlen(replyHTML), g_free);
} else if (!g_strcmp0(scheme, "closed"))
g_input_stream_close(inputStream.get(), 0, 0);
else if (!handler.reply.isNull())
g_memory_input_stream_add_data(G_MEMORY_INPUT_STREAM(inputStream.get()), handler.reply.data(), handler.reply.length(), 0);
auto response = adoptGRef(webkit_uri_scheme_response_new(inputStream.get(), handler.replyLength));
webkit_uri_scheme_response_set_status(response.get(), handler.statusCode, nullptr);
webkit_uri_scheme_response_set_content_type(response.get(), handler.mimeType.data());
if (!g_strcmp0(scheme, "headersresp")) {
auto* headers = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE);
soup_message_headers_append(headers, "x-test", "test_value");
webkit_uri_scheme_response_set_http_headers(response.get(), headers);
}
webkit_uri_scheme_request_finish_with_response(request, response.get());
}
void registerURISchemeHandler(const char* scheme, const char* reply, int replyLength, const char* mimeType, int statusCode = 200)
{
m_handlersMap.set(String::fromUTF8(scheme), URISchemeHandler(reply, replyLength, mimeType, statusCode));
webkit_web_context_register_uri_scheme(m_webContext.get(), scheme, uriSchemeRequestCallback, this, 0);
}
GRefPtr<WebKitURISchemeRequest> m_uriSchemeRequest;
HashMap<String, URISchemeHandler> m_handlersMap;
bool m_uriSchemeRequestCallbackUsesTestWebView { true };
int m_loadCounter { 0 };
};
String generateHTMLContent(unsigned contentLength)
{
String baseString("abcdefghijklmnopqrstuvwxyz0123457890");
unsigned baseLength = baseString.length();
StringBuilder builder;
builder.append("<html><body>");
if (contentLength <= baseLength)
builder.appendSubstring(baseString, 0, contentLength);
else {
unsigned currentLength = 0;
while (currentLength < contentLength) {
if ((currentLength + baseLength) <= contentLength)
builder.append(baseString);
else
builder.appendSubstring(baseString, 0, contentLength - currentLength);
// Account for the 12 characters of the '<html><body>' prefix.
currentLength = builder.length() - 12;
}
}
builder.append("</body></html>");
return builder.toString();
}
static GRefPtr<WebKitWebView> createTestWebViewWithWebContext(WebKitWebContext* context)
{
WebKitWebView* view = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
#if PLATFORM(WPE)
"backend", Test::createWebViewBackend(),
#endif
"web-context", context,
nullptr));
#if PLATFORM(GTK)
g_object_ref_sink(view);
#endif
return adoptGRef(view);
}
static void testWebContextURIScheme(URISchemeTest* test, gconstpointer)
{
test->registerURISchemeHandler("foo", kBarHTML, strlen(kBarHTML), "text/html");
test->loadURI("foo:blank");
test->waitUntilLoadFinished();
size_t mainResourceDataSize = 0;
const char* mainResourceData = test->mainResourceData(mainResourceDataSize);
g_assert_cmpint(mainResourceDataSize, ==, strlen(kBarHTML));
g_assert_cmpint(strncmp(mainResourceData, kBarHTML, mainResourceDataSize), ==, 0);
test->registerURISchemeHandler("echo", kEchoHTMLFormat, -1, "text/html");
test->loadURI("echo:hello-world");
test->waitUntilLoadFinished();
GUniquePtr<char> echoHTML(g_strdup_printf(kEchoHTMLFormat, webkit_uri_scheme_request_get_path(test->m_uriSchemeRequest.get())));
mainResourceDataSize = 0;
mainResourceData = test->mainResourceData(mainResourceDataSize);
g_assert_cmpint(mainResourceDataSize, ==, strlen(echoHTML.get()));
g_assert_cmpint(strncmp(mainResourceData, echoHTML.get(), mainResourceDataSize), ==, 0);
test->loadURI("echo:with#fragment");
test->waitUntilLoadFinished();
g_assert_cmpstr(webkit_uri_scheme_request_get_path(test->m_uriSchemeRequest.get()), ==, "with");
g_assert_cmpstr(webkit_uri_scheme_request_get_uri(test->m_uriSchemeRequest.get()), ==, "echo:with#fragment");
echoHTML.reset(g_strdup_printf(kEchoHTMLFormat, webkit_uri_scheme_request_get_path(test->m_uriSchemeRequest.get())));
mainResourceDataSize = 0;
mainResourceData = test->mainResourceData(mainResourceDataSize);
g_assert_cmpint(mainResourceDataSize, ==, strlen(echoHTML.get()));
g_assert_cmpint(strncmp(mainResourceData, echoHTML.get(), mainResourceDataSize), ==, 0);
test->registerURISchemeHandler("nomime", kBarHTML, -1, nullptr);
test->m_loadEvents.clear();
test->loadURI("nomime:foo-bar");
test->waitUntilLoadFinished();
g_assert_true(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
static const char* charsetHTML = "<html><body><p id='emoji'>🙂</p></body></html>";
test->registerURISchemeHandler("charset", charsetHTML, -1, "text/html; charset=\"UTF-8\"");
test->loadURI("charset:test");
test->waitUntilLoadFinished();
mainResourceDataSize = 0;
mainResourceData = test->mainResourceData(mainResourceDataSize);
g_assert_cmpint(mainResourceDataSize, ==, strlen(charsetHTML));
g_assert_cmpint(strncmp(mainResourceData, charsetHTML, mainResourceDataSize), ==, 0);
GUniqueOutPtr<GError> error;
auto* javascriptResult = test->runJavaScriptAndWaitUntilFinished("document.getElementById('emoji').innerText", &error.outPtr());
g_assert_nonnull(javascriptResult);
g_assert_no_error(error.get());
GUniquePtr<char> emoji(WebViewTest::javascriptResultToCString(javascriptResult));
g_assert_cmpstr(emoji.get(), ==, "🙂");
test->registerURISchemeHandler("empty", nullptr, 0, "text/html");
test->m_loadEvents.clear();
test->loadURI("empty:nothing");
test->waitUntilLoadFinished();
g_assert_false(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
g_assert_false(test->m_loadEvents.contains(LoadTrackingTest::LoadFailed));
// Anything over 8192 bytes will get multiple calls to g_input_stream_read_async in
// WebKitURISchemeRequest when reading data, but we still need way more than that to
// ensure that we reach the load-committed state before failing, so we use an 8MB HTML.
String longHTMLContent = generateHTMLContent(8 * 1024 * 1024);
test->registerURISchemeHandler("error", longHTMLContent.utf8().data(), -1, "text/html");
test->m_loadEvents.clear();
test->loadURI("error:error");
test->waitUntilLoadFinished();
g_assert_true(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
g_assert_true(test->m_loadFailed);
g_assert_error(test->m_error.get(), g_quark_from_string(errorDomain), errorCode);
g_assert_cmpstr(test->m_error->message, ==, genericErrorMessage);
test->m_loadEvents.clear();
test->loadURI("error:before-response");
test->waitUntilLoadFinished();
g_assert_true(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
g_assert_true(test->m_loadFailed);
g_assert_error(test->m_error.get(), g_quark_from_string(errorDomain), errorCode);
g_assert_cmpstr(test->m_error->message, ==, beforeReceiveResponseErrorMessage);
test->registerURISchemeHandler("closed", nullptr, 0, nullptr);
test->m_loadEvents.clear();
test->loadURI("closed:input-stream");
test->waitUntilLoadFinished();
g_assert_true(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
g_assert_true(test->m_loadFailed);
g_assert_error(test->m_error.get(), G_IO_ERROR, G_IO_ERROR_CLOSED);
test->registerURISchemeHandler("notfound", kBarHTML, strlen(kBarHTML), "text/html", 404);
test->m_loadEvents.clear();
test->loadURI("notfound:blank");
test->waitUntilLoadFinished();
g_assert_false(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
test->registerURISchemeHandler("nocontent", nullptr, 0, "application/json", 204);
test->m_loadEvents.clear();
test->loadURI("nocontent:blank");
test->waitUntilLoadFinished();
g_assert_false(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
g_assert_false(test->m_loadEvents.contains(LoadTrackingTest::LoadFailed));
static const char* formHTML = "<html><body><form id=\"test-form\" method=\"POST\" action=\"post:data\"></form></body></html>";
test->registerURISchemeHandler("post", nullptr, 0, "application/json", 204);
test->m_loadEvents.clear();
test->loadHtml(formHTML, "post:form");
test->waitUntilLoadFinished();
GUniqueOutPtr<GError> postError;
test->runJavaScriptAndWaitUntilFinished("document.getElementById('test-form').submit()", &postError.outPtr());
g_assert_no_error(postError.get());
test->waitUntilLoadFinished();
g_assert_false(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
g_assert_false(test->m_loadEvents.contains(LoadTrackingTest::LoadFailed));
static const char* headersHTML = "<html><body><script>let hdrs = new Headers({'X-Test': 'A', 'X-Test2': '1, 2, 3'});hdrs.append('X-Test2', '4');fetch('headers:data', {headers: hdrs})</script></body></html>";
test->registerURISchemeHandler("headers", nullptr, 0, "application/json", 204);
test->m_loadEvents.clear();
test->loadHtml(headersHTML, "headers:form");
test->waitUntilLoadFinished();
g_assert_false(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
g_assert_false(test->m_loadEvents.contains(LoadTrackingTest::LoadFailed));
static const char* respHTML = "<html><head><script>fetch('headersresp:data').then((d)=>{if(d.headers.get('X-Test') !== 'test_value') window.hasError=1}).catch((e)=> window.hasError=1)</script></head></html>";
test->registerURISchemeHandler("headersresp", nullptr, 0, "application/json", 204);
test->m_loadEvents.clear();
test->loadHtml(respHTML, "headersresp:form");
test->waitUntilLoadFinished();
GUniqueOutPtr<GError> respError;
test->runJavaScriptAndWaitUntilFinished("if(window.hasError) throw 'Headers are missing or invalid'", &respError.outPtr());
g_assert_no_error(respError.get());
g_assert_false(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
g_assert_false(test->m_loadEvents.contains(LoadTrackingTest::LoadFailed));
// Torture test time: make sure it still works if we issue a bunch of different requests all at
// once. Each request should finish and return exactly the same data.
int numIterations = 25;
GRefPtr<WebKitWebView> views[numIterations];
test->m_uriSchemeRequestCallbackUsesTestWebView = false;
for (int i = 0; i < numIterations; i++) {
views[i] = createTestWebViewWithWebContext(test->m_webContext.get());
test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(views[i].get()));
webkit_web_view_load_uri(views[i].get(), "foo:blank");
g_signal_connect(views[i].get(), "load-changed", G_CALLBACK(+[] (WebKitWebView* webView, WebKitLoadEvent loadEvent, gpointer userData) {
auto* test = static_cast<URISchemeTest*>(userData);
if (loadEvent != WEBKIT_LOAD_FINISHED)
return;
test->m_loadCounter--;
if (!test->m_loadCounter)
g_main_loop_quit(test->m_mainLoop);
}), test);
}
test->m_loadCounter = numIterations;
g_main_loop_run(test->m_mainLoop);
for (int i = 0; i < numIterations; i++) {
WebKitWebResource* resource = webkit_web_view_get_main_resource(views[i].get());
g_assert_nonnull(resource);
webkit_web_resource_get_data(resource, nullptr, +[] (GObject* object, GAsyncResult* result, gpointer userData) {
auto* test = static_cast<URISchemeTest*>(userData);
gsize dataSize;
unsigned char* data = webkit_web_resource_get_data_finish(WEBKIT_WEB_RESOURCE(object), result, &dataSize, nullptr);
g_assert_cmpint(strncmp(reinterpret_cast<char*>(data), kBarHTML, dataSize), ==, 0);
g_main_loop_quit(test->m_mainLoop);
}, test);
g_main_loop_run(test->m_mainLoop);
}
test->m_uriSchemeRequestCallbackUsesTestWebView = true;
}
#if PLATFORM(GTK)
static void testWebContextSpellChecker(Test* test, gconstpointer)
{
WebKitWebContext* webContext = test->m_webContext.get();
// Check what happens if no spell checking language has been set.
const gchar* const* currentLanguage = webkit_web_context_get_spell_checking_languages(webContext);
g_assert_null(currentLanguage);
// Set the language to a specific one.
GRefPtr<GPtrArray> languages = adoptGRef(g_ptr_array_new());
g_ptr_array_add(languages.get(), const_cast<gpointer>(static_cast<const void*>("en_US")));
g_ptr_array_add(languages.get(), 0);
webkit_web_context_set_spell_checking_languages(webContext, reinterpret_cast<const char* const*>(languages->pdata));
currentLanguage = webkit_web_context_get_spell_checking_languages(webContext);
g_assert_cmpuint(g_strv_length(const_cast<char**>(currentLanguage)), ==, 1);
g_assert_cmpstr(currentLanguage[0], ==, "en_US");
// Set the language string to list of valid languages.
g_ptr_array_remove_index_fast(languages.get(), languages->len - 1);
g_ptr_array_add(languages.get(), const_cast<gpointer>(static_cast<const void*>("en_GB")));
g_ptr_array_add(languages.get(), 0);
webkit_web_context_set_spell_checking_languages(webContext, reinterpret_cast<const char* const*>(languages->pdata));
currentLanguage = webkit_web_context_get_spell_checking_languages(webContext);
g_assert_cmpuint(g_strv_length(const_cast<char**>(currentLanguage)), ==, 2);
g_assert_cmpstr(currentLanguage[0], ==, "en_US");
g_assert_cmpstr(currentLanguage[1], ==, "en_GB");
// Try passing a wrong language along with good ones.
g_ptr_array_remove_index_fast(languages.get(), languages->len - 1);
g_ptr_array_add(languages.get(), const_cast<gpointer>(static_cast<const void*>("bd_WR")));
g_ptr_array_add(languages.get(), 0);
webkit_web_context_set_spell_checking_languages(webContext, reinterpret_cast<const char* const*>(languages->pdata));
currentLanguage = webkit_web_context_get_spell_checking_languages(webContext);
g_assert_cmpuint(g_strv_length(const_cast<char**>(currentLanguage)), ==, 2);
g_assert_cmpstr(currentLanguage[0], ==, "en_US");
g_assert_cmpstr(currentLanguage[1], ==, "en_GB");
// Try passing a list with only wrong languages.
languages = adoptGRef(g_ptr_array_new());
g_ptr_array_add(languages.get(), const_cast<gpointer>(static_cast<const void*>("bd_WR")));
g_ptr_array_add(languages.get(), const_cast<gpointer>(static_cast<const void*>("wr_BD")));
g_ptr_array_add(languages.get(), 0);
webkit_web_context_set_spell_checking_languages(webContext, reinterpret_cast<const char* const*>(languages->pdata));
currentLanguage = webkit_web_context_get_spell_checking_languages(webContext);
g_assert_null(currentLanguage);
// Check disabling and re-enabling spell checking.
webkit_web_context_set_spell_checking_enabled(webContext, FALSE);
g_assert_false(webkit_web_context_get_spell_checking_enabled(webContext));
webkit_web_context_set_spell_checking_enabled(webContext, TRUE);
g_assert_true(webkit_web_context_get_spell_checking_enabled(webContext));
}
#endif // PLATFORM(GTK)
static void testWebContextLanguages(WebViewTest* test, gconstpointer)
{
static const char* expectedDefaultLanguage = "en-US";
test->loadURI(kServer->getURIForPath("/").data());
test->waitUntilLoadFinished();
size_t mainResourceDataSize = 0;
const char* mainResourceData = test->mainResourceData(mainResourceDataSize);
g_assert_cmpuint(mainResourceDataSize, ==, strlen(expectedDefaultLanguage));
g_assert_cmpint(strncmp(mainResourceData, expectedDefaultLanguage, mainResourceDataSize), ==, 0);
GRefPtr<GPtrArray> languages = adoptGRef(g_ptr_array_new());
g_ptr_array_add(languages.get(), const_cast<gpointer>(static_cast<const void*>("en")));
g_ptr_array_add(languages.get(), const_cast<gpointer>(static_cast<const void*>("ES_es")));
g_ptr_array_add(languages.get(), const_cast<gpointer>(static_cast<const void*>("dE")));
g_ptr_array_add(languages.get(), 0);
webkit_web_context_set_preferred_languages(test->m_webContext.get(), reinterpret_cast<const char* const*>(languages->pdata));
static const char* expectedLanguages = "en,ES-es;q=0.90,dE;q=0.80";
test->loadURI(kServer->getURIForPath("/").data());
test->waitUntilLoadFinished();
mainResourceDataSize = 0;
mainResourceData = test->mainResourceData(mainResourceDataSize);
g_assert_cmpuint(mainResourceDataSize, ==, strlen(expectedLanguages));
g_assert_cmpint(strncmp(mainResourceData, expectedLanguages, mainResourceDataSize), ==, 0);
// When using the C locale, en-US should be used as default.
const char* cLanguage[] = { "C", nullptr };
webkit_web_context_set_preferred_languages(test->m_webContext.get(), cLanguage);
GUniqueOutPtr<GError> error;
WebKitJavascriptResult* javascriptResult = test->runJavaScriptAndWaitUntilFinished("Intl.DateTimeFormat().resolvedOptions().locale", &error.outPtr());
g_assert_nonnull(javascriptResult);
g_assert_no_error(error.get());
GUniquePtr<char> locale(WebViewTest::javascriptResultToCString(javascriptResult));
g_assert_cmpstr(locale.get(), ==, expectedDefaultLanguage);
// When using the POSIX locale, en-US should be used as default.
const char* posixLanguage[] = { "POSIX", nullptr };
webkit_web_context_set_preferred_languages(test->m_webContext.get(), posixLanguage);
javascriptResult = test->runJavaScriptAndWaitUntilFinished("Intl.DateTimeFormat().resolvedOptions().locale", &error.outPtr());
g_assert_nonnull(javascriptResult);
g_assert_no_error(error.get());
locale.reset(WebViewTest::javascriptResultToCString(javascriptResult));
g_assert_cmpstr(locale.get(), ==, expectedDefaultLanguage);
// An invalid locale should not be used.
const char* invalidLanguage[] = { "A", nullptr };
webkit_web_context_set_preferred_languages(test->m_webContext.get(), invalidLanguage);
javascriptResult = test->runJavaScriptAndWaitUntilFinished("Intl.DateTimeFormat().resolvedOptions().locale", &error.outPtr());
g_assert_nonnull(javascriptResult);
g_assert_no_error(error.get());
locale.reset(WebViewTest::javascriptResultToCString(javascriptResult));
g_assert_cmpstr(locale.get(), !=, "A");
}
#if USE(SOUP2)
static void serverCallback(SoupServer* server, SoupMessage* message, const char* path, GHashTable*, SoupClientContext* context, gpointer)
#else
static void serverCallback(SoupServer* server, SoupServerMessage* message, const char* path, GHashTable*, gpointer)
#endif
{
if (soup_server_message_get_method(message) != SOUP_METHOD_GET) {
soup_server_message_set_status(message, SOUP_STATUS_NOT_IMPLEMENTED, nullptr);
return;
}
auto* responseBody = soup_server_message_get_response_body(message);
if (g_str_equal(path, "/")) {
const char* acceptLanguage = soup_message_headers_get_one(soup_server_message_get_request_headers(message), "Accept-Language");
soup_server_message_set_status(message, SOUP_STATUS_OK, nullptr);
soup_message_body_append(responseBody, SOUP_MEMORY_COPY, acceptLanguage, strlen(acceptLanguage));
soup_message_body_complete(responseBody);
} else if (g_str_equal(path, "/empty")) {
const char* emptyHTML = "<html><body></body></html>";
soup_message_body_append(responseBody, SOUP_MEMORY_STATIC, emptyHTML, strlen(emptyHTML));
soup_message_body_complete(responseBody);
soup_server_message_set_status(message, SOUP_STATUS_OK, nullptr);
} else if (g_str_equal(path, "/echoPort")) {
#if USE(SOUP2)
char* port = g_strdup_printf("%u", g_inet_socket_address_get_port(G_INET_SOCKET_ADDRESS(soup_client_context_get_local_address(context))));
#else
char* port = g_strdup_printf("%u", g_inet_socket_address_get_port(G_INET_SOCKET_ADDRESS(soup_server_message_get_local_address(message))));
#endif
soup_message_body_append(responseBody, SOUP_MEMORY_TAKE, port, strlen(port));
soup_message_body_complete(responseBody);
soup_server_message_set_status(message, SOUP_STATUS_OK, nullptr);
} else
soup_server_message_set_status(message, SOUP_STATUS_NOT_FOUND, nullptr);
}
class SecurityPolicyTest: public Test {
public:
MAKE_GLIB_TEST_FIXTURE(SecurityPolicyTest);
enum SecurityPolicy {
Local = 1 << 1,
NoAccess = 1 << 2,
DisplayIsolated = 1 << 3,
Secure = 1 << 4,
CORSEnabled = 1 << 5,
EmptyDocument = 1 << 6
};
SecurityPolicyTest()
: m_manager(webkit_web_context_get_security_manager(m_webContext.get()))
{
}
void verifyThatSchemeMatchesPolicy(const char* scheme, unsigned policy)
{
if (policy & Local)
g_assert_true(webkit_security_manager_uri_scheme_is_local(m_manager, scheme));
else
g_assert_false(webkit_security_manager_uri_scheme_is_local(m_manager, scheme));
if (policy & NoAccess)
g_assert_true(webkit_security_manager_uri_scheme_is_no_access(m_manager, scheme));
else
g_assert_false(webkit_security_manager_uri_scheme_is_no_access(m_manager, scheme));
if (policy & DisplayIsolated)
g_assert_true(webkit_security_manager_uri_scheme_is_display_isolated(m_manager, scheme));
else
g_assert_false(webkit_security_manager_uri_scheme_is_display_isolated(m_manager, scheme));
if (policy & Secure)
g_assert_true(webkit_security_manager_uri_scheme_is_secure(m_manager, scheme));
else
g_assert_false(webkit_security_manager_uri_scheme_is_secure(m_manager, scheme));
if (policy & CORSEnabled)
g_assert_true(webkit_security_manager_uri_scheme_is_cors_enabled(m_manager, scheme));
else
g_assert_false(webkit_security_manager_uri_scheme_is_cors_enabled(m_manager, scheme));
if (policy & EmptyDocument)
g_assert_true(webkit_security_manager_uri_scheme_is_empty_document(m_manager, scheme));
else
g_assert_false(webkit_security_manager_uri_scheme_is_empty_document(m_manager, scheme));
}
WebKitSecurityManager* m_manager;
};
static void testWebContextSecurityPolicy(SecurityPolicyTest* test, gconstpointer)
{
// VerifyThatSchemeMatchesPolicy default policy for well known schemes.
test->verifyThatSchemeMatchesPolicy("http", SecurityPolicyTest::CORSEnabled);
test->verifyThatSchemeMatchesPolicy("https", SecurityPolicyTest::CORSEnabled | SecurityPolicyTest::Secure);
test->verifyThatSchemeMatchesPolicy("file", SecurityPolicyTest::Local);
test->verifyThatSchemeMatchesPolicy("data", SecurityPolicyTest::NoAccess | SecurityPolicyTest::Secure);
test->verifyThatSchemeMatchesPolicy("about", SecurityPolicyTest::NoAccess | SecurityPolicyTest::Secure | SecurityPolicyTest::EmptyDocument);
// Custom scheme.
test->verifyThatSchemeMatchesPolicy("foo", 0);
webkit_security_manager_register_uri_scheme_as_local(test->m_manager, "foo");
test->verifyThatSchemeMatchesPolicy("foo", SecurityPolicyTest::Local);
webkit_security_manager_register_uri_scheme_as_no_access(test->m_manager, "foo");
test->verifyThatSchemeMatchesPolicy("foo", SecurityPolicyTest::Local | SecurityPolicyTest::NoAccess);
webkit_security_manager_register_uri_scheme_as_display_isolated(test->m_manager, "foo");
test->verifyThatSchemeMatchesPolicy("foo", SecurityPolicyTest::Local | SecurityPolicyTest::NoAccess | SecurityPolicyTest::DisplayIsolated);
webkit_security_manager_register_uri_scheme_as_secure(test->m_manager, "foo");
test->verifyThatSchemeMatchesPolicy("foo", SecurityPolicyTest::Local | SecurityPolicyTest::NoAccess | SecurityPolicyTest::DisplayIsolated | SecurityPolicyTest::Secure);
webkit_security_manager_register_uri_scheme_as_cors_enabled(test->m_manager, "foo");
test->verifyThatSchemeMatchesPolicy("foo", SecurityPolicyTest::Local | SecurityPolicyTest::NoAccess | SecurityPolicyTest::DisplayIsolated | SecurityPolicyTest::Secure
| SecurityPolicyTest::CORSEnabled);
webkit_security_manager_register_uri_scheme_as_empty_document(test->m_manager, "foo");
test->verifyThatSchemeMatchesPolicy("foo", SecurityPolicyTest::Local | SecurityPolicyTest::NoAccess | SecurityPolicyTest::DisplayIsolated | SecurityPolicyTest::Secure
| SecurityPolicyTest::CORSEnabled | SecurityPolicyTest::EmptyDocument);
}
static void consoleMessageReceivedCallback(WebKitUserContentManager*, WebKitJavascriptResult* message, Vector<WebKitJavascriptResult*>* result)
{
g_assert_nonnull(message);
g_assert_nonnull(result);
result->append(webkit_javascript_result_ref(message));
}
static void testWebContextSecurityFileXHR(WebViewTest* test, gconstpointer)
{
GUniquePtr<char> fileURL(g_strdup_printf("file://%s/simple.html", Test::getResourcesDir(Test::WebKit2Resources).data()));
test->loadURI(fileURL.get());
test->waitUntilLoadFinished();
GUniquePtr<char> jsonURL(g_strdup_printf("file://%s/simple.json", Test::getResourcesDir().data()));
GUniquePtr<char> xhr(g_strdup_printf("var xhr = new XMLHttpRequest; xhr.open(\"GET\", \"%s\"); xhr.send();", jsonURL.get()));
Vector<WebKitJavascriptResult*> consoleMessages;
webkit_user_content_manager_register_script_message_handler(test->m_userContentManager.get(), "console");
g_signal_connect(test->m_userContentManager.get(), "script-message-received::console", G_CALLBACK(consoleMessageReceivedCallback), &consoleMessages);
// By default file access is not allowed, this will show a console message with a cross-origin error.
GUniqueOutPtr<GError> error;
WebKitJavascriptResult* javascriptResult = test->runJavaScriptAndWaitUntilFinished(xhr.get(), &error.outPtr());
g_assert_nonnull(javascriptResult);
g_assert_no_error(error.get());
g_assert_cmpuint(consoleMessages.size(), ==, 2);
Vector<GUniquePtr<char>, 2> expectedMessages;
expectedMessages.append(g_strdup("Cross origin requests are only supported for HTTP."));
expectedMessages.append(g_strdup_printf("XMLHttpRequest cannot load %s due to access control checks.", jsonURL.get()));
unsigned i = 0;
for (auto* consoleMessage : consoleMessages) {
g_assert_nonnull(consoleMessage);
GUniquePtr<char> messageString(WebViewTest::javascriptResultToCString(consoleMessage));
GRefPtr<GVariant> variant = g_variant_parse(G_VARIANT_TYPE("(uusus)"), messageString.get(), nullptr, nullptr, nullptr);
g_assert_nonnull(variant.get());
unsigned level;
const char* messageText;
g_variant_get(variant.get(), "(uu&su&s)", nullptr, &level, &messageText, nullptr, nullptr);
g_assert_cmpuint(level, ==, 3); // Console error message.
g_assert_cmpstr(messageText, ==, expectedMessages[i++].get());
webkit_javascript_result_unref(consoleMessage);
}
consoleMessages.clear();
// Allow file access from file URLs.
webkit_settings_set_allow_file_access_from_file_urls(webkit_web_view_get_settings(test->m_webView), TRUE);
test->loadURI(fileURL.get());
test->waitUntilLoadFinished();
javascriptResult = test->runJavaScriptAndWaitUntilFinished(xhr.get(), &error.outPtr());
g_assert_nonnull(javascriptResult);
g_assert_no_error(error.get());
// It isn't still possible to load file from an HTTP URL.
test->loadURI(kServer->getURIForPath("/").data());
test->waitUntilLoadFinished();
javascriptResult = test->runJavaScriptAndWaitUntilFinished(xhr.get(), &error.outPtr());
g_assert_nonnull(javascriptResult);
g_assert_no_error(error.get());
i = 0;
for (auto* consoleMessage : consoleMessages) {
g_assert_nonnull(consoleMessage);
GUniquePtr<char> messageString(WebViewTest::javascriptResultToCString(consoleMessage));
GRefPtr<GVariant> variant = g_variant_parse(G_VARIANT_TYPE("(uusus)"), messageString.get(), nullptr, nullptr, nullptr);
g_assert_nonnull(variant.get());
unsigned level;
const char* messageText;
g_variant_get(variant.get(), "(uu&su&s)", nullptr, &level, &messageText, nullptr, nullptr);
g_assert_cmpuint(level, ==, 3); // Console error message.
g_assert_cmpstr(messageText, ==, expectedMessages[i++].get());
webkit_javascript_result_unref(consoleMessage);
}
consoleMessages.clear();
g_signal_handlers_disconnect_matched(test->m_userContentManager.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, &consoleMessages);
webkit_user_content_manager_unregister_script_message_handler(test->m_userContentManager.get(), "console");
webkit_settings_set_allow_file_access_from_file_urls(webkit_web_view_get_settings(test->m_webView), FALSE);
}
class ProxyTest : public WebViewTest {
public:
MAKE_GLIB_TEST_FIXTURE(ProxyTest);
#if SOUP_CHECK_VERSION(2, 61, 90)
enum class WebSocketServerType {
Unknown,
NoProxy,
Proxy
};
#if USE(SOUP2)
static void webSocketProxyServerCallback(SoupServer*, SoupWebsocketConnection*, const char* path, SoupClientContext*, gpointer userData)
#else
static void webSocketProxyServerCallback(SoupServer*, SoupServerMessage*, const char* path, SoupWebsocketConnection*, gpointer userData)
#endif
{
static_cast<ProxyTest*>(userData)->webSocketConnected(ProxyTest::WebSocketServerType::Proxy);
}
#endif
ProxyTest()
{
// This "proxy server" is actually just a different instance of the main
// test server (kServer), listening on a different port. Requests
// will not actually be proxied to kServer because proxyServer is not
// actually a proxy server. We're testing whether the proxy settings
// work, not whether we can write a soup proxy server.
m_proxyServer.run(serverCallback);
g_assert_false(m_proxyServer.baseURL().isNull());
#if SOUP_CHECK_VERSION(2, 61, 90)
m_proxyServer.addWebSocketHandler(webSocketProxyServerCallback, this);
g_assert_false(m_proxyServer.baseWebSocketURL().isNull());
#endif
}
CString loadURIAndGetMainResourceData(const char* uri)
{
loadURI(uri);
waitUntilLoadFinished();
size_t dataSize = 0;
const char* data = mainResourceData(dataSize);
return CString(data, dataSize);
}
GUniquePtr<char> proxyServerPortAsString()
{
GUniquePtr<char> port(g_strdup_printf("%u", m_proxyServer.port()));
return port;
}
#if SOUP_CHECK_VERSION(2, 61, 90)
void webSocketConnected(WebSocketServerType serverType)
{
m_webSocketRequestReceived = serverType;
quitMainLoop();
}
WebSocketServerType createWebSocketAndWaitUntilConnected()
{
m_webSocketRequestReceived = WebSocketServerType::Unknown;
GUniquePtr<char> createWebSocket(g_strdup_printf("var ws = new WebSocket('%s');", kServer->getWebSocketURIForPath("/foo").data()));
webkit_web_view_run_javascript(m_webView, createWebSocket.get(), nullptr, nullptr, nullptr);
g_main_loop_run(m_mainLoop);
return m_webSocketRequestReceived;
}
#endif
WebKitTestServer m_proxyServer;
#if SOUP_CHECK_VERSION(2, 61, 90)
WebSocketServerType m_webSocketRequestReceived { WebSocketServerType::Unknown };
#endif
};
#if SOUP_CHECK_VERSION(2, 61, 90)
#if USE(SOUP2)
static void webSocketServerCallback(SoupServer*, SoupWebsocketConnection*, const char*, SoupClientContext*, gpointer userData)
#else
static void webSocketServerCallback(SoupServer*, SoupServerMessage*, const char*, SoupWebsocketConnection*, gpointer userData)
#endif
{
static_cast<ProxyTest*>(userData)->webSocketConnected(ProxyTest::WebSocketServerType::NoProxy);
}
#endif
static void ephemeralViewloadChanged(WebKitWebView* webView, WebKitLoadEvent loadEvent, WebViewTest* test)
{
if (loadEvent != WEBKIT_LOAD_FINISHED)
return;
g_signal_handlers_disconnect_by_func(webView, reinterpret_cast<void*>(ephemeralViewloadChanged), test);
test->quitMainLoop();
}
static void testWebContextProxySettings(ProxyTest* test, gconstpointer)
{
// Proxy URI is unset by default. Requests to kServer should be received by kServer.
GUniquePtr<char> serverPortAsString(g_strdup_printf("%u", kServer->port()));
auto mainResourceData = test->loadURIAndGetMainResourceData(kServer->getURIForPath("/echoPort").data());
ASSERT_CMP_CSTRING(mainResourceData, ==, serverPortAsString.get());
#if SOUP_CHECK_VERSION(2, 61, 90)
// WebSocket requests should also be received by kServer.
kServer->addWebSocketHandler(webSocketServerCallback, test);
auto serverType = test->createWebSocketAndWaitUntilConnected();
g_assert_true(serverType == ProxyTest::WebSocketServerType::NoProxy);
#endif
// Set default proxy URI to point to proxyServer. Requests to kServer should be received by proxyServer instead.
WebKitNetworkProxySettings* settings = webkit_network_proxy_settings_new(test->m_proxyServer.baseURL().string().utf8().data(), nullptr);
auto* dataManager = webkit_web_context_get_website_data_manager(test->m_webContext.get());
webkit_website_data_manager_set_network_proxy_settings(dataManager, WEBKIT_NETWORK_PROXY_MODE_CUSTOM, settings);
GUniquePtr<char> proxyServerPortAsString = test->proxyServerPortAsString();
mainResourceData = test->loadURIAndGetMainResourceData(kServer->getURIForPath("/echoPort").data());
ASSERT_CMP_CSTRING(mainResourceData, ==, proxyServerPortAsString.get());
webkit_network_proxy_settings_free(settings);
#if SOUP_CHECK_VERSION(2, 61, 90)
// WebSocket requests should also be received by proxyServer.
serverType = test->createWebSocketAndWaitUntilConnected();
g_assert_true(serverType == ProxyTest::WebSocketServerType::Proxy);
#endif
// Proxy settings also affect ephemeral web views.
auto webView = Test::adoptView(g_object_new(WEBKIT_TYPE_WEB_VIEW,
#if PLATFORM(WPE)
"backend", Test::createWebViewBackend(),
#endif
"web-context", test->m_webContext.get(),
"is-ephemeral", TRUE,
nullptr));
g_assert_true(webkit_web_view_is_ephemeral(webView.get()));
g_assert_false(webkit_web_context_is_ephemeral(webkit_web_view_get_context(webView.get())));
g_signal_connect(webView.get(), "load-changed", G_CALLBACK(ephemeralViewloadChanged), test);
webkit_web_view_load_uri(webView.get(), kServer->getURIForPath("/echoPort").data());
g_main_loop_run(test->m_mainLoop);
WebKitWebResource* resource = webkit_web_view_get_main_resource(webView.get());
g_assert_true(WEBKIT_IS_WEB_RESOURCE(resource));
webkit_web_resource_get_data(resource, nullptr, [](GObject* object, GAsyncResult* result, gpointer userData) {
size_t dataSize;
GUniquePtr<char> data(reinterpret_cast<char*>(webkit_web_resource_get_data_finish(WEBKIT_WEB_RESOURCE(object), result, &dataSize, nullptr)));
g_assert_nonnull(data);
auto* test = static_cast<ProxyTest*>(userData);
GUniquePtr<char> proxyServerPortAsString = test->proxyServerPortAsString();
ASSERT_CMP_CSTRING(CString(data.get(), dataSize), ==, proxyServerPortAsString.get());
test->quitMainLoop();
}, test);
g_main_loop_run(test->m_mainLoop);
// Remove the proxy. Requests to kServer should be received by kServer again.
webkit_website_data_manager_set_network_proxy_settings(dataManager, WEBKIT_NETWORK_PROXY_MODE_NO_PROXY, nullptr);
mainResourceData = test->loadURIAndGetMainResourceData(kServer->getURIForPath("/echoPort").data());
ASSERT_CMP_CSTRING(mainResourceData, ==, serverPortAsString.get());
// Use a default proxy uri, but ignoring requests to localhost.
static const char* ignoreHosts[] = { "localhost", nullptr };
settings = webkit_network_proxy_settings_new(test->m_proxyServer.baseURL().string().utf8().data(), ignoreHosts);
webkit_website_data_manager_set_network_proxy_settings(dataManager, WEBKIT_NETWORK_PROXY_MODE_CUSTOM, settings);
mainResourceData = test->loadURIAndGetMainResourceData(kServer->getURIForPath("/echoPort").data());
ASSERT_CMP_CSTRING(mainResourceData, ==, proxyServerPortAsString.get());
GUniquePtr<char> localhostEchoPortURI(g_strdup_printf("http://localhost:%s/echoPort", serverPortAsString.get()));
mainResourceData = test->loadURIAndGetMainResourceData(localhostEchoPortURI.get());
ASSERT_CMP_CSTRING(mainResourceData, ==, serverPortAsString.get());
webkit_network_proxy_settings_free(settings);
// Remove the proxy again to ensure next test is not using any previous values.
webkit_website_data_manager_set_network_proxy_settings(dataManager, WEBKIT_NETWORK_PROXY_MODE_NO_PROXY, nullptr);
mainResourceData = test->loadURIAndGetMainResourceData(kServer->getURIForPath("/echoPort").data());
ASSERT_CMP_CSTRING(mainResourceData, ==, serverPortAsString.get());
// Use scheme specific proxy instead of the default.
settings = webkit_network_proxy_settings_new(nullptr, nullptr);
webkit_network_proxy_settings_add_proxy_for_scheme(settings, "http", test->m_proxyServer.baseURL().string().utf8().data());
webkit_website_data_manager_set_network_proxy_settings(dataManager, WEBKIT_NETWORK_PROXY_MODE_CUSTOM, settings);
mainResourceData = test->loadURIAndGetMainResourceData(kServer->getURIForPath("/echoPort").data());
ASSERT_CMP_CSTRING(mainResourceData, ==, proxyServerPortAsString.get());
webkit_network_proxy_settings_free(settings);
// Reset to use the default resolver.
webkit_website_data_manager_set_network_proxy_settings(dataManager, WEBKIT_NETWORK_PROXY_MODE_DEFAULT, nullptr);
mainResourceData = test->loadURIAndGetMainResourceData(kServer->getURIForPath("/echoPort").data());
ASSERT_CMP_CSTRING(mainResourceData, ==, serverPortAsString.get());
#if SOUP_CHECK_VERSION(2, 61, 90)
kServer->removeWebSocketHandler();
#endif
}
class MemoryPressureTest : public WebViewTest {
public:
MAKE_GLIB_TEST_FIXTURE_WITH_SETUP_TEARDOWN(MemoryPressureTest, setup, teardown);
static void setup()
{
Test::s_memoryPressureSettings = webkit_memory_pressure_settings_new();
webkit_memory_pressure_settings_set_memory_limit(Test::s_memoryPressureSettings, 20);
webkit_memory_pressure_settings_set_poll_interval(Test::s_memoryPressureSettings, 0.5);
webkit_memory_pressure_settings_set_kill_threshold(Test::s_memoryPressureSettings, 1);
webkit_memory_pressure_settings_set_strict_threshold(Test::s_memoryPressureSettings, 0.75);
webkit_memory_pressure_settings_set_conservative_threshold(Test::s_memoryPressureSettings, 0.5);
}
static void teardown()
{
webkit_memory_pressure_settings_free(Test::s_memoryPressureSettings);
Test::s_memoryPressureSettings = nullptr;
}
static void webProcessTerminatedCallback(WebKitWebView* webView, WebKitWebProcessTerminationReason reason, MemoryPressureTest* test)
{
test->m_terminationReason = reason;
g_signal_handlers_disconnect_by_func(webView, reinterpret_cast<void*>(webProcessTerminatedCallback), test);
g_main_loop_quit(test->m_mainLoop);
}
void waitUntilWebProcessTerminated()
{
g_signal_connect_after(m_webView, "web-process-terminated", G_CALLBACK(MemoryPressureTest::webProcessTerminatedCallback), this);
g_main_loop_run(m_mainLoop);
}
WebKitWebProcessTerminationReason m_terminationReason { WEBKIT_WEB_PROCESS_CRASHED };
};
static void testMemoryPressureSettings(MemoryPressureTest* test, gconstpointer)
{
// Before testing the settings that have been set to the context, use a new instance
// of WebKitMemoryPressureSettings to test the default values, getters and setters.
WebKitMemoryPressureSettings* settings = webkit_memory_pressure_settings_new();
// We can't exactly know the default value for the memory limit, as it depends on
// the hardware of the machine, so just ensure that it's something > 0 and <= 3GB.
guint limit = webkit_memory_pressure_settings_get_memory_limit(settings);
g_assert_cmpuint(limit, >, 0);
g_assert_cmpuint(limit * MB, <=, 3 * GB);
g_assert_cmpfloat(webkit_memory_pressure_settings_get_conservative_threshold(settings), ==, 0.33);
g_assert_cmpfloat(webkit_memory_pressure_settings_get_strict_threshold(settings), ==, 0.5);
g_assert_cmpfloat(webkit_memory_pressure_settings_get_kill_threshold(settings), ==, 0);
g_assert_cmpfloat(webkit_memory_pressure_settings_get_poll_interval(settings), ==, 30);
// Test setting and getting values.
webkit_memory_pressure_settings_set_memory_limit(settings, 20);
g_assert_cmpuint(webkit_memory_pressure_settings_get_memory_limit(settings), ==, 20);
webkit_memory_pressure_settings_set_conservative_threshold(settings, 0.4);
g_assert_cmpfloat(webkit_memory_pressure_settings_get_conservative_threshold(settings), ==, 0.4);
webkit_memory_pressure_settings_set_strict_threshold(settings, 0.6);
g_assert_cmpfloat(webkit_memory_pressure_settings_get_strict_threshold(settings), ==, 0.6);
webkit_memory_pressure_settings_set_kill_threshold(settings, 1.1);
g_assert_cmpfloat(webkit_memory_pressure_settings_get_kill_threshold(settings), ==, 1.1);
webkit_memory_pressure_settings_set_poll_interval(settings, 5);
g_assert_cmpfloat(webkit_memory_pressure_settings_get_poll_interval(settings), ==, 5);
webkit_memory_pressure_settings_free(settings);
// An empty view uses around 7MB of memory. We're setting a maximum of 20MB here, polling every 0.5 seconds,
// and the kill fraction is 1, so the web process will be killed when it exceeds 20MB.
// The test html will allocate a canvas of 3000x3000, which requires around 36MB of memory, so once it gets
// created, the MemoryPressureHandler will detect that the memory usage is too high and kill the web process.
// This triggers the web-process-terminated signal in the view with WEBKIT_WEB_PROCESS_EXCEEDED_MEMORY_LIMIT
// as the termination reason.
static const char* html =
"<html>"
" <body>"
" <script>"
" setTimeout(function() {"
" var canvas = document.getElementById('canvas');"
" canvas.width = 3000;"
" canvas.height = 3000;"
" var context = canvas.getContext('2d');"
" context.fillStyle = 'green';"
" context.fillRect(0, 0, 3000, 3000);"
" }, 0);"
" </script>"
" <canvas id='canvas'></canvas>"
" </body>"
"</html>";
test->m_expectedWebProcessCrash = true;
test->loadHtml(html, nullptr);
test->waitUntilWebProcessTerminated();
g_assert_cmpuint(test->m_terminationReason, ==, WEBKIT_WEB_PROCESS_EXCEEDED_MEMORY_LIMIT);
}
void beforeAll()
{
kServer = new WebKitTestServer();
kServer->run(serverCallback);
Test::add("WebKitWebContext", "default-context", testWebContextDefault);
Test::add("WebKitWebContext", "ephemeral", testWebContextEphemeral);
URISchemeTest::add("WebKitWebContext", "uri-scheme", testWebContextURIScheme);
// FIXME: implement spellchecker in WPE.
#if PLATFORM(GTK)
Test::add("WebKitWebContext", "spell-checker", testWebContextSpellChecker);
#endif
WebViewTest::add("WebKitWebContext", "languages", testWebContextLanguages);
SecurityPolicyTest::add("WebKitSecurityManager", "security-policy", testWebContextSecurityPolicy);
WebViewTest::add("WebKitSecurityManager", "file-xhr", testWebContextSecurityFileXHR);
ProxyTest::add("WebKitWebContext", "proxy", testWebContextProxySettings);
MemoryPressureTest::add("WebKitWebContext", "memory-pressure", testMemoryPressureSettings);
}
void afterAll()
{
delete kServer;
}