1/*
2 * Copyright (C) 2012 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 Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2,1 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 "WebKitWebExtension.h"
22
23#include "APIDictionary.h"
24#include "APIInjectedBundleBundleClient.h"
25#include "APIString.h"
26#include "WebKitWebExtensionPrivate.h"
27#include "WebKitWebPagePrivate.h"
28#include "WebProcess.h"
29#include <WebCore/GCController.h>
30#include <wtf/HashMap.h>
31#include <wtf/glib/GRefPtr.h>
32#include <wtf/glib/WTFGType.h>
33
34using namespace WebKit;
35
36/**
37 * SECTION: WebKitWebExtension
38 * @Short_description: Represents a WebExtension of the WebProcess
39 * @Title: WebKitWebExtension
40 *
41 * WebKitWebExtension is a loadable module for the WebProcess. It allows you to execute code in the
42 * WebProcess and being able to use the DOM API, to change any request or to inject custom
43 * JavaScript code, for example.
44 *
45 * To create a WebKitWebExtension you should write a module with an initialization function that could
46 * be either webkit_web_extension_initialize() with prototype #WebKitWebExtensionInitializeFunction or
47 * webkit_web_extension_initialize_with_user_data() with prototype #WebKitWebExtensionInitializeWithUserDataFunction.
48 * This function has to be public and it has to use the #G_MODULE_EXPORT macro. It is called when the
49 * web process is initialized.
50 *
51 * <informalexample><programlisting>
52 * static void
53 * web_page_created_callback (WebKitWebExtension *extension,
54 * WebKitWebPage *web_page,
55 * gpointer user_data)
56 * {
57 * g_print ("Page %d created for %s\n",
58 * webkit_web_page_get_id (web_page),
59 * webkit_web_page_get_uri (web_page));
60 * }
61 *
62 * G_MODULE_EXPORT void
63 * webkit_web_extension_initialize (WebKitWebExtension *extension)
64 * {
65 * g_signal_connect (extension, "page-created",
66 * G_CALLBACK (web_page_created_callback),
67 * NULL);
68 * }
69 * </programlisting></informalexample>
70 *
71 * The previous piece of code shows a trivial example of an extension that notifies when
72 * a #WebKitWebPage is created.
73 *
74 * WebKit has to know where it can find the created WebKitWebExtension. To do so you
75 * should use the webkit_web_context_set_web_extensions_directory() function. The signal
76 * #WebKitWebContext::initialize-web-extensions is the recommended place to call it.
77 *
78 * To provide the initialization data used by the webkit_web_extension_initialize_with_user_data()
79 * function, you have to call webkit_web_context_set_web_extensions_initialization_user_data() with
80 * the desired data as parameter. You can see an example of this in the following piece of code:
81 *
82 * <informalexample><programlisting>
83 * #define WEB_EXTENSIONS_DIRECTORY /<!-- -->* ... *<!-- -->/
84 *
85 * static void
86 * initialize_web_extensions (WebKitWebContext *context,
87 * gpointer user_data)
88 * {
89 * /<!-- -->* Web Extensions get a different ID for each Web Process *<!-- -->/
90 * static guint32 unique_id = 0;
91 *
92 * webkit_web_context_set_web_extensions_directory (
93 * context, WEB_EXTENSIONS_DIRECTORY);
94 * webkit_web_context_set_web_extensions_initialization_user_data (
95 * context, g_variant_new_uint32 (unique_id++));
96 * }
97 *
98 * int main (int argc, char **argv)
99 * {
100 * g_signal_connect (webkit_web_context_get_default (),
101 * "initialize-web-extensions",
102 * G_CALLBACK (initialize_web_extensions),
103 * NULL);
104 *
105 * GtkWidget *view = webkit_web_view_new ();
106 *
107 * /<!-- -->* ... *<!-- -->/
108 * }
109 * </programlisting></informalexample>
110 */
111
112enum {
113 PAGE_CREATED,
114
115 LAST_SIGNAL
116};
117
118typedef HashMap<WebPage*, GRefPtr<WebKitWebPage> > WebPageMap;
119
120struct _WebKitWebExtensionPrivate {
121 WebPageMap pages;
122#if ENABLE(DEVELOPER_MODE)
123 bool garbageCollectOnPageDestroy;
124#endif
125};
126
127static guint signals[LAST_SIGNAL] = { 0, };
128
129WEBKIT_DEFINE_TYPE(WebKitWebExtension, webkit_web_extension, G_TYPE_OBJECT)
130
131static void webkit_web_extension_class_init(WebKitWebExtensionClass* klass)
132{
133 /**
134 * WebKitWebExtension::page-created:
135 * @extension: the #WebKitWebExtension on which the signal is emitted
136 * @web_page: the #WebKitWebPage created
137 *
138 * This signal is emitted when a new #WebKitWebPage is created in
139 * the Web Process.
140 */
141 signals[PAGE_CREATED] = g_signal_new(
142 "page-created",
143 G_TYPE_FROM_CLASS(klass),
144 G_SIGNAL_RUN_LAST,
145 0, 0, 0,
146 g_cclosure_marshal_VOID__OBJECT,
147 G_TYPE_NONE, 1,
148 WEBKIT_TYPE_WEB_PAGE);
149}
150
151class WebExtensionInjectedBundleClient final : public API::InjectedBundle::Client {
152public:
153 explicit WebExtensionInjectedBundleClient(WebKitWebExtension* extension)
154 : m_extension(extension)
155 {
156 }
157
158private:
159 void didCreatePage(InjectedBundle&, WebPage& page) override
160 {
161 GRefPtr<WebKitWebPage> webPage = adoptGRef(webkitWebPageCreate(&page));
162 m_extension->priv->pages.add(&page, webPage);
163 g_signal_emit(m_extension, signals[PAGE_CREATED], 0, webPage.get());
164 }
165
166 void willDestroyPage(InjectedBundle&, WebPage& page) override
167 {
168 m_extension->priv->pages.remove(&page);
169#if ENABLE(DEVELOPER_MODE)
170 if (m_extension->priv->garbageCollectOnPageDestroy)
171 WebCore::GCController::singleton().garbageCollectNow();
172#endif
173 }
174
175 void didReceiveMessage(InjectedBundle&, const String& messageName, API::Object* messageBody) override
176 {
177 ASSERT(messageBody->type() == API::Object::Type::Dictionary);
178 API::Dictionary& message = *static_cast<API::Dictionary*>(messageBody);
179 if (messageName == String::fromUTF8("PrefetchDNS")) {
180 API::String* hostname = static_cast<API::String*>(message.get(String::fromUTF8("Hostname")));
181 WebProcess::singleton().prefetchDNS(hostname->string());
182 } else
183 ASSERT_NOT_REACHED();
184 }
185
186 void didReceiveMessageToPage(InjectedBundle&, WebPage& page, const String& messageName, API::Object* messageBody) override
187 {
188 ASSERT(messageBody->type() == API::Object::Type::Dictionary);
189 if (auto* webPage = m_extension->priv->pages.get(&page))
190 webkitWebPageDidReceiveMessage(webPage, messageName, *static_cast<API::Dictionary*>(messageBody));
191 }
192
193 WebKitWebExtension* m_extension;
194};
195
196WebKitWebExtension* webkitWebExtensionCreate(InjectedBundle* bundle)
197{
198 WebKitWebExtension* extension = WEBKIT_WEB_EXTENSION(g_object_new(WEBKIT_TYPE_WEB_EXTENSION, NULL));
199 bundle->setClient(std::make_unique<WebExtensionInjectedBundleClient>(extension));
200 return extension;
201}
202
203void webkitWebExtensionSetGarbageCollectOnPageDestroy(WebKitWebExtension* extension)
204{
205#if ENABLE(DEVELOPER_MODE)
206 extension->priv->garbageCollectOnPageDestroy = true;
207#endif
208}
209
210/**
211 * webkit_web_extension_get_page:
212 * @extension: a #WebKitWebExtension
213 * @page_id: the identifier of the #WebKitWebPage to get
214 *
215 * Get the web page of the given @page_id.
216 *
217 * Returns: (transfer none): the #WebKitWebPage for the given @page_id, or %NULL if the
218 * identifier doesn't correspond to an existing web page.
219 */
220WebKitWebPage* webkit_web_extension_get_page(WebKitWebExtension* extension, guint64 pageID)
221{
222 g_return_val_if_fail(WEBKIT_IS_WEB_EXTENSION(extension), 0);
223
224 WebKitWebExtensionPrivate* priv = extension->priv;
225 WebPageMap::const_iterator end = priv->pages.end();
226 for (WebPageMap::const_iterator it = priv->pages.begin(); it != end; ++it)
227 if (it->key->pageID().toUInt64() == pageID)
228 return it->value.get();
229
230 return 0;
231}
232