package com.google.jstestdriver.idea.debug; import com.google.jstestdriver.idea.execution.settings.JstdRunSettings; import com.google.jstestdriver.idea.server.JstdBrowserInfo; import com.google.jstestdriver.idea.server.JstdServer; import com.google.jstestdriver.idea.server.JstdServerSettings; import com.intellij.execution.process.ProcessHandler; import com.intellij.ide.browsers.BrowserFamily; import com.intellij.ide.browsers.WebBrowser; import com.intellij.javascript.debugger.JavaScriptDebugEngine; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; import com.intellij.util.SmartList; import com.intellij.util.TimeoutUtil; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.methods.PostMethod; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.Collection; import java.util.List; public class JstdDebugBrowserInfo { private static final Logger LOG = Logger.getInstance(JstdDebugBrowserInfo.class); private final JavaScriptDebugEngine myDebugEngine; private final WebBrowser myWebBrowser; private final JstdServerSettings myServerSettings; private final JstdBrowserInfo myBrowserInfo; private JstdDebugBrowserInfo(@NotNull Pair<JavaScriptDebugEngine, WebBrowser> pair, @NotNull JstdServerSettings serverSettings, @NotNull JstdBrowserInfo browserInfo) { myDebugEngine = pair.first; myWebBrowser = pair.second; myServerSettings = serverSettings; myBrowserInfo = browserInfo; } @NotNull public JavaScriptDebugEngine getDebugEngine() { return myDebugEngine; } @NotNull public WebBrowser getBrowser() { return myWebBrowser; } @NotNull public JstdServerSettings getServerSettings() { return myServerSettings; } @NotNull public String getPath() { return "/slave/id/" + myBrowserInfo.getId() + "/page/CONSOLE/mode/quirks/timeout/" + myServerSettings.getBrowserTimeoutMillis() + "/upload_size/50/rt/CLIENT"; } /** * Posts 'heartbeat' event to the server. Usually, a captured browser posts this event, but Chrome is a special case: * When execution suspended on a breakpoint, it can't perform any background activity. * Emulating 'heartbeat' event keeps alive the debug session. * * @param testRunnerProcessHandler */ public void fixIfChrome(@NotNull final ProcessHandler testRunnerProcessHandler) { if (!BrowserFamily.CHROME.equals(myWebBrowser.getFamily())) { return; } ApplicationManager.getApplication().executeOnPooledThread(() -> { HttpClient client = new HttpClient(); while (!testRunnerProcessHandler.isProcessTerminated()) { String url = "http://127.0.0.1:" + myServerSettings.getPort() + "/heartbeat"; PostMethod method = new PostMethod(url); method.addParameter("id", myBrowserInfo.getId()); try { int responseCode = client.executeMethod(method); if (responseCode != 200) { LOG.warn(url + ": response code: " + responseCode); } } catch (IOException e) { LOG.warn("Cannot request " + url, e); } TimeoutUtil.sleep(5000); } client.getHttpConnectionManager().closeIdleConnections(0); }); } @Nullable public static JstdDebugBrowserInfo build(@NotNull JstdServer server, @NotNull JstdRunSettings runSettings) { Collection<JstdBrowserInfo> capturedBrowsers = server.getCapturedBrowsers(); List<JstdDebugBrowserInfo> debugBrowserInfos = new SmartList<>(); for (JstdBrowserInfo browserInfo : capturedBrowsers) { Pair<JavaScriptDebugEngine, WebBrowser> engine = JavaScriptDebugEngine.Companion.findByBrowserIdOrName(browserInfo.getName()); if (engine != null) { debugBrowserInfos.add(new JstdDebugBrowserInfo(engine, server.getSettings(), browserInfo)); } } if (debugBrowserInfos.size() == 1) { return debugBrowserInfos.get(0); } if (debugBrowserInfos.size() > 1) { WebBrowser preferredBrowser = runSettings.getPreferredDebugBrowser(); for (JstdDebugBrowserInfo info : debugBrowserInfos) { if (preferredBrowser.equals(info.getBrowser())) { return info; } } } return null; } }