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 | |
32 | using 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 | |
49 | enum { |
50 | PROP_0, |
51 | |
52 | PROP_ID |
53 | }; |
54 | |
55 | enum { |
56 | CREATE_WEB_VIEW, |
57 | |
58 | LAST_SIGNAL |
59 | }; |
60 | |
61 | struct _WebKitAutomationSessionPrivate { |
62 | RefPtr<WebAutomationSession> session; |
63 | WebKitApplicationInfo* applicationInfo; |
64 | WebKitWebContext* webContext; |
65 | CString id; |
66 | }; |
67 | |
68 | static guint signals[LAST_SIGNAL] = { 0, }; |
69 | |
70 | WEBKIT_DEFINE_TYPE(WebKitAutomationSession, webkit_automation_session, G_TYPE_OBJECT) |
71 | |
72 | class AutomationSessionClient final : public API::AutomationSessionClient { |
73 | public: |
74 | explicit AutomationSessionClient(WebKitAutomationSession* session) |
75 | : m_session(session) |
76 | { |
77 | } |
78 | |
79 | private: |
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 | |
192 | static 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 | |
205 | static 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 | |
218 | static 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 | |
229 | static 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 | |
243 | static 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) |
294 | WebKitAutomationSession* 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 | |
309 | WebAutomationSession& webkitAutomationSessionGetSession(WebKitAutomationSession* session) |
310 | { |
311 | return *session->priv->session; |
312 | } |
313 | |
314 | String 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 | |
322 | String 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, µ); |
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 | */ |
349 | const 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 | */ |
369 | void 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 | */ |
392 | WebKitApplicationInfo* 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 | |