123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- /*
- * Copyright (C) 2013 Sony Computer Entertainment Inc.
- *
- * 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 SONY COMPUTER ENTERTAINMENT INC. ``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 SONY COMPUTER ENTERTAINMENT INC.
- * 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 "InspectorServerManx.h"
- #include "DocumentLoader.h"
- #include "FileSystem.h"
- #include "Frame.h"
- #include "FrameLoader.h"
- #include "HTTPRequest.h"
- #include "InspectorClientManx.h"
- #include "InspectorProtocolVersion.h"
- #include "InspectorValues.h"
- #include "MIMETypeRegistry.h"
- #include "Page.h"
- #include "WebKitVersion.h"
- #include "WebSocketServerConnection.h"
- #include "WebViewPrivate.h"
- #include <errno.h>
- #include <manx/NetworkProfile.h>
- #include <wtf/Assertions.h>
- #include <wtf/text/CString.h>
- using namespace WebKit;
- namespace {
- // Use the last revision before the Blink/WebKit fork as our reference revision
- // (cf. https://groups.google.com/a/chromium.org/d/msg/blink-dev/J41PSKuMan0/gD5xcqicqP8J)
- // Revision numbering was maintained in Blink, but since history has started diverging after
- // that commit the WebKit revisions aren't mappable anymore.
- // ChromeDriver adjusts some event handling depending on that revision number.
- // (grep 'blink_revision' in chromedriver/chrome/navigation_tracker.cc)
- const int webkitRevision = 147503;
- }
- namespace WebCore {
- static unsigned clientIdFromRequestPath(const String& path)
- {
- size_t start = path.reverseFind('/');
- String numberString = path.substring(start + 1, path.length() - start - 1);
- bool ok = false;
- unsigned number = numberString.toUIntStrict(&ok);
- if (!ok)
- return 0;
- return number;
- }
- InspectorServerManx& InspectorServerManx::shared()
- {
- static InspectorServerManx& server = *new InspectorServerManx;
- return server;
- }
- InspectorServerManx::InspectorServerManx()
- : WebSocketServer(this)
- , m_nextAvailableClientId(1)
- {
- }
- InspectorServerManx::~InspectorServerManx()
- {
- // Close any remaining open connections.
- HashMap<unsigned, WebSocketServerConnection*>::iterator end = m_connectionMap.end();
- for (HashMap<unsigned, WebSocketServerConnection*>::iterator it = m_connectionMap.begin(); it != end; ++it) {
- WebSocketServerConnection* connection = it->value;
- InspectorClientManx* client = m_clientMap.get(connection->identifier());
- closeConnection(client, connection);
- }
- }
- int InspectorServerManx::registerClient(InspectorClientManx* client)
- {
- #ifndef ASSERT_DISABLED
- for (auto it = m_clientMap.begin(); it != m_clientMap.end(); ++it)
- ASSERT(it->second != client);
- #endif
- int clientId = m_nextAvailableClientId++;
- m_clientMap.set(clientId, client);
- updateServerState();
- return clientId;
- }
- void InspectorServerManx::unregisterClient(int clientId)
- {
- m_clientMap.remove(clientId);
- WebSocketServerConnection* connection = m_connectionMap.get(clientId);
- if (connection)
- closeConnection(0, connection);
- updateServerState();
- }
- void InspectorServerManx::sendMessageOverConnection(unsigned clientIdForConnection, const String& message)
- {
- WebSocketServerConnection* connection = m_connectionMap.get(clientIdForConnection);
- if (connection)
- connection->sendWebSocketMessage(message);
- }
- void InspectorServerManx::didReceiveUnrecognizedHTTPRequest(WebSocketServerConnection* connection, PassRefPtr<HTTPRequest> request)
- {
- // request->url() contains only the path extracted from the HTTP request line
- // and KURL is poor at parsing incomplete URLs, so extract the interesting parts manually.
- String path = request->url();
- size_t pathEnd = path.find('?');
- if (pathEnd == notFound)
- pathEnd = path.find('#');
- if (pathEnd != notFound)
- path.truncate(pathEnd);
- // Ask for the complete payload in memory for the sake of simplicity. A more efficient way would be
- // to ask for header data and then let the platform abstraction write the payload straight on the connection.
- Vector<char> body;
- String contentType;
- bool found = platformResourceForPath(path, body, contentType);
- HTTPHeaderMap headerFields;
- headerFields.set("Connection", "close");
- headerFields.set("Content-Length", String::number(body.size()));
- if (found)
- headerFields.set("Content-Type", contentType);
- // Send when ready and close immediately afterwards.
- connection->sendHTTPResponseHeader(found ? 200 : 404, found ? "OK" : "Not Found", headerFields);
- connection->sendRawData(body.data(), body.size());
- connection->shutdownAfterSendOrNow();
- }
- bool InspectorServerManx::didReceiveWebSocketUpgradeHTTPRequest(WebSocketServerConnection*, PassRefPtr<HTTPRequest> request)
- {
- String path = request->url();
- // NOTE: Keep this in sync with WebCore/inspector/front-end/inspector.js.
- DEFINE_STATIC_LOCAL(const String, inspectorWebSocketConnectionPathPrefix, ("/devtools/page/"));
- // Unknown path requested.
- if (!path.startsWith(inspectorWebSocketConnectionPathPrefix))
- return false;
- int clientId = clientIdFromRequestPath(path);
- // Invalid client id.
- if (!clientId)
- return false;
- // There is no client for that client id.
- InspectorClientManx* client = m_clientMap.get(clientId);
- if (!client)
- return false;
- return true;
- }
- void InspectorServerManx::didEstablishWebSocketConnection(WebSocketServerConnection* connection, PassRefPtr<HTTPRequest> request)
- {
- String path = request->url();
- unsigned clientId = clientIdFromRequestPath(path);
- ASSERT(clientId);
- // Ignore connections to a page that already have a remote inspector connected.
- if (m_connectionMap.contains(clientId)) {
- LOG_ERROR("A remote inspector connection already exist for client ID %d. Ignoring.", clientId);
- connection->shutdownNow();
- return;
- }
- // Map the clientId to the connection in case we need to close the connection locally.
- connection->setIdentifier(clientId);
- m_connectionMap.set(clientId, connection);
- InspectorClientManx* client = m_clientMap.get(clientId);
- client->remoteFrontendConnected();
- }
- void InspectorServerManx::didReceiveWebSocketMessage(WebSocketServerConnection* connection, const String& message)
- {
- // Dispatch incoming remote message locally.
- unsigned clientId = connection->identifier();
- ASSERT(clientId);
- InspectorClientManx* client = m_clientMap.get(clientId);
- client->dispatchMessageFromRemoteFrontend(message);
- }
- void InspectorServerManx::didCloseWebSocketConnection(WebSocketServerConnection* connection)
- {
- // Connection has already shut down.
- unsigned clientId = connection->identifier();
- if (!clientId)
- return;
- // The socket closing means the remote side has caused the close.
- InspectorClientManx* client = m_clientMap.get(clientId);
- closeConnection(client, connection);
- }
- String InspectorServerManx::inspectorFrontendPath() const
- {
- return inspectorBasePath() + String("/inspector.html");
- }
- String InspectorServerManx::inspectorBasePath() const
- {
- return getenv("MANX_INSPECTOR_SERVER_PATH");
- }
- bool InspectorServerManx::platformResourceForPath(const String& path, Vector<char>& data, String& contentType)
- {
- if (!path.startsWith('/'))
- return false;
- if (m_clientMap.isEmpty())
- return false;
- if (path.startsWith("/json")) {
- const String jsonSubpath = path.substring(5);
- if (jsonSubpath.isEmpty()) {
- RefPtr<InspectorArray> message = InspectorArray::create();
- for (HashMap<unsigned, InspectorClientManx*>::const_iterator it = m_clientMap.begin(); it != m_clientMap.end(); ++it) {
- InspectorClientManx* client = it->value;
- RefPtr<InspectorObject> tabInfo = InspectorObject::create();
- tabInfo->setString("id", String::number(it->key));
- if (client->page()->mainFrame() && client->page()->mainFrame()->loader()->activeDocumentLoader()) {
- tabInfo->setString("title", client->page()->mainFrame()->loader()->activeDocumentLoader()->title().string());
- tabInfo->setString("url", client->page()->mainFrame()->loader()->activeDocumentLoader()->url());
- }
- tabInfo->setString("type", "page");
- tabInfo->setString("webSocketDebuggerUrl", String::format("ws://%s:%u/devtools/page/%u", bindAddress().latin1().data(), port(), it->key));
- char* envValue = getenv("MANX_INSPECTOR_SERVER_PROCESS_NAME");
- if (envValue)
- tabInfo->setString("processDisplayName", envValue);
- message->pushObject(tabInfo);
- }
- CString tabInfo = message->toJSONString().utf8();
- data.append(tabInfo.data(), tabInfo.length());
- } else if (jsonSubpath == "/version") {
- InspectorClientManx* client = m_clientMap.begin()->value;
- ASSERT(client);
- // Basic version info needed by the WebDriver server
- RefPtr<InspectorObject> versionInfo = InspectorObject::create();
- versionInfo->setString("Browser", ""); // Intentionally kept blank, indicates custom browser to ChromeDriver
- versionInfo->setString("Protocol-Version", inspectorProtocolVersion());
- versionInfo->setString("User-Agent", client->webview()->userAgent());
- versionInfo->setString("WebKit-Version", String::format("%d.%d (@%d)", WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, webkitRevision));
- CString versionInfoString = versionInfo->toJSONString().utf8();
- data.append(versionInfoString.data(), versionInfoString.length());
- } else {
- // Unsupported command
- return false;
- }
- contentType = "application/json; charset=utf-8";
- return true;
- }
- // Point the default path to display the inspector landing page.
- // All other paths are mapped directly to a resource, if possible.
- const String localPath = (path == "/") ? inspectorFrontendPath() : inspectorBasePath() + path;
- PlatformFileHandle handle = openFile(localPath, OpenForRead);
- if (!isHandleValid(handle)) {
- LOG_ERROR("WebInspectorServer: couldn't access platform resource '%s' for reading! (%d)", localPath.utf8().data(), errno);
- return false;
- }
- long long fileSize;
- if (!getFileSize(localPath, fileSize)) {
- LOG_ERROR("WebInspectorServer: couldn't get file size for '%s'! (%d)", localPath.utf8().data(), errno);
- closeFile(handle);
- return false;
- }
- data.grow(fileSize);
- if (readFromFile(handle, data.data(), data.size()) < fileSize) {
- LOG_ERROR("WebInspectorServer: didn't read all contents of file '%s'! (%d)", localPath.utf8().data(), errno);
- closeFile(handle);
- return false;
- }
- closeFile(handle);
- contentType = MIMETypeRegistry::getMIMETypeForPath(localPath);
- return true;
- }
- void InspectorServerManx::closeConnection(InspectorClientManx* client, WebSocketServerConnection* connection)
- {
- // Local side cleanup.
- if (client)
- client->remoteFrontendDisconnected();
- // Remote side cleanup.
- m_connectionMap.remove(connection->identifier());
- connection->setIdentifier(0);
- connection->shutdownNow();
- }
- void InspectorServerManx::updateServerState()
- {
- char* envValue = getenv("MANX_INSPECTOR_SERVER_PORT");
- if (!envValue)
- return;
- if (!m_clientMap.isEmpty() && serverState() == Closed) {
- const int inspectorServerPort = atoi(envValue);
- if (inspectorServerPort <= 0) {
- LOG_ERROR("Invalid Inspector Server port!");
- return;
- }
- if (!listen(Manx::NetworkProfile::ipAddress(), inspectorServerPort))
- LOG_ERROR("Couldn't start the Inspector Server!");
- } else if (m_clientMap.isEmpty() && serverState() == Listening) {
- close();
- ASSERT(serverState() == Closed);
- }
- }
- }
|