blob: 43f4d481706a48b1c3f2a21b61d62d523a997f21 [file] [log] [blame]
/*
* Copyright (C) 2012 Samsung Electronics Ltd. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "WebViewTest.h"
#include <wtf/glib/GRefPtr.h>
#include <wtf/text/WTFString.h>
// Name of the test server application creating the webView object.
static const char* gTestServerAppName = "InspectorTestServer";
// Max seconds to wait for the test server before inspecting it.
static const int gMaxWaitForChild = 5;
// The PID for the test server running, so we can kill it if needed.
static GPid gChildProcessPid = 0;
// Whether the child has replied and it's ready.
static bool gChildIsReady = false;
static void stopTestServer()
{
// Do nothing if there's no server running.
if (!gChildProcessPid)
return;
g_spawn_close_pid(gChildProcessPid);
kill(gChildProcessPid, SIGTERM);
gChildProcessPid = 0;
}
static void sigAbortHandler(int sigNum)
{
// Just stop the test server if SIGABRT was received.
stopTestServer();
}
static gpointer testServerMonitorThreadFunc(gpointer)
{
// Wait for the specified timeout to happen.
g_usleep(gMaxWaitForChild * G_USEC_PER_SEC);
// Kill the child process if not ready yet.
if (!gChildIsReady)
stopTestServer();
g_thread_exit(0);
return 0;
}
static void startTestServerMonitor()
{
gChildIsReady = false;
g_thread_new("TestServerMonitor", testServerMonitorThreadFunc, 0);
}
static void startTestServer()
{
// Prepare argv[] for spawning the server process.
GUniquePtr<char> testServerPath(g_build_filename(WEBKIT_EXEC_PATH, "TestWebKitAPI", "WebKit2Gtk", gTestServerAppName, NULL));
// We install a handler to ensure that we kill the child process
// if the parent dies because of whatever the reason is.
signal(SIGABRT, sigAbortHandler);
char* testServerArgv[2];
testServerArgv[0] = testServerPath.get();
testServerArgv[1] = 0;
// Spawn the server, getting its stdout file descriptor to set a
// communication channel, so we know when it's ready.
int childStdout = 0;
g_assert(g_spawn_async_with_pipes(0, testServerArgv, 0, static_cast<GSpawnFlags>(0), 0, 0,
&gChildProcessPid, 0, &childStdout, 0, 0));
// Start monitoring the test server (in a separate thread) to
// ensure we don't block on the child process more than a timeout.
startTestServerMonitor();
char msg[2];
GIOChannel* ioChannel = g_io_channel_unix_new(childStdout);
if (g_io_channel_read_chars(ioChannel, msg, 2, 0, 0) == G_IO_STATUS_NORMAL) {
// Check whether the server sent a message saying it's ready
// and store the result globally, so the monitor can see it.
gChildIsReady = msg[0] == 'O' && msg[1] == 'K';
}
g_io_channel_unref(ioChannel);
close(childStdout);
// The timeout was reached and the server is not ready yet, so
// stop it inmediately, and let the unit tests fail.
if (!gChildIsReady)
stopTestServer();
}
class InspectorServerTest: public WebViewTest {
public:
MAKE_GLIB_TEST_FIXTURE(InspectorServerTest);
InspectorServerTest()
: WebViewTest()
{
}
bool getPageList()
{
loadHtml("<script type=\"text/javascript\">\n"
"var pages;\n"
"var xhr = new XMLHttpRequest;\n"
"xhr.open(\"GET\", \"/pagelist.json\");\n"
"xhr.onload = function(e) {\n"
"if (xhr.status == 200) {\n"
"pages = JSON.parse(xhr.responseText);\n"
"document.title = \"OK\";\n"
"} else \n"
"document.title = \"FAIL\";\n"
"}\n"
"xhr.send();\n"
"</script>\n",
"http://127.0.0.1:2999/");
waitUntilTitleChanged();
if (!strcmp(webkit_web_view_get_title(m_webView), "OK"))
return true;
return false;
}
~InspectorServerTest()
{
}
};
// Test to get inspector server page list from the test server.
// Should contain only one entry pointing to http://127.0.0.1:2999/webinspector/Main.html?page=1
static void testInspectorServerPageList(InspectorServerTest* test, gconstpointer)
{
GUniqueOutPtr<GError> error;
test->showInWindowAndWaitUntilMapped(GTK_WINDOW_TOPLEVEL);
g_assert(test->getPageList());
WebKitJavascriptResult* javascriptResult = test->runJavaScriptAndWaitUntilFinished("pages.length;", &error.outPtr());
g_assert(javascriptResult);
g_assert(!error.get());
g_assert_cmpint(WebViewTest::javascriptResultToNumber(javascriptResult), ==, 1);
javascriptResult = test->runJavaScriptAndWaitUntilFinished("pages[0].id;", &error.outPtr());
g_assert(javascriptResult);
g_assert(!error.get());
int pageId = WebViewTest::javascriptResultToNumber(javascriptResult);
GUniquePtr<char> valueString;
javascriptResult = test->runJavaScriptAndWaitUntilFinished("pages[0].url;", &error.outPtr());
g_assert(javascriptResult);
g_assert(!error.get());
valueString.reset(WebViewTest::javascriptResultToCString(javascriptResult));
g_assert_cmpstr(valueString.get(), ==, "http://127.0.0.1:2999/");
javascriptResult = test->runJavaScriptAndWaitUntilFinished("pages[0].inspectorUrl;", &error.outPtr());
g_assert(javascriptResult);
g_assert(!error.get());
valueString.reset(WebViewTest::javascriptResultToCString(javascriptResult));
String validInspectorURL = String("/Main.html?page=") + String::number(pageId);
ASSERT_CMP_CSTRING(valueString.get(), ==, validInspectorURL.utf8());
}
// Test sending a raw remote debugging message through our web socket server.
// For this specific message see: http://code.google.com/chrome/devtools/docs/protocol/tot/runtime.html#command-evaluate
static void testRemoteDebuggingMessage(InspectorServerTest* test, gconstpointer)
{
test->showInWindowAndWaitUntilMapped(GTK_WINDOW_TOPLEVEL);
test->loadHtml("<script type=\"text/javascript\">\n"
"var socket = new WebSocket('ws://127.0.0.1:2999/devtools/page/1');\n"
"socket.onmessage = function(message) {\n"
"var response = JSON.parse(message.data);\n"
"if (response.id === 1)\n"
"document.title = response.result.result.value;\n"
"else\n"
"document.title = \"FAIL\";\n"
"}\n"
"socket.onopen = function() {\n"
"socket.send('{\"id\": 1, \"method\": \"Runtime.evaluate\", \"params\": {\"expression\": \"2 + 2\" } }');\n"
"}\n"
"</script>",
"http://127.0.0.1:2999/");
test->waitUntilTitleChanged();
g_assert_cmpstr(webkit_web_view_get_title(test->m_webView), ==, "4");
}
static void openRemoteDebuggingSession(InspectorServerTest* test, gconstpointer)
{
// To test the whole pipeline this exploits a behavior of the inspector front-end which won't provide the page address as title unless the
// debugging session was established correctly through web socket.
// In our case page URL should be http://127.0.0.1:2999/
// So this test case will fail if:
// - The page list didn't return a valid inspector URL
// - Or the front-end couldn't be loaded through the inspector HTTP server
// - Or the web socket connection couldn't be established between the front-end and the page through the inspector server
// Let's see if this test isn't raising too many false positives, in which case we should use a better predicate if available.
test->showInWindowAndWaitUntilMapped(GTK_WINDOW_TOPLEVEL);
g_assert(test->getPageList());
GUniqueOutPtr<GError> error;
WebKitJavascriptResult* javascriptResult = test->runJavaScriptAndWaitUntilFinished("pages[0].inspectorUrl;", &error.outPtr());
g_assert(javascriptResult);
g_assert(!error.get());
String resolvedURL = String("http://127.0.0.1:2999") + String::fromUTF8(WebViewTest::javascriptResultToCString(javascriptResult));
test->loadURI(resolvedURL.utf8().data());
test->waitUntilLoadFinished();
javascriptResult = test->runJavaScriptAndWaitUntilFinished("document.title", &error.outPtr());
g_assert(javascriptResult);
g_assert(!error.get());
GUniquePtr<char> title(WebViewTest::javascriptResultToCString(javascriptResult));
g_assert_cmpstr(title.get(), ==, "127.0.0.1");
}
static void sendIncompleteRequest(InspectorServerTest* test, gconstpointer)
{
GUniqueOutPtr<GError> error;
// Connect to the inspector server.
GRefPtr<GSocketClient> client = adoptGRef(g_socket_client_new());
GRefPtr<GSocketConnection> connection = adoptGRef(g_socket_client_connect_to_host(client.get(), "127.0.0.1", 2999, 0, &error.outPtr()));
g_assert_no_error(error.get());
// Send incomplete request (missing blank line after headers) and check if inspector server
// replies. The server should not reply to an incomplete request and the test should timeout
// on read.
GOutputStream* ostream = g_io_stream_get_output_stream(G_IO_STREAM(connection.get()));
// Request missing blank line after headers.
const gchar* incompleteRequest = "GET /devtools/page/1 HTTP/1.1\r\nHost: Localhost\r\n";
g_output_stream_write(ostream, incompleteRequest, strlen(incompleteRequest), 0, &error.outPtr());
g_assert_no_error(error.get());
GInputStream* istream = g_io_stream_get_input_stream(G_IO_STREAM(connection.get()));
char response[16];
memset(response, 0, sizeof(response));
GRefPtr<GCancellable> cancel = adoptGRef(g_cancellable_new());
g_input_stream_read_async(istream, response, sizeof(response) - 1, G_PRIORITY_DEFAULT, cancel.get(), 0, 0);
// Give a chance for the server to reply.
test->wait(1);
g_cancellable_cancel(cancel.get());
// If we got any answer it means the server replied to an incomplete request, lets fail.
g_assert(response[0] == '\0');
}
void beforeAll()
{
// Overwrite WEBKIT_INSPECTOR_SERVER variable with default IP address but different port to avoid conflict with the test inspector server page.
g_setenv("WEBKIT_INSPECTOR_SERVER", "127.0.0.1:2998", TRUE);
startTestServer();
InspectorServerTest::add("WebKitWebInspectorServer", "test-page-list", testInspectorServerPageList);
InspectorServerTest::add("WebKitWebInspectorServer", "test-remote-debugging-message", testRemoteDebuggingMessage);
InspectorServerTest::add("WebKitWebInspectorServer", "test-open-debugging-session", openRemoteDebuggingSession);
InspectorServerTest::add("WebKitWebInspectorServer", "test-incomplete-request", sendIncompleteRequest);
}
void afterAll()
{
stopTestServer();
}