1/*
2 * Copyright (C) 2017 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 "WebKitAutomationSession.h"
22
23#include "APIAutomationSessionClient.h"
24#include "WebKitApplicationInfo.h"
25#include "WebKitAutomationSessionPrivate.h"
26#include "WebKitWebContextPrivate.h"
27#include "WebKitWebViewPrivate.h"
28#include <glib/gi18n-lib.h>
29#include <wtf/glib/WTFGType.h>
30#include <wtf/text/CString.h>
31
32using namespace WebKit;
33
34/**
35 * SECTION: WebKitAutomationSession
36 * @Short_description: Automation Session
37 * @Title: WebKitAutomationSession
38 *
39 * WebKitAutomationSession represents an automation session of a WebKitWebContext.
40 * When a new session is requested, a WebKitAutomationSession is created and the signal
41 * WebKitWebContext::automation-started is emitted with the WebKitAutomationSession as
42 * argument. Then, the automation client can request the session to create a new
43 * #WebKitWebView to interact with it. When this happens the signal #WebKitAutomationSession::create-web-view
44 * is emitted.
45 *
46 * Since: 2.18
47 */
48
49enum {
50 PROP_0,
51
52 PROP_ID
53};
54
55enum {
56 CREATE_WEB_VIEW,
57
58 LAST_SIGNAL
59};
60
61struct _WebKitAutomationSessionPrivate {
62 RefPtr<WebAutomationSession> session;
63 WebKitApplicationInfo* applicationInfo;
64 WebKitWebContext* webContext;
65 CString id;
66};
67
68static guint signals[LAST_SIGNAL] = { 0, };
69
70WEBKIT_DEFINE_TYPE(WebKitAutomationSession, webkit_automation_session, G_TYPE_OBJECT)
71
72class AutomationSessionClient final : public API::AutomationSessionClient {
73public:
74 explicit AutomationSessionClient(WebKitAutomationSession* session)
75 : m_session(session)
76 {
77 }
78
79private:
80 String sessionIdentifier() const override
81 {
82 return String::fromUTF8(m_session->priv->id.data());
83 }
84
85 void didDisconnectFromRemote(WebAutomationSession&) override
86 {
87#if ENABLE(REMOTE_INSPECTOR)
88 webkitWebContextWillCloseAutomationSession(m_session->priv->webContext);
89#endif
90 }
91
92 void requestNewPageWithOptions(WebAutomationSession&, API::AutomationSessionBrowsingContextOptions, CompletionHandler<void(WebPageProxy*)>&& completionHandler) override
93 {
94 WebKitWebView* webView = nullptr;
95 g_signal_emit(m_session, signals[CREATE_WEB_VIEW], 0, &webView);
96 if (!webView || !webkit_web_view_is_controlled_by_automation(webView))
97 completionHandler(nullptr);
98 else
99 completionHandler(&webkitWebViewGetPage(webView));
100 }
101
102 void requestMaximizeWindowOfPage(WebAutomationSession&, WebPageProxy& page, CompletionHandler<void()>&& completionHandler) override
103 {
104 if (auto* webView = webkitWebContextGetWebViewForPage(m_session->priv->webContext, &page))
105 webkitWebViewMaximizeWindow(webView, WTFMove(completionHandler));
106 else
107 completionHandler();
108 }
109
110 void requestHideWindowOfPage(WebAutomationSession&, WebPageProxy& page, CompletionHandler<void()>&& completionHandler) override
111 {
112 if (auto* webView = webkitWebContextGetWebViewForPage(m_session->priv->webContext, &page))
113 webkitWebViewMinimizeWindow(webView, WTFMove(completionHandler));
114 else
115 completionHandler();
116 }
117
118 void requestRestoreWindowOfPage(WebAutomationSession&, WebPageProxy& page, CompletionHandler<void()>&& completionHandler) override
119 {
120 if (auto* webView = webkitWebContextGetWebViewForPage(m_session->priv->webContext, &page))
121 webkitWebViewRestoreWindow(webView, WTFMove(completionHandler));
122 else
123 completionHandler();
124 }
125
126 bool isShowingJavaScriptDialogOnPage(WebAutomationSession&, WebPageProxy& page) override
127 {
128 auto* webView = webkitWebContextGetWebViewForPage(m_session->priv->webContext, &page);
129 if (!webView)
130 return false;
131 return webkitWebViewIsShowingScriptDialog(webView);
132 }
133
134 void dismissCurrentJavaScriptDialogOnPage(WebAutomationSession&, WebPageProxy& page) override
135 {
136 auto* webView = webkitWebContextGetWebViewForPage(m_session->priv->webContext, &page);
137 if (!webView)
138 return;
139 webkitWebViewDismissCurrentScriptDialog(webView);
140 }
141
142 void acceptCurrentJavaScriptDialogOnPage(WebAutomationSession&, WebPageProxy& page) override
143 {
144 auto* webView = webkitWebContextGetWebViewForPage(m_session->priv->webContext, &page);
145 if (!webView)
146 return;
147 webkitWebViewAcceptCurrentScriptDialog(webView);
148 }
149
150 String messageOfCurrentJavaScriptDialogOnPage(WebAutomationSession&, WebPageProxy& page) override
151 {
152 auto* webView = webkitWebContextGetWebViewForPage(m_session->priv->webContext, &page);
153 if (!webView)
154 return { };
155 return webkitWebViewGetCurrentScriptDialogMessage(webView);
156 }
157
158 void setUserInputForCurrentJavaScriptPromptOnPage(WebAutomationSession&, WebPageProxy& page, const String& userInput) override
159 {
160 auto* webView = webkitWebContextGetWebViewForPage(m_session->priv->webContext, &page);
161 if (!webView)
162 return;
163 webkitWebViewSetCurrentScriptDialogUserInput(webView, userInput);
164 }
165
166 Optional<API::AutomationSessionClient::JavaScriptDialogType> typeOfCurrentJavaScriptDialogOnPage(WebAutomationSession&, WebPageProxy& page) override
167 {
168 auto* webView = webkitWebContextGetWebViewForPage(m_session->priv->webContext, &page);
169 if (!webView)
170 return WTF::nullopt;
171 auto dialogType = webkitWebViewGetCurrentScriptDialogType(webView);
172 if (!dialogType)
173 return WTF::nullopt;
174 switch (dialogType.value()) {
175 case WEBKIT_SCRIPT_DIALOG_ALERT:
176 return API::AutomationSessionClient::JavaScriptDialogType::Alert;
177 case WEBKIT_SCRIPT_DIALOG_CONFIRM:
178 return API::AutomationSessionClient::JavaScriptDialogType::Confirm;
179 case WEBKIT_SCRIPT_DIALOG_PROMPT:
180 return API::AutomationSessionClient::JavaScriptDialogType::Prompt;
181 case WEBKIT_SCRIPT_DIALOG_BEFORE_UNLOAD_CONFIRM:
182 return API::AutomationSessionClient::JavaScriptDialogType::BeforeUnloadConfirm;
183 }
184
185 ASSERT_NOT_REACHED();
186 return WTF::nullopt;
187 }
188
189 WebKitAutomationSession* m_session;
190};
191
192static void webkitAutomationSessionGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* paramSpec)
193{
194 WebKitAutomationSession* session = WEBKIT_AUTOMATION_SESSION(object);
195
196 switch (propID) {
197 case PROP_ID:
198 g_value_set_string(value, session->priv->id.data());
199 break;
200 default:
201 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec);
202 }
203}
204
205static void webkitAutomationSessionSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* paramSpec)
206{
207 WebKitAutomationSession* session = WEBKIT_AUTOMATION_SESSION(object);
208
209 switch (propID) {
210 case PROP_ID:
211 session->priv->id = g_value_get_string(value);
212 break;
213 default:
214 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec);
215 }
216}
217
218static void webkitAutomationSessionConstructed(GObject* object)
219{
220 WebKitAutomationSession* session = WEBKIT_AUTOMATION_SESSION(object);
221
222 G_OBJECT_CLASS(webkit_automation_session_parent_class)->constructed(object);
223
224 session->priv->session = adoptRef(new WebAutomationSession());
225 session->priv->session->setSessionIdentifier(String::fromUTF8(session->priv->id.data()));
226 session->priv->session->setClient(std::make_unique<AutomationSessionClient>(session));
227}
228
229static void webkitAutomationSessionDispose(GObject* object)
230{
231 WebKitAutomationSession* session = WEBKIT_AUTOMATION_SESSION(object);
232
233 session->priv->session->setClient(nullptr);
234
235 if (session->priv->applicationInfo) {
236 webkit_application_info_unref(session->priv->applicationInfo);
237 session->priv->applicationInfo = nullptr;
238 }
239
240 G_OBJECT_CLASS(webkit_automation_session_parent_class)->dispose(object);
241}
242
243static void webkit_automation_session_class_init(WebKitAutomationSessionClass* sessionClass)
244{
245 GObjectClass* gObjectClass = G_OBJECT_CLASS(sessionClass);
246 gObjectClass->get_property = webkitAutomationSessionGetProperty;
247 gObjectClass->set_property = webkitAutomationSessionSetProperty;
248 gObjectClass->constructed = webkitAutomationSessionConstructed;
249 gObjectClass->dispose = webkitAutomationSessionDispose;
250
251 /**
252 * WebKitAutomationSession:id:
253 *
254 * The session unique identifier.
255 *
256 * Since: 2.18
257 */
258 g_object_class_install_property(
259 gObjectClass,
260 PROP_ID,
261 g_param_spec_string(
262 "id",
263 _("Identifier"),
264 _("The automation session identifier"),
265 nullptr,
266 static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
267
268 /**
269 * WebKitAutomationSession::create-web-view:
270 * @session: a #WebKitAutomationSession
271 *
272 * This signal is emitted when the automation client requests a new
273 * browsing context to interact with it. The callback handler should
274 * return a #WebKitWebView created with #WebKitWebView:is-controlled-by-automation
275 * construct property enabled. The returned #WebKitWebView could be an existing
276 * web view or a new one created and added to a new tab or window.
277 *
278 * Returns: (transfer none): a #WebKitWebView widget.
279 *
280 * Since: 2.18
281 */
282 signals[CREATE_WEB_VIEW] = g_signal_new(
283 "create-web-view",
284 G_TYPE_FROM_CLASS(sessionClass),
285 G_SIGNAL_RUN_LAST,
286 0,
287 nullptr, nullptr,
288 g_cclosure_marshal_generic,
289 WEBKIT_TYPE_WEB_VIEW, 0,
290 G_TYPE_NONE);
291}
292
293#if ENABLE(REMOTE_INSPECTOR)
294WebKitAutomationSession* webkitAutomationSessionCreate(WebKitWebContext* webContext, const char* sessionID, const Inspector::RemoteInspector::Client::SessionCapabilities& capabilities)
295{
296 auto* session = WEBKIT_AUTOMATION_SESSION(g_object_new(WEBKIT_TYPE_AUTOMATION_SESSION, "id", sessionID, nullptr));
297 session->priv->webContext = webContext;
298 if (capabilities.acceptInsecureCertificates)
299 webkit_web_context_set_tls_errors_policy(webContext, WEBKIT_TLS_ERRORS_POLICY_IGNORE);
300 for (auto& certificate : capabilities.certificates) {
301 GRefPtr<GTlsCertificate> tlsCertificate = adoptGRef(g_tls_certificate_new_from_file(certificate.second.utf8().data(), nullptr));
302 if (tlsCertificate)
303 webkit_web_context_allow_tls_certificate_for_host(webContext, tlsCertificate.get(), certificate.first.utf8().data());
304 }
305 return session;
306}
307#endif
308
309WebAutomationSession& webkitAutomationSessionGetSession(WebKitAutomationSession* session)
310{
311 return *session->priv->session;
312}
313
314String webkitAutomationSessionGetBrowserName(WebKitAutomationSession* session)
315{
316 if (session->priv->applicationInfo)
317 return String::fromUTF8(webkit_application_info_get_name(session->priv->applicationInfo));
318
319 return g_get_prgname();
320}
321
322String webkitAutomationSessionGetBrowserVersion(WebKitAutomationSession* session)
323{
324 if (!session->priv->applicationInfo)
325 return { };
326
327 guint64 major, minor, micro;
328 webkit_application_info_get_version(session->priv->applicationInfo, &major, &minor, &micro);
329
330 if (!micro && !minor)
331 return String::number(major);
332
333 if (!micro)
334 return makeString(String::number(major), ".", String::number(minor));
335
336 return makeString(String::number(major), ".", String::number(minor), ".", String::number(micro));
337}
338
339/**
340 * webkit_automation_session_get_id:
341 * @session: a #WebKitAutomationSession
342 *
343 * Get the unique identifier of a #WebKitAutomationSession
344 *
345 * Returns: the unique identifier of @session
346 *
347 * Since: 2.18
348 */
349const char* webkit_automation_session_get_id(WebKitAutomationSession* session)
350{
351 g_return_val_if_fail(WEBKIT_IS_AUTOMATION_SESSION(session), nullptr);
352 return session->priv->id.data();
353}
354
355/**
356 * webkit_automation_session_set_application_info:
357 * @session: a #WebKitAutomationSession
358 * @info: a #WebKitApplicationInfo
359 *
360 * Set the application information to @session. This information will be used by the driver service
361 * to match the requested capabilities with the actual application information. If this information
362 * is not provided to the session when a new automation session is requested, the creation might fail
363 * if the client requested a specific browser name or version. This will not have any effect when called
364 * after the automation session has been fully created, so this must be called in the callback of
365 * #WebKitWebContext::automation-started signal.
366 *
367 * Since: 2.18
368 */
369void webkit_automation_session_set_application_info(WebKitAutomationSession* session, WebKitApplicationInfo* info)
370{
371 g_return_if_fail(WEBKIT_IS_AUTOMATION_SESSION(session));
372 g_return_if_fail(info);
373
374 if (session->priv->applicationInfo == info)
375 return;
376
377 if (session->priv->applicationInfo)
378 webkit_application_info_unref(session->priv->applicationInfo);
379 session->priv->applicationInfo = webkit_application_info_ref(info);
380}
381
382/**
383 * webkit_automation_session_get_application_info:
384 * @session: a #WebKitAutomationSession
385 *
386 * Get the #WebKitAutomationSession previously set with webkit_automation_session_set_application_info().
387 *
388 * Returns: (transfer none): the #WebKitAutomationSession of @session, or %NULL if no one has been set.
389 *
390 * Since: 2.18
391 */
392WebKitApplicationInfo* webkit_automation_session_get_application_info(WebKitAutomationSession* session)
393{
394 g_return_val_if_fail(WEBKIT_IS_AUTOMATION_SESSION(session), nullptr);
395
396 return session->priv->applicationInfo;
397}
398