| /* |
| * 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 != null && 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; |
| } |
| } |