blob: b130992156b678805331827d1201047bed069a85 [file] [log] [blame]
/*
* Copyright (C) 2017 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2,1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "TestMain.h"
#include <gio/gio.h>
#include <wtf/UUID.h>
#include <wtf/text/StringBuilder.h>
class AutomationTest: public Test {
public:
MAKE_GLIB_TEST_FIXTURE(AutomationTest);
AutomationTest()
: m_mainLoop(adoptGRef(g_main_loop_new(nullptr, TRUE)))
{
g_dbus_connection_new_for_address("tcp:host=127.0.0.1,port=2229",
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, nullptr, nullptr, [](GObject*, GAsyncResult* result, gpointer userData) {
GRefPtr<GDBusConnection> connection = adoptGRef(g_dbus_connection_new_for_address_finish(result, nullptr));
static_cast<AutomationTest*>(userData)->setConnection(WTFMove(connection));
}, this);
g_main_loop_run(m_mainLoop.get());
}
~AutomationTest()
{
}
struct Target {
Target() = default;
Target(guint64 id, CString name, bool isPaired)
: id(id)
, name(name)
, isPaired(isPaired)
{
}
guint64 id { 0 };
CString name;
bool isPaired { false };
};
const GDBusInterfaceVTable s_interfaceVTable = {
// method_call
[](GDBusConnection* connection, const gchar* sender, const gchar* objectPath, const gchar* interfaceName, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) {
auto* test = static_cast<AutomationTest*>(userData);
if (!g_strcmp0(methodName, "SetTargetList")) {
guint64 connectionID;
GUniqueOutPtr<GVariantIter> iter;
g_variant_get(parameters, "(ta(tsssb))", &connectionID, &iter.outPtr());
guint64 targetID;
const char* type;
const char* name;
const char* dummy;
gboolean isPaired;
while (g_variant_iter_loop(iter.get(), "(t&s&s&sb)", &targetID, &type, &name, &dummy, &isPaired)) {
if (!g_strcmp0(type, "Automation")) {
test->setTarget(connectionID, Target(targetID, name, isPaired));
break;
}
}
g_dbus_method_invocation_return_value(invocation, nullptr);
} else if (!g_strcmp0(methodName, "SendMessageToFrontend")) {
guint64 connectionID, targetID;
const char* message;
g_variant_get(parameters, "(tt&s)", &connectionID, &targetID, &message);
test->receivedMessage(connectionID, targetID, message);
g_dbus_method_invocation_return_value(invocation, nullptr);
}
},
// get_property
nullptr,
// set_property
nullptr,
// padding
{ 0 }
};
void registerDBusObject()
{
static const char introspectionXML[] =
"<node>"
" <interface name='org.webkit.RemoteInspectorClient'>"
" <method name='SetTargetList'>"
" <arg type='t' name='connectionID' direction='in'/>"
" <arg type='a(tsssb)' name='list' direction='in'/>"
" </method>"
" <method name='SendMessageToFrontend'>"
" <arg type='t' name='connectionID' direction='in'/>"
" <arg type='t' name='target' direction='in'/>"
" <arg type='s' name='message' direction='in'/>"
" </method>"
" </interface>"
"</node>";
static GDBusNodeInfo* introspectionData = nullptr;
if (!introspectionData)
introspectionData = g_dbus_node_info_new_for_xml(introspectionXML, nullptr);
g_dbus_connection_register_object(m_connection.get(), "/org/webkit/RemoteInspectorClient", introspectionData->interfaces[0], &s_interfaceVTable, this, nullptr, nullptr);
}
void setConnection(GRefPtr<GDBusConnection>&& connection)
{
g_assert_true(G_IS_DBUS_CONNECTION(connection.get()));
m_connection = WTFMove(connection);
registerDBusObject();
g_main_loop_quit(m_mainLoop.get());
}
void setTarget(guint64 connectionID, Target&& target)
{
bool newConnection = !m_connectionID;
bool wasPaired = m_target.isPaired;
m_connectionID = connectionID;
m_target = WTFMove(target);
if (newConnection || (!wasPaired && m_target.isPaired))
g_main_loop_quit(m_mainLoop.get());
}
void receivedMessage(guint64 connectionID, guint64 targetID, const char* message)
{
g_assert_cmpuint(connectionID, ==, m_connectionID);
g_assert_cmpuint(targetID, ==, m_target.id);
m_message = message;
g_main_loop_quit(m_mainLoop.get());
}
void sendCommandToBackend(const String& command, const String& parameters = String())
{
static long sequenceID = 0;
StringBuilder messageBuilder;
messageBuilder.appendLiteral("{\"id\":");
messageBuilder.appendNumber(++sequenceID);
messageBuilder.appendLiteral(",\"method\":\"Automation.");
messageBuilder.append(command);
messageBuilder.append('"');
if (!parameters.isNull()) {
messageBuilder.appendLiteral(",\"params\":");
messageBuilder.append(parameters);
}
messageBuilder.append('}');
g_dbus_connection_call(m_connection.get(), nullptr, "/org/webkit/Inspector", "org.webkit.Inspector",
"SendMessageToBackend", g_variant_new("(tts)", m_connectionID, m_target.id, messageBuilder.toString().utf8().data()),
nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, nullptr, nullptr, nullptr);
}
static WebKitWebView* createWebViewCallback(WebKitAutomationSession* session, AutomationTest* test)
{
test->m_createWebViewWasCalled = true;
return test->m_webViewForAutomation;
}
static WebKitWebView* createWebViewInWindowCallback(WebKitAutomationSession* session, AutomationTest* test)
{
test->m_createWebViewInWindowWasCalled = true;
return test->m_webViewForAutomation;
}
static WebKitWebView* createWebViewInTabCallback(WebKitAutomationSession* session, AutomationTest* test)
{
test->m_createWebViewInTabWasCalled = true;
return test->m_webViewForAutomation;
}
void automationStarted(WebKitAutomationSession* session)
{
m_session = session;
assertObjectIsDeletedWhenTestFinishes(G_OBJECT(m_session));
g_assert_null(webkit_automation_session_get_application_info(session));
WebKitApplicationInfo* info = webkit_application_info_new();
webkit_application_info_set_name(info, "AutomationTestBrowser");
webkit_application_info_set_version(info, WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION);
webkit_automation_session_set_application_info(session, info);
webkit_application_info_unref(info);
g_assert_true(webkit_automation_session_get_application_info(session) == info);
}
static void automationStartedCallback(WebKitWebContext* webContext, WebKitAutomationSession* session, AutomationTest* test)
{
g_assert_true(webContext == test->m_webContext.get());
g_assert_true(WEBKIT_IS_AUTOMATION_SESSION(session));
test->automationStarted(session);
}
static GUniquePtr<char> toVersionString(unsigned major, unsigned minor, unsigned micro)
{
if (!micro && !minor)
return GUniquePtr<char>(g_strdup_printf("%u", major));
if (!micro)
return GUniquePtr<char>(g_strdup_printf("%u.%u", major, minor));
return GUniquePtr<char>(g_strdup_printf("%u.%u.%u", major, minor, micro));
}
WebKitAutomationSession* requestSession(const char* sessionID)
{
auto signalID = g_signal_connect(m_webContext.get(), "automation-started", G_CALLBACK(automationStartedCallback), this);
g_dbus_connection_call(m_connection.get(), nullptr, "/org/webkit/Inspector", "org.webkit.Inspector",
"StartAutomationSession", g_variant_new("(sa{sv})", sessionID, nullptr), nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, nullptr,
[](GObject* source, GAsyncResult* result, gpointer userData) {
auto* test = static_cast<AutomationTest*>(userData);
if (!test->m_session)
return;
GRefPtr<GVariant> capabilities = adoptGRef(g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, nullptr));
g_assert_nonnull(capabilities.get());
const char* browserName;
const char* browserVersion;
g_variant_get(capabilities.get(), "(&s&s)", &browserName, &browserVersion);
g_assert_cmpstr(browserName, ==, "AutomationTestBrowser");
GUniquePtr<char> versionString = toVersionString(WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION);
g_assert_cmpstr(browserVersion, ==, versionString.get());
}, this
);
auto timeoutID = g_timeout_add(1000, [](gpointer userData) -> gboolean {
g_main_loop_quit(static_cast<GMainLoop*>(userData));
return G_SOURCE_REMOVE;
}, m_mainLoop.get());
g_main_loop_run(m_mainLoop.get());
if (!m_connectionID)
m_session = nullptr;
if (m_session && m_connectionID)
g_source_remove(timeoutID);
g_signal_handler_disconnect(m_webContext.get(), signalID);
return m_session;
}
void setupIfNeeded()
{
if (m_target.isPaired)
return;
g_assert_cmpuint(m_target.id, !=, 0);
g_dbus_connection_call(m_connection.get(), nullptr, "/org/webkit/Inspector", "org.webkit.Inspector",
"Setup", g_variant_new("(tt)", m_connectionID, m_target.id), nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, nullptr, nullptr, nullptr);
g_main_loop_run(m_mainLoop.get());
g_assert_true(m_target.isPaired);
}
bool createTopLevelBrowsingContext(WebKitWebView* webView)
{
setupIfNeeded();
m_webViewForAutomation = webView;
m_createWebViewWasCalled = false;
m_message = CString();
auto signalID = g_signal_connect(m_session, "create-web-view", G_CALLBACK(createWebViewCallback), this);
sendCommandToBackend("createBrowsingContext");
g_main_loop_run(m_mainLoop.get());
g_signal_handler_disconnect(m_session, signalID);
g_assert_true(m_createWebViewWasCalled);
g_assert_false(m_message.isNull());
m_webViewForAutomation = nullptr;
if (strstr(m_message.data(), "The remote session failed to create a new browsing context"))
return false;
if (strstr(m_message.data(), "handle"))
return true;
return false;
}
bool createNewWindow(WebKitWebView* webView)
{
m_webViewForAutomation = webView;
m_createWebViewInWindowWasCalled = false;
m_message = { };
auto signalID = g_signal_connect(m_session, "create-web-view::window", G_CALLBACK(createWebViewInWindowCallback), this);
sendCommandToBackend("createBrowsingContext", "{\"presentationHint\":\"Window\"}");
g_main_loop_run(m_mainLoop.get());
g_signal_handler_disconnect(m_session, signalID);
g_assert_true(m_createWebViewInWindowWasCalled);
g_assert_false(m_message.isNull());
m_webViewForAutomation = nullptr;
if (strstr(m_message.data(), "\"presentation\":\"Window\""))
return true;
return false;
}
bool createNewTab(WebKitWebView* webView)
{
m_webViewForAutomation = webView;
m_createWebViewInTabWasCalled = false;
m_message = { };
auto signalID = g_signal_connect(m_session, "create-web-view::tab", G_CALLBACK(createWebViewInTabCallback), this);
sendCommandToBackend("createBrowsingContext", "{\"presentationHint\":\"Tab\"}");
g_main_loop_run(m_mainLoop.get());
g_signal_handler_disconnect(m_session, signalID);
g_assert_true(m_createWebViewInTabWasCalled);
g_assert_false(m_message.isNull());
m_webViewForAutomation = nullptr;
if (strstr(m_message.data(), "\"presentation\":\"Tab\""))
return true;
return false;
}
GRefPtr<GMainLoop> m_mainLoop;
GRefPtr<GDBusConnection> m_connection;
WebKitAutomationSession* m_session;
guint64 m_connectionID { 0 };
Target m_target;
WebKitWebView* m_webViewForAutomation { nullptr };
bool m_createWebViewWasCalled { false };
bool m_createWebViewInWindowWasCalled { false };
bool m_createWebViewInTabWasCalled { false };
CString m_message;
};
static void testAutomationSessionRequestSession(AutomationTest* test, gconstpointer)
{
String sessionID = createCanonicalUUIDString();
// WebKitAutomationSession::automation-started is never emitted if automation is not enabled.
g_assert_false(webkit_web_context_is_automation_allowed(test->m_webContext.get()));
auto* session = test->requestSession(sessionID.utf8().data());
g_assert_null(session);
webkit_web_context_set_automation_allowed(test->m_webContext.get(), TRUE);
g_assert_true(webkit_web_context_is_automation_allowed(test->m_webContext.get()));
// There can't be more than one context with automation enabled
GRefPtr<WebKitWebContext> otherContext = adoptGRef(webkit_web_context_new());
Test::removeLogFatalFlag(G_LOG_LEVEL_WARNING);
webkit_web_context_set_automation_allowed(otherContext.get(), TRUE);
Test::addLogFatalFlag(G_LOG_LEVEL_WARNING);
g_assert_false(webkit_web_context_is_automation_allowed(otherContext.get()));
session = test->requestSession(sessionID.utf8().data());
g_assert_cmpstr(webkit_automation_session_get_id(session), ==, sessionID.utf8().data());
g_assert_cmpuint(test->m_target.id, >, 0);
ASSERT_CMP_CSTRING(test->m_target.name, ==, sessionID.utf8());
g_assert_false(test->m_target.isPaired);
// Will fail to create a browsing context when not creating a web view (or not handling the signal).
g_assert_false(test->createTopLevelBrowsingContext(nullptr));
// Will also fail if the web view is not controlled by automation.
auto webView = Test::adoptView(Test::createWebView(test->m_webContext.get()));
g_assert_false(webkit_web_view_is_controlled_by_automation(webView.get()));
g_assert_false(test->createTopLevelBrowsingContext(webView.get()));
// And will work with a proper web view.
webView = Test::adoptView(g_object_new(WEBKIT_TYPE_WEB_VIEW,
#if PLATFORM(WPE)
"backend", Test::createWebViewBackend(),
#endif
"web-context", test->m_webContext.get(),
"is-controlled-by-automation", TRUE,
nullptr));
g_assert_true(webkit_web_view_is_controlled_by_automation(webView.get()));
g_assert_cmpuint(webkit_web_view_get_automation_presentation_type(webView.get()), ==, WEBKIT_AUTOMATION_BROWSING_CONTEXT_PRESENTATION_WINDOW);
g_assert_true(test->createTopLevelBrowsingContext(webView.get()));
auto newWebViewInWindow = Test::adoptView(g_object_new(WEBKIT_TYPE_WEB_VIEW,
#if PLATFORM(WPE)
"backend", Test::createWebViewBackend(),
#endif
"web-context", test->m_webContext.get(),
"is-controlled-by-automation", TRUE,
nullptr));
g_assert_true(webkit_web_view_is_controlled_by_automation(newWebViewInWindow.get()));
g_assert_cmpuint(webkit_web_view_get_automation_presentation_type(newWebViewInWindow.get()), ==, WEBKIT_AUTOMATION_BROWSING_CONTEXT_PRESENTATION_WINDOW);
g_assert_true(test->createNewWindow(newWebViewInWindow.get()));
auto newWebViewInTab = Test::adoptView(g_object_new(WEBKIT_TYPE_WEB_VIEW,
#if PLATFORM(WPE)
"backend", Test::createWebViewBackend(),
#endif
"web-context", test->m_webContext.get(),
"is-controlled-by-automation", TRUE,
"automation-presentation-type", WEBKIT_AUTOMATION_BROWSING_CONTEXT_PRESENTATION_TAB,
nullptr));
g_assert_true(webkit_web_view_is_controlled_by_automation(newWebViewInTab.get()));
g_assert_cmpuint(webkit_web_view_get_automation_presentation_type(newWebViewInTab.get()), ==, WEBKIT_AUTOMATION_BROWSING_CONTEXT_PRESENTATION_TAB);
g_assert_true(test->createNewTab(newWebViewInTab.get()));
webkit_web_context_set_automation_allowed(test->m_webContext.get(), FALSE);
}
static void testAutomationSessionApplicationInfo(Test* test, gconstpointer)
{
WebKitApplicationInfo* info = webkit_application_info_new();
g_assert_cmpstr(webkit_application_info_get_name(info), ==, g_get_prgname());
webkit_application_info_set_name(info, "WebKitGTKBrowser");
g_assert_cmpstr(webkit_application_info_get_name(info), ==, "WebKitGTKBrowser");
webkit_application_info_set_name(info, nullptr);
g_assert_cmpstr(webkit_application_info_get_name(info), ==, g_get_prgname());
guint64 major, minor, micro;
webkit_application_info_get_version(info, &major, nullptr, nullptr);
g_assert_cmpuint(major, ==, 0);
webkit_application_info_set_version(info, 1, 2, 3);
webkit_application_info_get_version(info, &major, &minor, &micro);
g_assert_cmpuint(major, ==, 1);
g_assert_cmpuint(minor, ==, 2);
g_assert_cmpuint(micro, ==, 3);
webkit_application_info_unref(info);
}
void beforeAll()
{
g_setenv("WEBKIT_INSPECTOR_SERVER", "127.0.0.1:2229", TRUE);
AutomationTest::add("WebKitAutomationSession", "request-session", testAutomationSessionRequestSession);
Test::add("WebKitAutomationSession", "application-info", testAutomationSessionApplicationInfo);
}
void afterAll()
{
g_unsetenv("WEBKIT_INSPECTOR_SERVER");
}