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 | |
95 | class Test { |
96 | public: |
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 | |