1/*
2 * Copyright (C) 2011 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#pragma once
21
22#include <cairo.h>
23#include <glib-object.h>
24#include <wtf/HashSet.h>
25#include <wtf/glib/GRefPtr.h>
26#include <wtf/glib/GUniquePtr.h>
27#include <wtf/text/CString.h>
28
29#if PLATFORM(GTK)
30#include <webkit2/webkit2.h>
31#elif PLATFORM(WPE)
32#include "HeadlessViewBackend.h"
33#include <wpe/webkit.h>
34#endif
35
36#define TEST_PATH_FORMAT "/webkit/%s/%s"
37
38#define MAKE_GLIB_TEST_FIXTURE(ClassName) \
39 static void setUp(ClassName* fixture, gconstpointer data) \
40 { \
41 new (fixture) ClassName; \
42 } \
43 static void tearDown(ClassName* fixture, gconstpointer data) \
44 { \
45 fixture->~ClassName(); \
46 } \
47 static void add(const char* suiteName, const char* testName, void (*testFunc)(ClassName*, const void*)) \
48 { \
49 GUniquePtr<gchar> testPath(g_strdup_printf(TEST_PATH_FORMAT, suiteName, testName)); \
50 g_test_add(testPath.get(), ClassName, 0, ClassName::setUp, testFunc, ClassName::tearDown); \
51 }
52
53#define MAKE_GLIB_TEST_FIXTURE_WITH_SETUP_TEARDOWN(ClassName, setup, teardown) \
54 static void setUp(ClassName* fixture, gconstpointer data) \
55 { \
56 setup(); \
57 new (fixture) ClassName; \
58 } \
59 static void tearDown(ClassName* fixture, gconstpointer data) \
60 { \
61 fixture->~ClassName(); \
62 teardown(); \
63 } \
64 static void add(const char* suiteName, const char* testName, void (*testFunc)(ClassName*, const void*)) \
65 { \
66 GUniquePtr<gchar> testPath(g_strdup_printf(TEST_PATH_FORMAT, suiteName, testName)); \
67 g_test_add(testPath.get(), ClassName, 0, ClassName::setUp, testFunc, ClassName::tearDown); \
68 }
69
70#define ASSERT_CMP_CSTRING(s1, cmp, s2) \
71 do { \
72 CString __s1 = (s1); \
73 CString __s2 = (s2); \
74 if (g_strcmp0(__s1.data(), __s2.data()) cmp 0) ; \
75 else { \
76 g_assertion_message_cmpstr(G_LOG_DOMAIN, __FILE__, __LINE__, \
77 G_STRFUNC, #s1 " " #cmp " " #s2, __s1.data(), #cmp, __s2.data()); \
78 } \
79 } while (0)
80
81#if !defined(g_assert_cmpfloat_with_epsilon)
82#define g_assert_cmpfloat_with_epsilon(n1,n2,epsilon) \
83 do { \
84 double __n1 = (n1); \
85 double __n2 = (n2); \
86 double __epsilon = (epsilon); \
87 if ((((__n1) > (__n2) ? (__n1) - (__n2) : (__n2) - (__n1)) < (__epsilon))) ; \
88 else { \
89 g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, \
90 G_STRFUNC, #n1 " == " #n2 " (+/- " #epsilon ")", __n1, "==", __n2, 'f'); \
91 } \
92 } while(0)
93#endif
94
95class Test {
96public:
97 MAKE_GLIB_TEST_FIXTURE(Test);
98
99 static GRefPtr<WebKitWebView> adoptView(gpointer view)
100 {
101 g_assert_true(WEBKIT_IS_WEB_VIEW(view));
102#if PLATFORM(GTK)
103 g_assert_true(g_object_is_floating(view));
104 return GRefPtr<WebKitWebView>(WEBKIT_WEB_VIEW(view));
105#elif PLATFORM(WPE)
106 return adoptGRef(WEBKIT_WEB_VIEW(view));
107#endif
108 }
109
110 static const char* dataDirectory();
111
112 static void initializeWebExtensionsCallback(WebKitWebContext* context, Test* test)
113 {
114 test->initializeWebExtensions();
115 }
116
117 Test()
118 {
119 GUniquePtr<char> localStorageDirectory(g_build_filename(dataDirectory(), "local-storage", nullptr));
120 GUniquePtr<char> indexedDBDirectory(g_build_filename(dataDirectory(), "indexeddb", nullptr));
121 GUniquePtr<char> diskCacheDirectory(g_build_filename(dataDirectory(), "disk-cache", nullptr));
122 GUniquePtr<char> applicationCacheDirectory(g_build_filename(dataDirectory(), "appcache", nullptr));
123 GUniquePtr<char> webSQLDirectory(g_build_filename(dataDirectory(), "websql", nullptr));
124 GRefPtr<WebKitWebsiteDataManager> websiteDataManager = adoptGRef(webkit_website_data_manager_new(
125 "local-storage-directory", localStorageDirectory.get(), "indexeddb-directory", indexedDBDirectory.get(),
126 "disk-cache-directory", diskCacheDirectory.get(), "offline-application-cache-directory", applicationCacheDirectory.get(),
127 "websql-directory", webSQLDirectory.get(), nullptr));
128
129 m_webContext = adoptGRef(webkit_web_context_new_with_website_data_manager(websiteDataManager.get()));
130 g_signal_connect(m_webContext.get(), "initialize-web-extensions", G_CALLBACK(initializeWebExtensionsCallback), this);
131 }
132
133 virtual ~Test()
134 {
135 g_signal_handlers_disconnect_matched(m_webContext.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this);
136 m_webContext = nullptr;
137 if (m_watchedObjects.isEmpty())
138 return;
139
140 g_print("Leaked objects:");
141 HashSet<GObject*>::const_iterator end = m_watchedObjects.end();
142 for (HashSet<GObject*>::const_iterator it = m_watchedObjects.begin(); it != end; ++it)
143 g_print(" %s(%p)", g_type_name_from_instance(reinterpret_cast<GTypeInstance*>(*it)), *it);
144 g_print("\n");
145
146 g_assert_true(m_watchedObjects.isEmpty());
147 }
148
149 virtual void initializeWebExtensions()
150 {
151 webkit_web_context_set_web_extensions_directory(m_webContext.get(), WEBKIT_TEST_WEB_EXTENSIONS_DIR);
152 webkit_web_context_set_web_extensions_initialization_user_data(m_webContext.get(), g_variant_new_uint32(++s_webExtensionID));
153 }
154
155#if PLATFORM(WPE)
156 static WebKitWebViewBackend* createWebViewBackend()
157 {
158 auto* headlessBackend = new WPEToolingBackends::HeadlessViewBackend(800, 600);
159 // Make the view initially hidden for consistency with GTK+ tests.
160 wpe_view_backend_remove_activity_state(headlessBackend->backend(), wpe_view_activity_state_visible | wpe_view_activity_state_focused);
161 return webkit_web_view_backend_new(headlessBackend->backend(), [](gpointer userData) {
162 delete static_cast<WPEToolingBackends::HeadlessViewBackend*>(userData);
163 }, headlessBackend);
164 }
165#endif
166
167 static WebKitWebView* createWebView()
168 {
169#if PLATFORM(GTK)
170 return WEBKIT_WEB_VIEW(webkit_web_view_new());
171#elif PLATFORM(WPE)
172 return webkit_web_view_new(createWebViewBackend());
173#endif
174 }
175
176 static WebKitWebView* createWebView(WebKitWebContext* context)
177 {
178#if PLATFORM(GTK)
179 return WEBKIT_WEB_VIEW(webkit_web_view_new_with_context(context));
180#elif PLATFORM(WPE)
181 return webkit_web_view_new_with_context(createWebViewBackend(), context);
182#endif
183 }
184
185 static WebKitWebView* createWebView(WebKitWebView* relatedView)
186 {
187#if PLATFORM(GTK)
188 return WEBKIT_WEB_VIEW(webkit_web_view_new_with_related_view(relatedView));
189#elif PLATFORM(WPE)
190 return webkit_web_view_new_with_related_view(createWebViewBackend(), relatedView);
191#endif
192 }
193
194 static WebKitWebView* createWebView(WebKitUserContentManager* contentManager)
195 {
196#if PLATFORM(GTK)
197 return WEBKIT_WEB_VIEW(webkit_web_view_new_with_user_content_manager(contentManager));
198#elif PLATFORM(WPE)
199 return webkit_web_view_new_with_user_content_manager(createWebViewBackend(), contentManager);
200#endif
201 }
202
203 static WebKitWebView* createWebView(WebKitSettings* settings)
204 {
205#if PLATFORM(GTK)
206 return WEBKIT_WEB_VIEW(webkit_web_view_new_with_settings(settings));
207#elif PLATFORM(WPE)
208 return webkit_web_view_new_with_settings(createWebViewBackend(), settings);
209#endif
210 }
211
212 static void objectFinalized(Test* test, GObject* finalizedObject)
213 {
214 test->m_watchedObjects.remove(finalizedObject);
215 }
216
217 void assertObjectIsDeletedWhenTestFinishes(GObject* object)
218 {
219 m_watchedObjects.add(object);
220 g_object_weak_ref(object, reinterpret_cast<GWeakNotify>(objectFinalized), this);
221 }
222
223
224 enum ResourcesDir {
225 WebKitGLibResources,
226 WebKit2Resources,
227 };
228
229 static CString getResourcesDir(ResourcesDir resourcesDir = WebKitGLibResources)
230 {
231 switch (resourcesDir) {
232 case WebKitGLibResources: {
233 GUniquePtr<char> resourcesDir(g_build_filename(WEBKIT_SRC_DIR, "Tools", "TestWebKitAPI", "Tests", "WebKitGLib", "resources", nullptr));
234 return resourcesDir.get();
235 }
236 case WebKit2Resources: {
237 GUniquePtr<char> resourcesDir(g_build_filename(WEBKIT_SRC_DIR, "Tools", "TestWebKitAPI", "Tests", "WebKit", nullptr));
238 return resourcesDir.get();
239 }
240 }
241 RELEASE_ASSERT_NOT_REACHED();
242 }
243
244 void addLogFatalFlag(unsigned flag)
245 {
246 unsigned fatalMask = g_log_set_always_fatal(static_cast<GLogLevelFlags>(G_LOG_FATAL_MASK));
247 fatalMask |= flag;
248 g_log_set_always_fatal(static_cast<GLogLevelFlags>(fatalMask));
249 }
250
251 void removeLogFatalFlag(unsigned flag)
252 {
253 unsigned fatalMask = g_log_set_always_fatal(static_cast<GLogLevelFlags>(G_LOG_FATAL_MASK));
254 fatalMask &= ~flag;
255 g_log_set_always_fatal(static_cast<GLogLevelFlags>(fatalMask));
256 }
257
258 static bool cairoSurfacesEqual(cairo_surface_t* s1, cairo_surface_t* s2)
259 {
260 return (cairo_image_surface_get_format(s1) == cairo_image_surface_get_format(s2)
261 && cairo_image_surface_get_width(s1) == cairo_image_surface_get_width(s2)
262 && cairo_image_surface_get_height(s1) == cairo_image_surface_get_height(s2)
263 && cairo_image_surface_get_stride(s1) == cairo_image_surface_get_stride(s2)
264 && !memcmp(const_cast<const void*>(reinterpret_cast<void*>(cairo_image_surface_get_data(s1))),
265 const_cast<const void*>(reinterpret_cast<void*>(cairo_image_surface_get_data(s2))),
266 cairo_image_surface_get_height(s1)*cairo_image_surface_get_stride(s1)));
267 }
268
269 HashSet<GObject*> m_watchedObjects;
270 GRefPtr<WebKitWebContext> m_webContext;
271 static uint32_t s_webExtensionID;
272};
273