1/*
2 * Copyright (C) 2014 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#include "config.h"
21#include "WebKitUserContentManager.h"
22
23#include "APISerializedScriptValue.h"
24#include "InjectUserScriptImmediately.h"
25#include "WebKitJavascriptResultPrivate.h"
26#include "WebKitUserContentManagerPrivate.h"
27#include "WebKitUserContentPrivate.h"
28#include "WebKitWebContextPrivate.h"
29#include "WebScriptMessageHandler.h"
30#include <wtf/glib/GRefPtr.h>
31#include <wtf/glib/WTFGType.h>
32
33#if PLATFORM(WPE)
34#include "WPEView.h"
35#endif
36
37using namespace WebCore;
38using namespace WebKit;
39
40struct _WebKitUserContentManagerPrivate {
41 _WebKitUserContentManagerPrivate()
42 : userContentController(adoptRef(new WebUserContentControllerProxy))
43 {
44 }
45
46 RefPtr<WebUserContentControllerProxy> userContentController;
47};
48
49/**
50 * SECTION:WebKitUserContentManager
51 * @short_description: Manages user-defined content which affects web pages.
52 * @title: WebKitUserContentManager
53 *
54 * Using a #WebKitUserContentManager user CSS style sheets can be set to
55 * be injected in the web pages loaded by a #WebKitWebView, by
56 * webkit_user_content_manager_add_style_sheet().
57 *
58 * To use a #WebKitUserContentManager, it must be created using
59 * webkit_user_content_manager_new(), and then passed to
60 * webkit_web_view_new_with_user_content_manager(). User style
61 * sheets can be created with webkit_user_style_sheet_new().
62 *
63 * User style sheets can be added and removed at any time, but
64 * they will affect the web pages loaded afterwards.
65 *
66 * Since: 2.6
67 */
68
69WEBKIT_DEFINE_TYPE(WebKitUserContentManager, webkit_user_content_manager, G_TYPE_OBJECT)
70
71enum {
72 SCRIPT_MESSAGE_RECEIVED,
73
74 LAST_SIGNAL
75};
76
77static guint signals[LAST_SIGNAL] = { 0, };
78
79static void webkit_user_content_manager_class_init(WebKitUserContentManagerClass* klass)
80{
81 GObjectClass* gObjectClass = G_OBJECT_CLASS(klass);
82
83 /**
84 * WebKitUserContentManager::script-message-received:
85 * @manager: the #WebKitUserContentManager
86 * @js_result: the #WebKitJavascriptResult holding the value received from the JavaScript world.
87 *
88 * This signal is emitted when JavaScript in a web view calls
89 * <code>window.webkit.messageHandlers.&lt;name&gt;.postMessage()</code>, after registering
90 * <code>&lt;name&gt;</code> using
91 * webkit_user_content_manager_register_script_message_handler()
92 *
93 * Since: 2.8
94 */
95 signals[SCRIPT_MESSAGE_RECEIVED] =
96 g_signal_new(
97 "script-message-received",
98 G_TYPE_FROM_CLASS(gObjectClass),
99 static_cast<GSignalFlags>(G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED),
100 0, nullptr, nullptr,
101 g_cclosure_marshal_VOID__BOXED,
102 G_TYPE_NONE, 1,
103 WEBKIT_TYPE_JAVASCRIPT_RESULT);
104}
105
106/**
107 * webkit_user_content_manager_new:
108 *
109 * Creates a new user content manager.
110 *
111 * Returns: A #WebKitUserContentManager
112 *
113 * Since: 2.6
114 */
115WebKitUserContentManager* webkit_user_content_manager_new()
116{
117 return WEBKIT_USER_CONTENT_MANAGER(g_object_new(WEBKIT_TYPE_USER_CONTENT_MANAGER, nullptr));
118}
119
120/**
121 * webkit_user_content_manager_add_style_sheet:
122 * @manager: A #WebKitUserContentManager
123 * @stylesheet: A #WebKitUserStyleSheet
124 *
125 * Adds a #WebKitUserStyleSheet to the given #WebKitUserContentManager.
126 * The same #WebKitUserStyleSheet can be reused with multiple
127 * #WebKitUserContentManager instances.
128 *
129 * Since: 2.6
130 */
131void webkit_user_content_manager_add_style_sheet(WebKitUserContentManager* manager, WebKitUserStyleSheet* styleSheet)
132{
133 g_return_if_fail(WEBKIT_IS_USER_CONTENT_MANAGER(manager));
134 g_return_if_fail(styleSheet);
135 manager->priv->userContentController->addUserStyleSheet(webkitUserStyleSheetGetUserStyleSheet(styleSheet));
136}
137
138/**
139 * webkit_user_content_manager_remove_all_style_sheets:
140 * @manager: A #WebKitUserContentManager
141 *
142 * Removes all user style sheets from the given #WebKitUserContentManager.
143 *
144 * Since: 2.6
145 */
146void webkit_user_content_manager_remove_all_style_sheets(WebKitUserContentManager* manager)
147{
148 g_return_if_fail(WEBKIT_IS_USER_CONTENT_MANAGER(manager));
149 manager->priv->userContentController->removeAllUserStyleSheets();
150}
151
152/**
153 * webkit_user_content_manager_add_script:
154 * @manager: A #WebKitUserContentManager
155 * @script: A #WebKitUserScript
156 *
157 * Adds a #WebKitUserScript to the given #WebKitUserContentManager.
158 * The same #WebKitUserScript can be reused with multiple
159 * #WebKitUserContentManager instances.
160 *
161 * Since: 2.6
162 */
163void webkit_user_content_manager_add_script(WebKitUserContentManager* manager, WebKitUserScript* script)
164{
165 g_return_if_fail(WEBKIT_IS_USER_CONTENT_MANAGER(manager));
166 g_return_if_fail(script);
167 manager->priv->userContentController->addUserScript(webkitUserScriptGetUserScript(script), InjectUserScriptImmediately::No);
168}
169
170/**
171 * webkit_user_content_manager_remove_all_scripts:
172 * @manager: A #WebKitUserContentManager
173 *
174 * Removes all user scripts from the given #WebKitUserContentManager
175 *
176 * Since: 2.6
177 */
178void webkit_user_content_manager_remove_all_scripts(WebKitUserContentManager* manager)
179{
180 g_return_if_fail(WEBKIT_IS_USER_CONTENT_MANAGER(manager));
181 manager->priv->userContentController->removeAllUserScripts();
182}
183
184class ScriptMessageClientGtk final : public WebScriptMessageHandler::Client {
185public:
186 ScriptMessageClientGtk(WebKitUserContentManager* manager, const char* handlerName)
187 : m_handlerName(g_quark_from_string(handlerName))
188 , m_manager(manager)
189 {
190 }
191
192 void didPostMessage(WebPageProxy&, const FrameInfoData&, WebCore::SerializedScriptValue& serializedScriptValue) override
193 {
194 WebKitJavascriptResult* jsResult = webkitJavascriptResultCreate(serializedScriptValue);
195 g_signal_emit(m_manager, signals[SCRIPT_MESSAGE_RECEIVED], m_handlerName, jsResult);
196 webkit_javascript_result_unref(jsResult);
197 }
198
199 virtual ~ScriptMessageClientGtk() { }
200
201private:
202 GQuark m_handlerName;
203 WebKitUserContentManager* m_manager;
204};
205
206/**
207 * webkit_user_content_manager_register_script_message_handler:
208 * @manager: A #WebKitUserContentManager
209 * @name: Name of the script message channel
210 *
211 * Registers a new user script message handler. After it is registered,
212 * scripts can use `window.webkit.messageHandlers.&lt;name&gt;.postMessage(value)`
213 * to send messages. Those messages are received by connecting handlers
214 * to the #WebKitUserContentManager::script-message-received signal. The
215 * handler name is used as the detail of the signal. To avoid race
216 * conditions between registering the handler name, and starting to
217 * receive the signals, it is recommended to connect to the signal
218 * *before* registering the handler name:
219 *
220 * <informalexample><programlisting>
221 * WebKitWebView *view = webkit_web_view_new ();
222 * WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager ();
223 * g_signal_connect (manager, "script-message-received::foobar",
224 * G_CALLBACK (handle_script_message), NULL);
225 * webkit_user_content_manager_register_script_message_handler (manager, "foobar");
226 * </programlisting></informalexample>
227 *
228 * Registering a script message handler will fail if the requested
229 * name has been already registered before.
230 *
231 * Returns: %TRUE if message handler was registered successfully, or %FALSE otherwise.
232 *
233 * Since: 2.8
234 */
235gboolean webkit_user_content_manager_register_script_message_handler(WebKitUserContentManager* manager, const char* name)
236{
237 g_return_val_if_fail(WEBKIT_IS_USER_CONTENT_MANAGER(manager), FALSE);
238 g_return_val_if_fail(name, FALSE);
239
240 Ref<WebScriptMessageHandler> handler =
241 WebScriptMessageHandler::create(std::make_unique<ScriptMessageClientGtk>(manager, name), String::fromUTF8(name), API::UserContentWorld::normalWorld());
242 return manager->priv->userContentController->addUserScriptMessageHandler(handler.get());
243}
244
245/**
246 * webkit_user_content_manager_unregister_script_message_handler:
247 * @manager: A #WebKitUserContentManager
248 * @name: Name of the script message channel
249 *
250 * Unregisters a previously registered message handler.
251 *
252 * Note that this does *not* disconnect handlers for the
253 * #WebKitUserContentManager::script-message-received signal;
254 * they will be kept connected, but the signal will not be emitted
255 * unless the handler name is registered again.
256 *
257 * See also webkit_user_content_manager_register_script_message_handler().
258 *
259 * Since: 2.8
260 */
261void webkit_user_content_manager_unregister_script_message_handler(WebKitUserContentManager* manager, const char* name)
262{
263 g_return_if_fail(WEBKIT_IS_USER_CONTENT_MANAGER(manager));
264 g_return_if_fail(name);
265 manager->priv->userContentController->removeUserMessageHandlerForName(String::fromUTF8(name), API::UserContentWorld::normalWorld());
266}
267
268/**
269 * webkit_user_content_manager_register_script_message_handler_in_world:
270 * @manager: A #WebKitUserContentManager
271 * @name: Name of the script message channel
272 * @world_name: the name of a #WebKitScriptWorld
273 *
274 * Registers a new user script message handler in script world with name @world_name.
275 * See webkit_user_content_manager_register_script_message_handler() for full description.
276 *
277 * Registering a script message handler will fail if the requested
278 * name has been already registered before.
279 *
280 * Returns: %TRUE if message handler was registered successfully, or %FALSE otherwise.
281 *
282 * Since: 2.22
283 */
284gboolean webkit_user_content_manager_register_script_message_handler_in_world(WebKitUserContentManager* manager, const char* name, const char* worldName)
285{
286 g_return_val_if_fail(WEBKIT_IS_USER_CONTENT_MANAGER(manager), FALSE);
287 g_return_val_if_fail(name, FALSE);
288 g_return_val_if_fail(worldName, FALSE);
289
290 Ref<WebScriptMessageHandler> handler =
291 WebScriptMessageHandler::create(std::make_unique<ScriptMessageClientGtk>(manager, name), String::fromUTF8(name), webkitUserContentWorld(worldName));
292 return manager->priv->userContentController->addUserScriptMessageHandler(handler.get());
293}
294
295/**
296 * webkit_user_content_manager_unregister_script_message_handler_in_world:
297 * @manager: A #WebKitUserContentManager
298 * @name: Name of the script message channel
299 * @world_name: the name of a #WebKitScriptWorld
300 *
301 * Unregisters a previously registered message handler in script world with name @world_name.
302 *
303 * Note that this does *not* disconnect handlers for the
304 * #WebKitUserContentManager::script-message-received signal;
305 * they will be kept connected, but the signal will not be emitted
306 * unless the handler name is registered again.
307 *
308 * See also webkit_user_content_manager_register_script_message_handler_in_world().
309 *
310 * Since: 2.22
311 */
312void webkit_user_content_manager_unregister_script_message_handler_in_world(WebKitUserContentManager* manager, const char* name, const char* worldName)
313{
314 g_return_if_fail(WEBKIT_IS_USER_CONTENT_MANAGER(manager));
315 g_return_if_fail(name);
316 g_return_if_fail(worldName);
317
318 manager->priv->userContentController->removeUserMessageHandlerForName(String::fromUTF8(name), webkitUserContentWorld(worldName));
319}
320
321/**
322 * webkit_user_content_manager_add_filter:
323 * @manager: A #WebKitUserContentManager
324 * @filter: A #WebKitUserContentFilter
325 *
326 * Adds a #WebKitUserContentFilter to the given #WebKitUserContentManager.
327 * The same #WebKitUserContentFilter can be reused with multiple
328 * #WebKitUserContentManager instances.
329 *
330 * Filters need to be saved and loaded from #WebKitUserContentFilterStore.
331 *
332 * Since: 2.24
333 */
334void webkit_user_content_manager_add_filter(WebKitUserContentManager* manager, WebKitUserContentFilter* filter)
335{
336 g_return_if_fail(WEBKIT_IS_USER_CONTENT_MANAGER(manager));
337 g_return_if_fail(filter);
338 manager->priv->userContentController->addContentRuleList(webkitUserContentFilterGetContentRuleList(filter));
339}
340
341/**
342 * webkit_user_content_manager_remove_filter:
343 * @manager: A #WebKitUserContentManager
344 * @filter: A #WebKitUserContentFilter
345 *
346 * Removes a filter from the given #WebKitUserContentManager.
347 *
348 * Since 2.24
349 */
350void webkit_user_content_manager_remove_filter(WebKitUserContentManager* manager, WebKitUserContentFilter* filter)
351{
352 g_return_if_fail(WEBKIT_IS_USER_CONTENT_MANAGER(manager));
353 g_return_if_fail(filter);
354 manager->priv->userContentController->removeContentRuleList(webkitUserContentFilterGetContentRuleList(filter).name());
355}
356
357/**
358 * webkit_user_content_manager_remove_all_filters:
359 * @manager: A #WebKitUserContentManager
360 *
361 * Removes all content filters from the given #WebKitUserContentManager.
362 *
363 * Since: 2.24
364 */
365void webkit_user_content_manager_remove_all_filters(WebKitUserContentManager* manager)
366{
367 g_return_if_fail(WEBKIT_IS_USER_CONTENT_MANAGER(manager));
368 manager->priv->userContentController->removeAllContentRuleLists();
369}
370
371WebUserContentControllerProxy* webkitUserContentManagerGetUserContentControllerProxy(WebKitUserContentManager* manager)
372{
373 return manager->priv->userContentController.get();
374}
375