InspectorServerManx.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. /*
  2. * Copyright (C) 2013 Sony Computer Entertainment Inc.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions
  6. * are met:
  7. * 1. Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * 2. Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. *
  13. * THIS SOFTWARE IS PROVIDED BY SONY COMPUTER ENTERTAINMENT INC. ``AS IS''
  14. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  15. * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  16. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SONY COMPUTER ENTERTAINMENT INC.
  17. * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  18. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  19. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  20. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  21. * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  22. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  23. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  24. */
  25. #include "config.h"
  26. #include "InspectorServerManx.h"
  27. #include "DocumentLoader.h"
  28. #include "FileSystem.h"
  29. #include "Frame.h"
  30. #include "FrameLoader.h"
  31. #include "HTTPRequest.h"
  32. #include "InspectorClientManx.h"
  33. #include "InspectorProtocolVersion.h"
  34. #include "InspectorValues.h"
  35. #include "MIMETypeRegistry.h"
  36. #include "Page.h"
  37. #include "WebKitVersion.h"
  38. #include "WebSocketServerConnection.h"
  39. #include "WebViewPrivate.h"
  40. #include <errno.h>
  41. #include <manx/NetworkProfile.h>
  42. #include <wtf/Assertions.h>
  43. #include <wtf/text/CString.h>
  44. using namespace WebKit;
  45. namespace {
  46. // Use the last revision before the Blink/WebKit fork as our reference revision
  47. // (cf. https://groups.google.com/a/chromium.org/d/msg/blink-dev/J41PSKuMan0/gD5xcqicqP8J)
  48. // Revision numbering was maintained in Blink, but since history has started diverging after
  49. // that commit the WebKit revisions aren't mappable anymore.
  50. // ChromeDriver adjusts some event handling depending on that revision number.
  51. // (grep 'blink_revision' in chromedriver/chrome/navigation_tracker.cc)
  52. const int webkitRevision = 147503;
  53. }
  54. namespace WebCore {
  55. static unsigned clientIdFromRequestPath(const String& path)
  56. {
  57. size_t start = path.reverseFind('/');
  58. String numberString = path.substring(start + 1, path.length() - start - 1);
  59. bool ok = false;
  60. unsigned number = numberString.toUIntStrict(&ok);
  61. if (!ok)
  62. return 0;
  63. return number;
  64. }
  65. InspectorServerManx& InspectorServerManx::shared()
  66. {
  67. static InspectorServerManx& server = *new InspectorServerManx;
  68. return server;
  69. }
  70. InspectorServerManx::InspectorServerManx()
  71. : WebSocketServer(this)
  72. , m_nextAvailableClientId(1)
  73. {
  74. }
  75. InspectorServerManx::~InspectorServerManx()
  76. {
  77. // Close any remaining open connections.
  78. HashMap<unsigned, WebSocketServerConnection*>::iterator end = m_connectionMap.end();
  79. for (HashMap<unsigned, WebSocketServerConnection*>::iterator it = m_connectionMap.begin(); it != end; ++it) {
  80. WebSocketServerConnection* connection = it->value;
  81. InspectorClientManx* client = m_clientMap.get(connection->identifier());
  82. closeConnection(client, connection);
  83. }
  84. }
  85. int InspectorServerManx::registerClient(InspectorClientManx* client)
  86. {
  87. #ifndef ASSERT_DISABLED
  88. for (auto it = m_clientMap.begin(); it != m_clientMap.end(); ++it)
  89. ASSERT(it->second != client);
  90. #endif
  91. int clientId = m_nextAvailableClientId++;
  92. m_clientMap.set(clientId, client);
  93. updateServerState();
  94. return clientId;
  95. }
  96. void InspectorServerManx::unregisterClient(int clientId)
  97. {
  98. m_clientMap.remove(clientId);
  99. WebSocketServerConnection* connection = m_connectionMap.get(clientId);
  100. if (connection)
  101. closeConnection(0, connection);
  102. updateServerState();
  103. }
  104. void InspectorServerManx::sendMessageOverConnection(unsigned clientIdForConnection, const String& message)
  105. {
  106. WebSocketServerConnection* connection = m_connectionMap.get(clientIdForConnection);
  107. if (connection)
  108. connection->sendWebSocketMessage(message);
  109. }
  110. void InspectorServerManx::didReceiveUnrecognizedHTTPRequest(WebSocketServerConnection* connection, PassRefPtr<HTTPRequest> request)
  111. {
  112. // request->url() contains only the path extracted from the HTTP request line
  113. // and KURL is poor at parsing incomplete URLs, so extract the interesting parts manually.
  114. String path = request->url();
  115. size_t pathEnd = path.find('?');
  116. if (pathEnd == notFound)
  117. pathEnd = path.find('#');
  118. if (pathEnd != notFound)
  119. path.truncate(pathEnd);
  120. // Ask for the complete payload in memory for the sake of simplicity. A more efficient way would be
  121. // to ask for header data and then let the platform abstraction write the payload straight on the connection.
  122. Vector<char> body;
  123. String contentType;
  124. bool found = platformResourceForPath(path, body, contentType);
  125. HTTPHeaderMap headerFields;
  126. headerFields.set("Connection", "close");
  127. headerFields.set("Content-Length", String::number(body.size()));
  128. if (found)
  129. headerFields.set("Content-Type", contentType);
  130. // Send when ready and close immediately afterwards.
  131. connection->sendHTTPResponseHeader(found ? 200 : 404, found ? "OK" : "Not Found", headerFields);
  132. connection->sendRawData(body.data(), body.size());
  133. connection->shutdownAfterSendOrNow();
  134. }
  135. bool InspectorServerManx::didReceiveWebSocketUpgradeHTTPRequest(WebSocketServerConnection*, PassRefPtr<HTTPRequest> request)
  136. {
  137. String path = request->url();
  138. // NOTE: Keep this in sync with WebCore/inspector/front-end/inspector.js.
  139. DEFINE_STATIC_LOCAL(const String, inspectorWebSocketConnectionPathPrefix, ("/devtools/page/"));
  140. // Unknown path requested.
  141. if (!path.startsWith(inspectorWebSocketConnectionPathPrefix))
  142. return false;
  143. int clientId = clientIdFromRequestPath(path);
  144. // Invalid client id.
  145. if (!clientId)
  146. return false;
  147. // There is no client for that client id.
  148. InspectorClientManx* client = m_clientMap.get(clientId);
  149. if (!client)
  150. return false;
  151. return true;
  152. }
  153. void InspectorServerManx::didEstablishWebSocketConnection(WebSocketServerConnection* connection, PassRefPtr<HTTPRequest> request)
  154. {
  155. String path = request->url();
  156. unsigned clientId = clientIdFromRequestPath(path);
  157. ASSERT(clientId);
  158. // Ignore connections to a page that already have a remote inspector connected.
  159. if (m_connectionMap.contains(clientId)) {
  160. LOG_ERROR("A remote inspector connection already exist for client ID %d. Ignoring.", clientId);
  161. connection->shutdownNow();
  162. return;
  163. }
  164. // Map the clientId to the connection in case we need to close the connection locally.
  165. connection->setIdentifier(clientId);
  166. m_connectionMap.set(clientId, connection);
  167. InspectorClientManx* client = m_clientMap.get(clientId);
  168. client->remoteFrontendConnected();
  169. }
  170. void InspectorServerManx::didReceiveWebSocketMessage(WebSocketServerConnection* connection, const String& message)
  171. {
  172. // Dispatch incoming remote message locally.
  173. unsigned clientId = connection->identifier();
  174. ASSERT(clientId);
  175. InspectorClientManx* client = m_clientMap.get(clientId);
  176. client->dispatchMessageFromRemoteFrontend(message);
  177. }
  178. void InspectorServerManx::didCloseWebSocketConnection(WebSocketServerConnection* connection)
  179. {
  180. // Connection has already shut down.
  181. unsigned clientId = connection->identifier();
  182. if (!clientId)
  183. return;
  184. // The socket closing means the remote side has caused the close.
  185. InspectorClientManx* client = m_clientMap.get(clientId);
  186. closeConnection(client, connection);
  187. }
  188. String InspectorServerManx::inspectorFrontendPath() const
  189. {
  190. return inspectorBasePath() + String("/inspector.html");
  191. }
  192. String InspectorServerManx::inspectorBasePath() const
  193. {
  194. return getenv("MANX_INSPECTOR_SERVER_PATH");
  195. }
  196. bool InspectorServerManx::platformResourceForPath(const String& path, Vector<char>& data, String& contentType)
  197. {
  198. if (!path.startsWith('/'))
  199. return false;
  200. if (m_clientMap.isEmpty())
  201. return false;
  202. if (path.startsWith("/json")) {
  203. const String jsonSubpath = path.substring(5);
  204. if (jsonSubpath.isEmpty()) {
  205. RefPtr<InspectorArray> message = InspectorArray::create();
  206. for (HashMap<unsigned, InspectorClientManx*>::const_iterator it = m_clientMap.begin(); it != m_clientMap.end(); ++it) {
  207. InspectorClientManx* client = it->value;
  208. RefPtr<InspectorObject> tabInfo = InspectorObject::create();
  209. tabInfo->setString("id", String::number(it->key));
  210. if (client->page()->mainFrame() && client->page()->mainFrame()->loader()->activeDocumentLoader()) {
  211. tabInfo->setString("title", client->page()->mainFrame()->loader()->activeDocumentLoader()->title().string());
  212. tabInfo->setString("url", client->page()->mainFrame()->loader()->activeDocumentLoader()->url());
  213. }
  214. tabInfo->setString("type", "page");
  215. tabInfo->setString("webSocketDebuggerUrl", String::format("ws://%s:%u/devtools/page/%u", bindAddress().latin1().data(), port(), it->key));
  216. char* envValue = getenv("MANX_INSPECTOR_SERVER_PROCESS_NAME");
  217. if (envValue)
  218. tabInfo->setString("processDisplayName", envValue);
  219. message->pushObject(tabInfo);
  220. }
  221. CString tabInfo = message->toJSONString().utf8();
  222. data.append(tabInfo.data(), tabInfo.length());
  223. } else if (jsonSubpath == "/version") {
  224. InspectorClientManx* client = m_clientMap.begin()->value;
  225. ASSERT(client);
  226. // Basic version info needed by the WebDriver server
  227. RefPtr<InspectorObject> versionInfo = InspectorObject::create();
  228. versionInfo->setString("Browser", ""); // Intentionally kept blank, indicates custom browser to ChromeDriver
  229. versionInfo->setString("Protocol-Version", inspectorProtocolVersion());
  230. versionInfo->setString("User-Agent", client->webview()->userAgent());
  231. versionInfo->setString("WebKit-Version", String::format("%d.%d (@%d)", WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, webkitRevision));
  232. CString versionInfoString = versionInfo->toJSONString().utf8();
  233. data.append(versionInfoString.data(), versionInfoString.length());
  234. } else {
  235. // Unsupported command
  236. return false;
  237. }
  238. contentType = "application/json; charset=utf-8";
  239. return true;
  240. }
  241. // Point the default path to display the inspector landing page.
  242. // All other paths are mapped directly to a resource, if possible.
  243. const String localPath = (path == "/") ? inspectorFrontendPath() : inspectorBasePath() + path;
  244. PlatformFileHandle handle = openFile(localPath, OpenForRead);
  245. if (!isHandleValid(handle)) {
  246. LOG_ERROR("WebInspectorServer: couldn't access platform resource '%s' for reading! (%d)", localPath.utf8().data(), errno);
  247. return false;
  248. }
  249. long long fileSize;
  250. if (!getFileSize(localPath, fileSize)) {
  251. LOG_ERROR("WebInspectorServer: couldn't get file size for '%s'! (%d)", localPath.utf8().data(), errno);
  252. closeFile(handle);
  253. return false;
  254. }
  255. data.grow(fileSize);
  256. if (readFromFile(handle, data.data(), data.size()) < fileSize) {
  257. LOG_ERROR("WebInspectorServer: didn't read all contents of file '%s'! (%d)", localPath.utf8().data(), errno);
  258. closeFile(handle);
  259. return false;
  260. }
  261. closeFile(handle);
  262. contentType = MIMETypeRegistry::getMIMETypeForPath(localPath);
  263. return true;
  264. }
  265. void InspectorServerManx::closeConnection(InspectorClientManx* client, WebSocketServerConnection* connection)
  266. {
  267. // Local side cleanup.
  268. if (client)
  269. client->remoteFrontendDisconnected();
  270. // Remote side cleanup.
  271. m_connectionMap.remove(connection->identifier());
  272. connection->setIdentifier(0);
  273. connection->shutdownNow();
  274. }
  275. void InspectorServerManx::updateServerState()
  276. {
  277. char* envValue = getenv("MANX_INSPECTOR_SERVER_PORT");
  278. if (!envValue)
  279. return;
  280. if (!m_clientMap.isEmpty() && serverState() == Closed) {
  281. const int inspectorServerPort = atoi(envValue);
  282. if (inspectorServerPort <= 0) {
  283. LOG_ERROR("Invalid Inspector Server port!");
  284. return;
  285. }
  286. if (!listen(Manx::NetworkProfile::ipAddress(), inspectorServerPort))
  287. LOG_ERROR("Couldn't start the Inspector Server!");
  288. } else if (m_clientMap.isEmpty() && serverState() == Listening) {
  289. close();
  290. ASSERT(serverState() == Closed);
  291. }
  292. }
  293. }