/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package flash.tools.debugger.concrete; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.BindException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketTimeoutException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import flash.localization.LocalizationManager; import flash.tools.debugger.AIRLaunchInfo; import flash.tools.debugger.DebuggerLocalizer; import flash.tools.debugger.DefaultDebuggerCallbacks; import flash.tools.debugger.IDebuggerCallbacks; import flash.tools.debugger.ILaunchNotification; import flash.tools.debugger.ILauncher; import flash.tools.debugger.IProgress; import flash.tools.debugger.Player; import flash.tools.debugger.Session; import flash.tools.debugger.SessionManager2; import flash.tools.debugger.VersionException; import flash.util.URLHelper; public class PlayerSessionManager implements SessionManager2 { private ServerSocket m_serverSocket; private HashMap<String, Object> m_prefs; private IDebuggerCallbacks m_debuggerCallbacks; private static LocalizationManager m_localizationManager; private Socket m_connectSocket; private boolean m_cancelConnect; static { // set up for localizing messages m_localizationManager = new LocalizationManager(); m_localizationManager.addLocalizer( new DebuggerLocalizer("flash.tools.debugger.concrete.djapi.") ); //$NON-NLS-1$ } public PlayerSessionManager() { m_debuggerCallbacks = new DefaultDebuggerCallbacks(); m_serverSocket = null; m_connectSocket = null; m_cancelConnect = false; m_prefs = new HashMap<String, Object>(); // manager setPreference(PREF_ACCEPT_TIMEOUT, 120000); // 2 minutes setPreference(PREF_URI_MODIFICATION, 1); setPreference(PREF_CONNECT_TIMEOUT, 120000); // 2 minutes setPreference(PREF_CONNECT_WAIT_INTERVAL, 250); // 0.25 seconds setPreference(PREF_CONNECT_RETRY_ATTEMPTS, -1); // Retry till timeout // session // response to requests setPreference(PREF_SOCKET_TIMEOUT, -1); // no timeout by default setPreference(PREF_RESPONSE_TIMEOUT, 750); // 0.75s setPreference(PREF_CONTEXT_RESPONSE_TIMEOUT, 1000); // 1s setPreference(PREF_GETVAR_RESPONSE_TIMEOUT, 1500); // 1.5s setPreference(PREF_SETVAR_RESPONSE_TIMEOUT, 5000); // 5s setPreference(PREF_SWFSWD_LOAD_TIMEOUT, 5000); // 5s // wait for a suspend to occur after a halt setPreference(PREF_SUSPEND_WAIT, 7000); // invoke getters by default setPreference(PREF_INVOKE_GETTERS, 1); // hierarchical variables view setPreference(PREF_HIERARCHICAL_VARIABLES, 0); } /** * Set preference * If an invalid preference is passed, it will be silently ignored. */ public void setPreference(String pref, int value) { m_prefs.put(pref, new Integer(value)); } public void setPreference(String pref, String value){ m_prefs.put(pref, value); } public Set<String> keySet() { return m_prefs.keySet(); } public Object getPreferenceAsObject(String pref) { return m_prefs.get(pref); } /* * @see flash.tools.debugger.SessionManager#getPreference(java.lang.String) */ public int getPreference(String pref) { int val = 0; Integer i = (Integer)m_prefs.get(pref); if (i == null) throw new NullPointerException(); val = i.intValue(); return val; } /* * @see flash.tools.debugger.SessionManager#startListening() */ public void startListening() throws IOException { if (m_serverSocket == null) m_serverSocket = new ServerSocket(DProtocol.DEBUG_PORT); } /* * @see flash.tools.debugger.SessionManager#stopListening() */ public void stopListening() throws IOException { if (m_serverSocket != null) { m_serverSocket.close(); m_serverSocket = null; } } /* * @see flash.tools.debugger.SessionManager#isListening() */ public boolean isListening() { return (m_serverSocket == null) ? false : true; } private class LaunchInfo { private String m_uri; public LaunchInfo(String uri) { m_uri = uri; } public boolean isAbout() { return m_uri.startsWith("about:"); //$NON-NLS-1$ } public boolean isHttpOrAbout() { return m_uri.startsWith("http:") || m_uri.startsWith("https:") || isAbout(); //$NON-NLS-1$ //$NON-NLS-2$ } public boolean isWebPage() { return isHttpOrAbout() || m_uri.endsWith(".htm") || m_uri.endsWith(".html"); //$NON-NLS-1$ //$NON-NLS-2$ } public boolean isWebBrowserNativeLaunch() { return isWebPage() && (m_debuggerCallbacks.getHttpExe() != null); } public boolean isPlayerNativeLaunch() { return m_uri.length() > 0 && !isWebPage() && (m_debuggerCallbacks.getPlayerExe() != null); } public boolean isAIRLaunch() { return m_uri.startsWith("file:") && (m_uri.endsWith("-app.xml") || m_uri.endsWith("application.xml")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } private enum OS { Mac, Windows, Unix } private OS getOS() { String osName = System.getProperty("os.name").toLowerCase(); //$NON-NLS-1$ if (osName.startsWith("mac os x")) //$NON-NLS-1$ { return OS.Mac; } else if (osName.startsWith("windows")) //$NON-NLS-1$ { return OS.Windows; } else { return OS.Unix; } } /* * @see flash.tools.debugger.SessionManager#launch(java.lang.String, flash.tools.debugger.AIRLaunchInfo, boolean, flash.tools.debugger.IProgress) */ public Session launch(String uri, AIRLaunchInfo airLaunchInfo, boolean forDebugging, IProgress waitReporter, ILaunchNotification launchNotification) throws IOException { String[] launchCommand = getLaunchCommand(uri, airLaunchInfo,forDebugging); // create the process and attach a thread to watch it during our accept phase Process proc = m_debuggerCallbacks.launchDebugTarget(launchCommand); ProcessListener processListener = startProcessListener(airLaunchInfo,forDebugging, launchNotification, launchCommand, proc,false); PlayerSession session = null; if (forDebugging) { session = waitForConnection(uri, airLaunchInfo, waitReporter, proc, processListener); } return session; } private PlayerSession waitForConnection(String uri, AIRLaunchInfo airLaunchInfo, IProgress waitReporter, Process proc, ProcessListener pl) throws IOException { /* now wait for a connection */ PlayerSession session = (PlayerSession)accept(pl, waitReporter); session.setProcess(proc); session.setLaunchUrl(uri); session.setAIRLaunchInfo(airLaunchInfo); return session; } /** * Tweaks the launch URI if necessary, e.g. may append "?debug=true" */ private String tweakNativeLaunchUri(String uri, boolean forDebugging, LaunchInfo launchInfo) throws IOException, FileNotFoundException { // first let's see if it's an HTTP URL or not if (launchInfo.isHttpOrAbout()) { boolean modify = (getPreference(PREF_URI_MODIFICATION) != 0); if (modify && forDebugging && !launchInfo.isAbout()) { // escape spaces if we have any uri = URLHelper.escapeSpace(uri); // be sure that ?debug=true is included in query string URLHelper urlHelper = new URLHelper(uri); Map<String, String> params = urlHelper.getParameterMap(); params.put("debug", "true"); //$NON-NLS-1$ //$NON-NLS-2$ urlHelper.setParameterMap(params); uri = urlHelper.getURL(); } } else { // ok, its not an http: type request therefore we should be able to see // it on the file system, right? If not then it's probably not valid File f = null; if (uri.startsWith("file:")) //$NON-NLS-1$ { try { f = new File(new URI(uri)); } catch (URISyntaxException e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } } else { f = new File(uri); } if (f.exists()) { // Do not use getCanonicalPath() -- see FB-24595 uri = f.getAbsolutePath(); } else { throw new FileNotFoundException(uri); } } return uri; } /** * Gets the arguments needed for launching a swf that needs to run * in a web browser or the standalone player. */ private String[] getFlashLaunchArgs(String uri, LaunchInfo launchInfo) throws FileNotFoundException { String[] launchCommand; OS os = getOS(); /** * Various ways to launch this stupid thing. If we have the exe * values for the player, then we can launch it directly, monitor * it and kill it when we die; otherwise we launch it through * a command shell (cmd.exe, open, or bash) and our Process object * dies right away since it spawned another process to run the * Player within. */ if (os == OS.Mac) { if (launchInfo.isWebBrowserNativeLaunch()) { File httpExe = m_debuggerCallbacks.getHttpExe(); String[] customParams = m_debuggerCallbacks.getBrowserParameters(uri); if (customParams == null) { launchCommand = new String[] { "/usr/bin/open", "-a", httpExe.toString(), uri }; //$NON-NLS-1$ //$NON-NLS-2$ } else { final int prependLen = 4; launchCommand = new String[customParams.length + prependLen ]; launchCommand[0] = "/usr/bin/open"; //$NON-NLS-1$ launchCommand[1] = "-a"; //$NON-NLS-1$ launchCommand[2] = httpExe.toString(); launchCommand[3] = "--args"; //$NON-NLS-1$ for ( int i = 0; i < customParams.length; i++) { launchCommand[i + prependLen] = customParams[i]; } } } else if (launchInfo.isPlayerNativeLaunch()) { File playerExe = m_debuggerCallbacks.getPlayerExe(); launchCommand = new String[] { "/usr/bin/open", "-a", playerExe.toString(), uri }; //$NON-NLS-1$ //$NON-NLS-2$ } else { launchCommand = new String[] { "/usr/bin/open", uri }; //$NON-NLS-1$ } } else // Windows or *nix { if (launchInfo.isWebBrowserNativeLaunch()) { File httpExe = m_debuggerCallbacks.getHttpExe(); String[] customParams = m_debuggerCallbacks.getBrowserParameters(uri); if (customParams == null) { if (os == OS.Windows) launchCommand = getWindowsBrowserLaunchArgs(httpExe, uri); else launchCommand = new String[] { httpExe.toString(), uri }; } else { final int prependLen = 1; launchCommand = new String[customParams.length + prependLen]; launchCommand[0] = httpExe.toString(); for ( int i = 0; i < customParams.length; i++) { launchCommand[i + prependLen] = customParams[i]; } } } else if (launchInfo.isPlayerNativeLaunch()) { File playerExe = m_debuggerCallbacks.getPlayerExe(); launchCommand = new String[] { playerExe.toString(), uri }; } else { if (os == OS.Windows) { // We must quote all ampersands in the URL; if we don't, then // cmd.exe will interpret the ampersand as a command separator. uri = uri.replaceAll("&", "\"&\""); //$NON-NLS-1$ //$NON-NLS-2$ launchCommand = new String[] { "cmd", "/c", "start", uri }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } else { String exeName; if (launchInfo.isWebPage()) exeName = m_debuggerCallbacks.getHttpExeName(); else exeName = m_debuggerCallbacks.getPlayerExeName(); throw new FileNotFoundException(exeName); } } } return launchCommand; } /** * Gets the arguments needed for launching a web browser on Windows. */ private String[] getWindowsBrowserLaunchArgs(File httpExe, String uri) { if (httpExe.getName().equalsIgnoreCase("chrome.exe")) //$NON-NLS-1$ { // FB-16779: Adding "--disable-hang-monitor" to prevent Chrome // from warning us that a plug-inappears to be hung; it does // that when the user hits a breakpoint. return new String[] { httpExe.toString(), "--disable-hang-monitor", uri }; //$NON-NLS-1$ } else if (httpExe.getName().equalsIgnoreCase("iexplore.exe")) //$NON-NLS-1$ { boolean isIE8 = false; try { int[] ieVersion = m_debuggerCallbacks.getAppVersion(httpExe); if (ieVersion != null) isIE8 = (ieVersion[0] >= 8); } catch (IOException e) { // ignore } if (isIE8) { // FB-22107: Tell IE to keep using the new process we are // launching, rather than merging the new process into the // old one. This allows us to terminate the new IE // debugging session. return new String[] { httpExe.toString(), "-noframemerging", uri }; //$NON-NLS-1$ } } return new String[] { httpExe.toString(), uri }; } /** * Gets the arguments needed for launching a swf that needs to run * in AIR. */ private String[] getAIRLaunchArgs(String uri, AIRLaunchInfo airLaunchInfo) throws IOException { List<String> cmdList = new LinkedList<String>(); cmdList.add(airLaunchInfo.airDebugLauncher.getPath()); if (airLaunchInfo.airRuntimeDir != null) { cmdList.add("-runtime"); //$NON-NLS-1$ cmdList.add(airLaunchInfo.airRuntimeDir.getPath()); } if (airLaunchInfo.airSecurityPolicy != null) { cmdList.add("-security-policy"); //$NON-NLS-1$ cmdList.add(airLaunchInfo.airSecurityPolicy.getPath()); } if (airLaunchInfo.airPublisherID != null && airLaunchInfo.airPublisherID.length() > 0) { cmdList.add("-pubid"); //$NON-NLS-1$ cmdList.add(airLaunchInfo.airPublisherID); } if (airLaunchInfo.profile != null && airLaunchInfo.profile.length() > 0) { cmdList.add("-profile"); //$NON-NLS-1$ cmdList.add(airLaunchInfo.profile); } if (airLaunchInfo.screenSize != null && airLaunchInfo.screenSize.length() > 0) { cmdList.add("-screensize"); //$NON-NLS-1$ cmdList.add(airLaunchInfo.screenSize); } if (airLaunchInfo.dpi > 0) { //TODO: this is apparently only going to be used in AIR 2.5. //Figure out permanent solution when AIR 3.0 comes along. cmdList.add("-XscreenDPI"); //$NON-NLS-1$ cmdList.add(String.valueOf(airLaunchInfo.dpi)); } if (airLaunchInfo.versionPlatform != null && airLaunchInfo.versionPlatform.length() > 0) { cmdList.add("-XversionPlatform"); //$NON-NLS-1$ cmdList.add(airLaunchInfo.versionPlatform); } if (airLaunchInfo.extDir != null && airLaunchInfo.extDir.length() > 0) { cmdList.add("-extdir"); //$NON-NLS-1$ cmdList.add(airLaunchInfo.extDir); } if (airLaunchInfo.deviceExtDir != null && airLaunchInfo.deviceExtDir.length() > 0) { cmdList.add("-XdeviceExtDir"); //$NON-NLS-1$ cmdList.add(airLaunchInfo.deviceExtDir); } // If it's a "file:" URL, then pass the actual filename; otherwise, use the URL // ok, its not an http: type request therefore we should be able to see // it on the file system, right? If not then it's probably not valid File f = null; if (uri.startsWith("file:")) //$NON-NLS-1$ { try { f = new File(new URI(uri)); cmdList.add(f.getPath()); } catch (URISyntaxException e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } } else { cmdList.add(uri); } if (airLaunchInfo.applicationContentRootDir != null) { cmdList.add(airLaunchInfo.applicationContentRootDir.getAbsolutePath()); } List<String> args = null; if (airLaunchInfo.applicationArgumentsArray != null) { args = Arrays.asList(airLaunchInfo.applicationArgumentsArray); } else if (airLaunchInfo.applicationArguments != null) { args = splitArgs(airLaunchInfo.applicationArguments); } if (args != null && args.size() > 0) { cmdList.add("--"); //$NON-NLS-1$ cmdList.addAll(args); } return cmdList.toArray(new String[cmdList.size()]); } /** * This is annoying: We must duplicate the operating system's behavior * with regard to splitting arguments. * * @param arguments A single string of arguments that are intended to * be passed to an AIR application. The tricky part is that some * of the arguments may be quoted, and if they are, then the quoting * will be in a way that is specific to the current platform. For * example, on Windows, strings are quoted with the double-quote character * ("); on Mac and Unix, strings can be quoted with either double-quote * or single-quote. * @return The equivalent */ private List<String> splitArgs(String arguments) { List<String> retval = new ArrayList<String>(); arguments = arguments.trim(); // Windows quotes only with double-quote; Mac and Unix also allow single-quote. boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("win"); //$NON-NLS-1$ //$NON-NLS-2$ boolean isMacOrUnix = !isWindows; int i=0; while (i<arguments.length()) { char ch = arguments.charAt(i); if (ch == ' ' || ch == '\t') { // keep looping i++; } else if (ch == '"' || (isMacOrUnix && ch == '\'')) { char quote = ch; int nextQuote = arguments.indexOf(quote, i+1); if (nextQuote == -1) { retval.add(arguments.substring(i+1)); return retval; } else { retval.add(arguments.substring(i+1, nextQuote)); i = nextQuote+1; } } else { int startPos = i; while (i<arguments.length()) { ch = arguments.charAt(i); if (ch == ' ' || ch == '\t') { break; } i++; } retval.add(arguments.substring(startPos, i)); } } return retval; } /* * @see flash.tools.debugger.SessionManager#playerForUri(java.lang.String, flash.tools.debugger.AIRLaunchInfo) */ public Player playerForUri(String url, AIRLaunchInfo airLaunchInfo) { LaunchInfo launchInfo = new LaunchInfo(url); if (airLaunchInfo != null) { return new AIRPlayer(airLaunchInfo.airDebugLauncher); } else if (launchInfo.isAIRLaunch()) { return new AIRPlayer(null); } else { // Find the Netscape plugin if (getOS() == OS.Mac) { if (!launchInfo.isWebBrowserNativeLaunch()) { File playerExe = m_debuggerCallbacks.getPlayerExe(); return new StandalonePlayer(playerExe); } File flashPlugin = new File("/Library/Internet Plug-Ins/Flash Player.plugin"); //$NON-NLS-1$ return new NetscapePluginPlayer(m_debuggerCallbacks.getHttpExe(), flashPlugin); } else { if (launchInfo.isWebBrowserNativeLaunch()) { File httpExe = m_debuggerCallbacks.getHttpExe(); if (httpExe.getName().equalsIgnoreCase("iexplore.exe")) //$NON-NLS-1$ { // IE on Windows: Find the ActiveX control String activeXFile = null; final String registryKey = "HKEY_CLASSES_ROOT\\CLSID\\{D27CDB6E-AE6D-11cf-96B8-444553540000}\\InprocServer32"; //$NON-NLS-1$ //check if this is a 64-bit machine boolean is64Bit = (System.getenv("ProgramFiles(x86)") != null); //$NON-NLS-1$ try { if (is64Bit) { //now we have to ensure that we only query the registry //that is 32 or 64-bit depending upon whether we are //launching 32 or 64-bit IE. if (httpExe.getCanonicalPath().contains("(x86)")) { //$NON-NLS-1$ activeXFile = m_debuggerCallbacks.queryWindowsRegistry(registryKey, null, 1); } else { activeXFile = m_debuggerCallbacks.queryWindowsRegistry(registryKey, null, 2); } } else { activeXFile = m_debuggerCallbacks.queryWindowsRegistry(registryKey, null, 1); } } catch (IOException e) { // ignore } if (activeXFile == null) return null; // we couldn't find the player File file = new File(activeXFile); return new ActiveXPlayer(httpExe, file); } else { // Find the Netscape plugin File browserDir = httpExe.getParentFile(); // Opera puts plugins under "program\plugins" rather than under "plugins" if (httpExe.getName().equalsIgnoreCase("opera.exe")) //$NON-NLS-1$ browserDir = new File(browserDir, "program"); //$NON-NLS-1$ File pluginsDir = new File(browserDir, "plugins"); //$NON-NLS-1$ File flashPlugin = new File(pluginsDir, "NPSWF32.dll"); // WARNING, Windows-specific //$NON-NLS-1$ // Bug FB-4691: The player is now installed via a registry key, not // in the "plugins" directory. // // Although Mozilla does not document this, the actual behavior of // the browser seems to be that it looks first in the "plugins" directory, // and then, if the file is not found there, it looks in the registry. // So, we mimic that behavior. if (!flashPlugin.exists()) { File pathFromRegistry = getWindowsMozillaPlayerPathFromRegistry(); if (pathFromRegistry != null) flashPlugin = pathFromRegistry; } return new NetscapePluginPlayer(httpExe, flashPlugin); } } else if (launchInfo.isPlayerNativeLaunch()) { File playerExe = m_debuggerCallbacks.getPlayerExe(); return new StandalonePlayer(playerExe); } } } return null; } /** * Look in the Windows registry for the Mozilla version of the Flash player. */ private File getWindowsMozillaPlayerPathFromRegistry() { final String KEY = "\\SOFTWARE\\MozillaPlugins\\@adobe.com/FlashPlayer"; //$NON-NLS-1$ final String PATH = "Path"; //$NON-NLS-1$ // According to // // http://developer.mozilla.org/en/docs/Plugins:_The_first_install_problem // // the MozillaPlugins key can be written to either HKEY_CURRENT_USER or // HKEY_LOCAL_MACHINE. Unfortunately, as of this writing, Firefox // (version 2.0.0.2) doesn't actually work that way -- it only checks // HKEY_LOCAL_MACHINE, but not HKEY_CURRENT_USER. // // But in hopeful anticipation of a fix for that, we are going to check both // locations. On current builds, that won't do any harm, because the // current Flash Player installer only writes to HKEY_LOCAL_MACHINE. In the // future, if Mozilla gets fixed and then the Flash player installer gets // updated, then our code will already work correctly. // // Another quirk: In my opinion, it would be better for Mozilla to look first // in HKEY_CURRENT_USER, and then in HKEY_LOCAL_MACHINE. However, according to // // http://developer.mozilla.org/en/docs/Installing_plugins_to_Gecko_embedding_browsers_on_Windows // // they don't agree with that -- they want HKEY_LOCAL_MACHINE first. String[] roots = { "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER" }; //$NON-NLS-1$ //$NON-NLS-2$ for (int i=0; i<roots.length; ++i) { try { String path = m_debuggerCallbacks.queryWindowsRegistry(roots[i] + KEY, PATH, 1); if (path != null) return new File(path); } catch (IOException e) { // ignore } } return null; } /* * @see flash.tools.debugger.SessionManager#supportsLaunch() */ public boolean supportsLaunch() { return true; } /* * @see flash.tools.debugger.SessionManager#accept(flash.tools.debugger.IProgress) */ public Session accept(IProgress waitReporter) throws IOException { return accept(null, waitReporter); } /** * A private variation on <code>accept()</code> that also has an argument * indicating that the process we are waiting for has terminated. * * @param pl * Optional process listener. If non-null, this is used to detect * if a process that was launched has terminated unexpectedly. * For example, if launch() launches adl, but adl exits, then we * don't want to continue to wait for a socket connection. */ private Session accept(ProcessListener pl, IProgress waitReporter) throws IOException { // get timeout int timeout = getPreference(PREF_ACCEPT_TIMEOUT); int totalTimeout = timeout; int iterateOn = 100; PlayerSession session = null; try { m_serverSocket.setSoTimeout(iterateOn); // Wait 100ms per iteration. We have to do that so that we can report how long // we have been waiting. Socket s = null; while (s == null && !airAppTerminated(pl)) { try { s = m_serverSocket.accept(); } catch(IOException ste) { timeout -= iterateOn; if (timeout < 0 || m_serverSocket == null || m_serverSocket.isClosed()) throw ste; // we reached the timeout, or someome called stopListening() } // Tell the progress monitor we've waited a little while longer, // so that the Eclipse progress bar can keep chugging along if (waitReporter != null) waitReporter.setProgress(totalTimeout - timeout, totalTimeout); } if (s == null && airAppTerminated(pl)) { throw pl.createLaunchFailureException(); } /* create a new session around this socket */ session = PlayerSession.createFromSocketWithOptions(s, m_debuggerCallbacks, this); // transfer preferences session.setPreferences(m_prefs); } catch(NullPointerException npe) { throw new BindException(getLocalizationManager().getLocalizedTextString("serverSocketNotListening")); //$NON-NLS-1$ } return session; } /** * Returns true if the passed-in process listener is for an AIR application * that has terminated. This is used by accept() in order to detect that it * should give up listening on the socket. * * The reason we can't do this for Flash player-based apps is that unlike * AIR apps, the process that we launched sometimes acts as just sort of a * "launcher" process that terminates quickly, and the actual Flash player * is in some other process. For example, on Mac, we often invoke the "open" * program to open a web browser; and on Windows, if you launch firefox.exe * but it detects that there is already a running instance of firefox.exe, * the new instance will just pass a message to the old instance, and then * the new instance will terminate. * * @param pl * a process listener, or <code>null</code> * @return true if pl refers to an AIR app that has terminated. */ private boolean airAppTerminated(ProcessListener pl) { if (pl != null) { if (pl.isAIRApp()) { if (pl.isProcessDead()) { return true; } } } return false; } /* * @see flash.tools.debugger.SessionManager#getDebuggerCallbacks() */ public IDebuggerCallbacks getDebuggerCallbacks() { return m_debuggerCallbacks; } /* * @see flash.tools.debugger.SessionManager#setDebuggerCallbacks(flash.tools.debugger.IDebuggerCallbacks) */ public void setDebuggerCallbacks(IDebuggerCallbacks debuggerCallbacks) { m_debuggerCallbacks = debuggerCallbacks; } /** * A private variation on <code>connect()</code> that also has an argument * indicating that the process we are waiting for has terminated. * * @param pl * Optional process listener. If non-null, this is used to detect * if a process that was launched has terminated unexpectedly. * For example, if launch() launches adl, but adl exits, then we * don't want to continue to wait for a socket connection. */ public Session connect(int port, IProgress waitReporter) throws IOException { final int waitTime = getPreference(PREF_CONNECT_WAIT_INTERVAL); final int maxRetryAttempts = getPreference(PREF_CONNECT_RETRY_ATTEMPTS); final int totalTimeout = getPreference(PREF_CONNECT_TIMEOUT); final long timeForConnectStart = System.currentTimeMillis(); long elapsedTime = 0; int retryAttempts = -1; PlayerSession session = null; Socket s = null; m_cancelConnect = false; // Try to see if a connect happens till totalTimeout // If the connection was refused in between, retry // again after waitTime until totalTimeout is elapsed. // Retry mechanism is disabled if PREF_CONNECT_RETRY_ATTEMPTS // is 0. while (s == null) { try { InetSocketAddress localAddress = new InetSocketAddress(InetAddress.getByName(null), port); s = new Socket(); //save the socket for canceling connect m_connectSocket = s; //connect to loopback address at the specified port s.connect(localAddress, totalTimeout); } catch(IOException ste) { if (ste instanceof SocketTimeoutException) { //if we timed out, abort connect abortConnect(ste); } safeCloseSocket(s); s = null; retryAttempts++; //if we should not retry, checkConnectTimeout //throws an exception elapsedTime = checkConnectTimeout(waitTime, maxRetryAttempts, totalTimeout, retryAttempts, timeForConnectStart, ste); } // Tell the progress monitor we've waited a little while longer, // so that the Eclipse progress bar can keep chugging along if (waitReporter != null) waitReporter.setProgress((int)elapsedTime, totalTimeout); if (s != null) { /** If we connected, make sure that we get some response * back after sending the handshake. This is required because * of the way port forwarding works. A connect will be successful * if port forwarding is set up, but we won't get any response * unless the application is actually listening. */ /* create a new session around this socket */ session = PlayerSession.createFromSocketWithOptions(s, m_debuggerCallbacks, this); // transfer preferences session.setPreferences(m_prefs); try { session.bind(); } catch (VersionException ex) { session.unbind(); safeCloseSocket(s); s = null; retryAttempts++; /** The VersionException here is considered as an IOException * because we do not know if there was even a valid application * listening on the port. Once the port is forwarded, connect * succeeds and we get a VersionException even if player is not * listening on that port. */ elapsedTime = checkConnectTimeout(waitTime, maxRetryAttempts, totalTimeout, retryAttempts, timeForConnectStart, new IOException(ex.getLocalizedMessage())); } } } m_connectSocket = null; return session; } /** * @param waitTime * @param maxRetryAttempts * @param totalTimeout * @param retryAttempts * @param startTime * @param caughtException * @return * @throws IOException */ private long checkConnectTimeout(final int waitTime, final int maxRetryAttempts, final int totalTimeout, int retryAttempts, final long startTime, IOException caughtException) throws IOException { long elapsedTime; long endTime = System.currentTimeMillis(); elapsedTime = endTime - startTime; // check if we should retry boolean retryFinished = (maxRetryAttempts != -1 && retryAttempts >= maxRetryAttempts); // check if we timed out or somebody called stopConnecting() if (retryFinished || elapsedTime > totalTimeout || m_cancelConnect ) { abortConnect(caughtException); } //wait a bit before retrying try { Thread.sleep(waitTime); } catch (InterruptedException e) { abortConnect(caughtException); } //check cancel before resuming if (m_cancelConnect ) { abortConnect(caughtException); } return elapsedTime; } /** * @param ste * @throws IOException */ private void abortConnect(IOException ste) throws IOException { m_connectSocket = null; m_cancelConnect = false; throw ste; } /** * @param s */ private void safeCloseSocket(Socket s) { //clean up the socket if (s != null && !s.isClosed()) { try { s.close(); } catch (IOException closeException) { //ignore } } } /* * @see flash.tools.debugger.SessionManager#stopConnecting() */ public void stopConnecting() throws IOException { if (!m_cancelConnect) { m_cancelConnect = true; if (m_connectSocket != null) { m_connectSocket.close(); m_connectSocket = null; } } } /* * @see flash.tools.debugger.SessionManager#isConnecting() */ public boolean isConnecting() { return (m_connectSocket == null) ? false : true; } /** * Returns the localization manager. Use this for all localized strings. */ public static LocalizationManager getLocalizationManager() { return m_localizationManager; } @Override public Process launchForRun(String uri, AIRLaunchInfo airLaunchInfo, IProgress waitReporter, ILaunchNotification launchNotification) throws IOException { String[] launchCommand = getLaunchCommand(uri, airLaunchInfo, false); // create the process and attach a thread to watch it during our accept phase Process proc = m_debuggerCallbacks.launchDebugTarget(launchCommand); //forDebugging = false // If launching an AIR app, and forDebugging=false (meaning we are just running it, // not debugging it), start a background thread that will call the launchNotification // when the launch is complete. startProcessListener(airLaunchInfo, false,launchNotification, launchCommand, proc,true); return proc; } private ProcessListener startProcessListener(AIRLaunchInfo airLaunchInfo, boolean forDebugging, ILaunchNotification launchNotification, String[] launchCommand, Process proc, boolean isRunLaunch) { ProcessListener processListener = new ProcessListener(launchCommand, proc, launchNotification, forDebugging, airLaunchInfo != null); // BUG FB-9874: launchNotifier added processListener.setIsRunLaunch(isRunLaunch); // If launching an AIR app, and forDebugging=false (meaning we are just running it, // not debugging it), start a background thread that will call the launchNotification // when the launch is complete. if (!forDebugging && airLaunchInfo != null && launchNotification != null) processListener.startLaunchNotifier(); return processListener; } private String[] getLaunchCommand(String uri, AIRLaunchInfo airLaunchInfo, boolean forDebugging) throws IOException, FileNotFoundException { String[] launchCommand; uri = uri.trim(); if (airLaunchInfo == null) { LaunchInfo launchInfo = new LaunchInfo(uri); uri = tweakNativeLaunchUri(uri, forDebugging, launchInfo); launchCommand = getFlashLaunchArgs(uri, launchInfo); } else // else, AIR { launchCommand = getAIRLaunchArgs(uri, airLaunchInfo); } return launchCommand; } @Override public Process launchForRun(String uri, AIRLaunchInfo airLaunchInfo, IProgress waitReporter, ILaunchNotification launchNotification, ILauncher launcher) throws IOException { String[] launchCommand = getLaunchCommandForLauncher(uri, airLaunchInfo,false); // create the process and attach a thread to watch it during our accept phase Process proc = m_debuggerCallbacks.launchDebugTarget(launchCommand,launcher); //forDebugging = false // If launching an AIR app, and forDebugging=false (meaning we are just running it, // not debugging it), start a background thread that will call the launchNotification // when the launch is complete. startProcessListener(airLaunchInfo, false,launchNotification, launchCommand, proc, true);; return proc; } private String[] getLaunchCommandForLauncher(String uri, AIRLaunchInfo airLaunchInfo,boolean forDebugging) throws IOException, FileNotFoundException { String[] launchCommand; if (airLaunchInfo == null) { LaunchInfo launchInfo = new LaunchInfo(uri); uri = tweakNativeLaunchUri(uri, forDebugging, launchInfo); launchCommand = new String[]{uri}; } else // else, AIR { launchCommand = getAIRLaunchArgs(uri, airLaunchInfo); } return launchCommand; } @Override public Session launch(String uri, AIRLaunchInfo airLaunchInfo, boolean forDebugging, IProgress waitReporter, ILaunchNotification launchNotification, ILauncher launcher) throws IOException { String[] launchCommand = getLaunchCommandForLauncher(uri, airLaunchInfo,forDebugging); // create the process and attach a thread to watch it during our accept phase Process proc = m_debuggerCallbacks.launchDebugTarget(launchCommand, launcher); ProcessListener processListener = startProcessListener(airLaunchInfo,forDebugging, launchNotification, launchCommand, proc,false); PlayerSession session = null; if (forDebugging) { session = waitForConnection(uri, airLaunchInfo, waitReporter, proc, processListener); session.setLauncher(launcher); } return session; } }