| 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 | |