| /* |
| * 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(); |
| } |