[WTF] Add makeUnique<T>, which ensures T is fast-allocated, makeUnique / makeUniqueWi...
[WebKit-https.git] / Source / WebKit / WebProcess / InjectedBundle / API / glib / WebKitWebExtension.cpp
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
34 using 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
112 enum {
113     PAGE_CREATED,
114
115     LAST_SIGNAL
116 };
117
118 typedef HashMap<WebPage*, GRefPtr<WebKitWebPage> > WebPageMap;
119
120 struct _WebKitWebExtensionPrivate {
121     WebPageMap pages;
122 #if ENABLE(DEVELOPER_MODE)
123     bool garbageCollectOnPageDestroy;
124 #endif
125 };
126
127 static guint signals[LAST_SIGNAL] = { 0, };
128
129 WEBKIT_DEFINE_TYPE(WebKitWebExtension, webkit_web_extension, G_TYPE_OBJECT)
130
131 static 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
151 class WebExtensionInjectedBundleClient final : public API::InjectedBundle::Client {
152 public:
153     explicit WebExtensionInjectedBundleClient(WebKitWebExtension* extension)
154         : m_extension(extension)
155     {
156     }
157
158 private:
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
196 WebKitWebExtension* webkitWebExtensionCreate(InjectedBundle* bundle)
197 {
198     WebKitWebExtension* extension = WEBKIT_WEB_EXTENSION(g_object_new(WEBKIT_TYPE_WEB_EXTENSION, NULL));
199     bundle->setClient(makeUnique<WebExtensionInjectedBundleClient>(extension));
200     return extension;
201 }
202
203 void 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  */
220 WebKitWebPage* 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 }