PageRenderTime 46ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/modules/debugger/src/java/flash/tools/debugger/concrete/PlayerSessionManager.java

https://github.com/adufilie/flex-sdk
Java | 1135 lines | 939 code | 83 blank | 113 comment | 119 complexity | c3389d3252ef947d74dc6f53ad32f15c MD5 | raw file
Possible License(s): Apache-2.0
  1. /*
  2. *
  3. * Licensed to the Apache Software Foundation (ASF) under one or more
  4. * contributor license agreements. See the NOTICE file distributed with
  5. * this work for additional information regarding copyright ownership.
  6. * The ASF licenses this file to You under the Apache License, Version 2.0
  7. * (the "License"); you may not use this file except in compliance with
  8. * the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. *
  18. */
  19. package flash.tools.debugger.concrete;
  20. import java.io.File;
  21. import java.io.FileNotFoundException;
  22. import java.io.IOException;
  23. import java.net.BindException;
  24. import java.net.InetAddress;
  25. import java.net.InetSocketAddress;
  26. import java.net.ServerSocket;
  27. import java.net.Socket;
  28. import java.net.SocketTimeoutException;
  29. import java.net.URI;
  30. import java.net.URISyntaxException;
  31. import java.util.ArrayList;
  32. import java.util.Arrays;
  33. import java.util.HashMap;
  34. import java.util.LinkedList;
  35. import java.util.List;
  36. import java.util.Map;
  37. import java.util.Set;
  38. import flash.localization.LocalizationManager;
  39. import flash.tools.debugger.AIRLaunchInfo;
  40. import flash.tools.debugger.DebuggerLocalizer;
  41. import flash.tools.debugger.DefaultDebuggerCallbacks;
  42. import flash.tools.debugger.IDebuggerCallbacks;
  43. import flash.tools.debugger.ILaunchNotification;
  44. import flash.tools.debugger.IProgress;
  45. import flash.tools.debugger.Player;
  46. import flash.tools.debugger.Session;
  47. import flash.tools.debugger.SessionManager2;
  48. import flash.tools.debugger.VersionException;
  49. import flash.util.URLHelper;
  50. public class PlayerSessionManager implements SessionManager2
  51. {
  52. private ServerSocket m_serverSocket;
  53. private HashMap<String, Object> m_prefs;
  54. private IDebuggerCallbacks m_debuggerCallbacks;
  55. private static LocalizationManager m_localizationManager;
  56. private Socket m_connectSocket;
  57. private boolean m_cancelConnect;
  58. static
  59. {
  60. // set up for localizing messages
  61. m_localizationManager = new LocalizationManager();
  62. m_localizationManager.addLocalizer( new DebuggerLocalizer("flash.tools.debugger.concrete.djapi.") ); //$NON-NLS-1$
  63. }
  64. public PlayerSessionManager()
  65. {
  66. m_debuggerCallbacks = new DefaultDebuggerCallbacks();
  67. m_serverSocket = null;
  68. m_connectSocket = null;
  69. m_cancelConnect = false;
  70. m_prefs = new HashMap<String, Object>();
  71. // manager
  72. setPreference(PREF_ACCEPT_TIMEOUT, 120000); // 2 minutes
  73. setPreference(PREF_URI_MODIFICATION, 1);
  74. setPreference(PREF_CONNECT_TIMEOUT, 120000); // 2 minutes
  75. setPreference(PREF_CONNECT_WAIT_INTERVAL, 250); // 0.25 seconds
  76. setPreference(PREF_CONNECT_RETRY_ATTEMPTS, -1); // Retry till timeout
  77. // session
  78. // response to requests
  79. setPreference(PREF_SOCKET_TIMEOUT, -1); // no timeout by default
  80. setPreference(PREF_RESPONSE_TIMEOUT, 750); // 0.75s
  81. setPreference(PREF_CONTEXT_RESPONSE_TIMEOUT, 1000); // 1s
  82. setPreference(PREF_GETVAR_RESPONSE_TIMEOUT, 1500); // 1.5s
  83. setPreference(PREF_SETVAR_RESPONSE_TIMEOUT, 5000); // 5s
  84. setPreference(PREF_SWFSWD_LOAD_TIMEOUT, 5000); // 5s
  85. // wait for a suspend to occur after a halt
  86. setPreference(PREF_SUSPEND_WAIT, 7000);
  87. // invoke getters by default
  88. setPreference(PREF_INVOKE_GETTERS, 1);
  89. // hierarchical variables view
  90. setPreference(PREF_HIERARCHICAL_VARIABLES, 0);
  91. }
  92. /**
  93. * Set preference
  94. * If an invalid preference is passed, it will be silently ignored.
  95. */
  96. public void setPreference(String pref, int value) { m_prefs.put(pref, new Integer(value)); }
  97. public void setPreference(String pref, String value){ m_prefs.put(pref, value); }
  98. public Set<String> keySet() { return m_prefs.keySet(); }
  99. public Object getPreferenceAsObject(String pref) { return m_prefs.get(pref); }
  100. /*
  101. * @see flash.tools.debugger.SessionManager#getPreference(java.lang.String)
  102. */
  103. public int getPreference(String pref)
  104. {
  105. int val = 0;
  106. Integer i = (Integer)m_prefs.get(pref);
  107. if (i == null)
  108. throw new NullPointerException();
  109. val = i.intValue();
  110. return val;
  111. }
  112. /*
  113. * @see flash.tools.debugger.SessionManager#startListening()
  114. */
  115. public void startListening() throws IOException
  116. {
  117. if (m_serverSocket == null)
  118. m_serverSocket = new ServerSocket(DProtocol.DEBUG_PORT);
  119. }
  120. /*
  121. * @see flash.tools.debugger.SessionManager#stopListening()
  122. */
  123. public void stopListening() throws IOException
  124. {
  125. if (m_serverSocket != null)
  126. {
  127. m_serverSocket.close();
  128. m_serverSocket = null;
  129. }
  130. }
  131. /*
  132. * @see flash.tools.debugger.SessionManager#isListening()
  133. */
  134. public boolean isListening()
  135. {
  136. return (m_serverSocket == null) ? false : true;
  137. }
  138. private class LaunchInfo
  139. {
  140. private String m_uri;
  141. public LaunchInfo(String uri)
  142. {
  143. m_uri = uri;
  144. }
  145. public boolean isAbout()
  146. {
  147. return m_uri.startsWith("about:"); //$NON-NLS-1$
  148. }
  149. public boolean isHttpOrAbout()
  150. {
  151. return m_uri.startsWith("http:") || m_uri.startsWith("https:") || isAbout(); //$NON-NLS-1$ //$NON-NLS-2$
  152. }
  153. public boolean isWebPage()
  154. {
  155. return isHttpOrAbout() || m_uri.endsWith(".htm") || m_uri.endsWith(".html"); //$NON-NLS-1$ //$NON-NLS-2$
  156. }
  157. public boolean isWebBrowserNativeLaunch()
  158. {
  159. return isWebPage() && (m_debuggerCallbacks.getHttpExe() != null);
  160. }
  161. public boolean isPlayerNativeLaunch()
  162. {
  163. return m_uri.length() > 0 && !isWebPage() && (m_debuggerCallbacks.getPlayerExe() != null);
  164. }
  165. public boolean isAIRLaunch()
  166. {
  167. return m_uri.startsWith("file:") && (m_uri.endsWith("-app.xml") || m_uri.endsWith("application.xml")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
  168. }
  169. }
  170. private enum OS {
  171. Mac,
  172. Windows,
  173. Unix
  174. }
  175. private OS getOS()
  176. {
  177. String osName = System.getProperty("os.name").toLowerCase(); //$NON-NLS-1$
  178. if (osName.startsWith("mac os x")) //$NON-NLS-1$
  179. {
  180. return OS.Mac;
  181. }
  182. else if (osName.startsWith("windows")) //$NON-NLS-1$
  183. {
  184. return OS.Windows;
  185. }
  186. else
  187. {
  188. return OS.Unix;
  189. }
  190. }
  191. /*
  192. * @see flash.tools.debugger.SessionManager#launch(java.lang.String, flash.tools.debugger.AIRLaunchInfo, boolean, flash.tools.debugger.IProgress)
  193. */
  194. public Session launch(String uri, AIRLaunchInfo airLaunchInfo, boolean forDebugging, IProgress waitReporter, ILaunchNotification launchNotification) throws IOException
  195. {
  196. String[] launchCommand;
  197. uri = uri.trim();
  198. if (airLaunchInfo == null)
  199. {
  200. LaunchInfo launchInfo = new LaunchInfo(uri);
  201. uri = tweakNativeLaunchUri(uri, forDebugging, launchInfo);
  202. launchCommand = getFlashLaunchArgs(uri, launchInfo);
  203. }
  204. else // else, AIR
  205. {
  206. launchCommand = getAIRLaunchArgs(uri, airLaunchInfo);
  207. }
  208. ProcessListener pl = null;
  209. PlayerSession session = null;
  210. // create the process and attach a thread to watch it during our accept phase
  211. Process proc = m_debuggerCallbacks.launchDebugTarget(launchCommand);
  212. pl = new ProcessListener(launchCommand, proc, launchNotification, forDebugging, airLaunchInfo != null); // BUG FB-9874: launchNotifier added
  213. // If launching an AIR app, and forDebugging=false (meaning we are just running it,
  214. // not debugging it), start a background thread that will call the launchNotification
  215. // when the launch is complete.
  216. if (!forDebugging && airLaunchInfo != null && launchNotification != null)
  217. pl.startLaunchNotifier();
  218. if (forDebugging)
  219. {
  220. /* now wait for a connection */
  221. session = (PlayerSession)accept(pl, waitReporter);
  222. session.setProcess(proc);
  223. session.setLaunchUrl(uri);
  224. session.setAIRLaunchInfo(airLaunchInfo);
  225. }
  226. return session;
  227. }
  228. public Process launchForRun(String uri, AIRLaunchInfo airLaunchInfo, IProgress waitReporter,
  229. ILaunchNotification launchNotification) throws IOException {
  230. String[] launchCommand;
  231. uri = uri.trim();
  232. if (airLaunchInfo == null)
  233. {
  234. LaunchInfo launchInfo = new LaunchInfo(uri);
  235. //forDebugging = false;
  236. uri = tweakNativeLaunchUri(uri, false, launchInfo);
  237. launchCommand = getFlashLaunchArgs(uri, launchInfo);
  238. }
  239. else // else, AIR
  240. {
  241. launchCommand = getAIRLaunchArgs(uri, airLaunchInfo);
  242. }
  243. ProcessListener pl = null;
  244. // create the process and attach a thread to watch it during our accept phase
  245. Process proc = m_debuggerCallbacks.launchDebugTarget(launchCommand);
  246. //forDebugging = false
  247. pl = new ProcessListener(launchCommand, proc, launchNotification, false, airLaunchInfo != null); // BUG FB-9874: launchNotifier added
  248. pl.setIsRunLaunch(true);
  249. // If launching an AIR app, and forDebugging=false (meaning we are just running it,
  250. // not debugging it), start a background thread that will call the launchNotification
  251. // when the launch is complete.
  252. if (airLaunchInfo != null && launchNotification != null)
  253. pl.startLaunchNotifier();
  254. return proc;
  255. }
  256. /**
  257. * Tweaks the launch URI if necessary, e.g. may append "?debug=true"
  258. */
  259. private String tweakNativeLaunchUri(String uri, boolean forDebugging,
  260. LaunchInfo launchInfo) throws IOException, FileNotFoundException
  261. {
  262. // first let's see if it's an HTTP URL or not
  263. if (launchInfo.isHttpOrAbout())
  264. {
  265. boolean modify = (getPreference(PREF_URI_MODIFICATION) != 0);
  266. if (modify && forDebugging && !launchInfo.isAbout())
  267. {
  268. // escape spaces if we have any
  269. uri = URLHelper.escapeSpace(uri);
  270. // be sure that ?debug=true is included in query string
  271. URLHelper urlHelper = new URLHelper(uri);
  272. Map<String, String> params = urlHelper.getParameterMap();
  273. params.put("debug", "true"); //$NON-NLS-1$ //$NON-NLS-2$
  274. urlHelper.setParameterMap(params);
  275. uri = urlHelper.getURL();
  276. }
  277. }
  278. else
  279. {
  280. // ok, its not an http: type request therefore we should be able to see
  281. // it on the file system, right? If not then it's probably not valid
  282. File f = null;
  283. if (uri.startsWith("file:")) //$NON-NLS-1$
  284. {
  285. try
  286. {
  287. f = new File(new URI(uri));
  288. }
  289. catch (URISyntaxException e)
  290. {
  291. IOException ioe = new IOException(e.getMessage());
  292. ioe.initCause(e);
  293. throw ioe;
  294. }
  295. }
  296. else
  297. {
  298. f = new File(uri);
  299. }
  300. if (f != null && f.exists()) {
  301. // Do not use getCanonicalPath() -- see FB-24595
  302. uri = f.getAbsolutePath();
  303. } else {
  304. throw new FileNotFoundException(uri);
  305. }
  306. }
  307. return uri;
  308. }
  309. /**
  310. * Gets the arguments needed for launching a swf that needs to run
  311. * in a web browser or the standalone player.
  312. */
  313. private String[] getFlashLaunchArgs(String uri, LaunchInfo launchInfo) throws FileNotFoundException
  314. {
  315. String[] launchCommand;
  316. OS os = getOS();
  317. /**
  318. * Various ways to launch this stupid thing. If we have the exe
  319. * values for the player, then we can launch it directly, monitor
  320. * it and kill it when we die; otherwise we launch it through
  321. * a command shell (cmd.exe, open, or bash) and our Process object
  322. * dies right away since it spawned another process to run the
  323. * Player within.
  324. */
  325. if (os == OS.Mac)
  326. {
  327. if (launchInfo.isWebBrowserNativeLaunch())
  328. {
  329. File httpExe = m_debuggerCallbacks.getHttpExe();
  330. String[] customParams = m_debuggerCallbacks.getBrowserParameters(uri);
  331. if (customParams == null) {
  332. launchCommand = new String[] { "/usr/bin/open", "-a", httpExe.toString(), uri }; //$NON-NLS-1$ //$NON-NLS-2$
  333. }
  334. else {
  335. final int prependLen = 4;
  336. launchCommand = new String[customParams.length + prependLen ];
  337. launchCommand[0] = "/usr/bin/open";
  338. launchCommand[1] = "-a";
  339. launchCommand[2] = httpExe.toString();
  340. launchCommand[3] = "--args";
  341. for ( int i = 0; i < customParams.length; i++) {
  342. launchCommand[i + prependLen] = customParams[i];
  343. }
  344. }
  345. }
  346. else if (launchInfo.isPlayerNativeLaunch())
  347. {
  348. File playerExe = m_debuggerCallbacks.getPlayerExe();
  349. launchCommand = new String[] { "/usr/bin/open", "-a", playerExe.toString(), uri }; //$NON-NLS-1$ //$NON-NLS-2$
  350. }
  351. else
  352. {
  353. launchCommand = new String[] { "/usr/bin/open", uri }; //$NON-NLS-1$
  354. }
  355. }
  356. else // Windows or *nix
  357. {
  358. if (launchInfo.isWebBrowserNativeLaunch())
  359. {
  360. File httpExe = m_debuggerCallbacks.getHttpExe();
  361. String[] customParams = m_debuggerCallbacks.getBrowserParameters(uri);
  362. if (customParams == null) {
  363. if (os == OS.Windows)
  364. launchCommand = getWindowsBrowserLaunchArgs(httpExe, uri);
  365. else
  366. launchCommand = new String[] { httpExe.toString(), uri };
  367. }
  368. else {
  369. final int prependLen = 1;
  370. launchCommand = new String[customParams.length + prependLen];
  371. launchCommand[0] = httpExe.toString();
  372. for ( int i = 0; i < customParams.length; i++) {
  373. launchCommand[i + prependLen] = customParams[i];
  374. }
  375. }
  376. }
  377. else if (launchInfo.isPlayerNativeLaunch())
  378. {
  379. File playerExe = m_debuggerCallbacks.getPlayerExe();
  380. launchCommand = new String[] { playerExe.toString(), uri };
  381. }
  382. else
  383. {
  384. if (os == OS.Windows)
  385. {
  386. // We must quote all ampersands in the URL; if we don't, then
  387. // cmd.exe will interpret the ampersand as a command separator.
  388. uri = uri.replaceAll("&", "\"&\""); //$NON-NLS-1$ //$NON-NLS-2$
  389. launchCommand = new String[] { "cmd", "/c", "start", uri }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
  390. }
  391. else
  392. {
  393. String exeName;
  394. if (launchInfo.isWebPage())
  395. exeName = m_debuggerCallbacks.getHttpExeName();
  396. else
  397. exeName = m_debuggerCallbacks.getPlayerExeName();
  398. throw new FileNotFoundException(exeName);
  399. }
  400. }
  401. }
  402. return launchCommand;
  403. }
  404. /**
  405. * Gets the arguments needed for launching a web browser on Windows.
  406. */
  407. private String[] getWindowsBrowserLaunchArgs(File httpExe, String uri)
  408. {
  409. if (httpExe.getName().equalsIgnoreCase("chrome.exe")) //$NON-NLS-1$
  410. {
  411. // FB-16779: Adding "--disable-hang-monitor" to prevent Chrome
  412. // from warning us that a plug-inappears to be hung; it does
  413. // that when the user hits a breakpoint.
  414. return new String[] { httpExe.toString(), "--disable-hang-monitor", uri }; //$NON-NLS-1$
  415. }
  416. else if (httpExe.getName().equalsIgnoreCase("iexplore.exe")) //$NON-NLS-1$
  417. {
  418. boolean isIE8 = false;
  419. try
  420. {
  421. int[] ieVersion = m_debuggerCallbacks.getAppVersion(httpExe);
  422. if (ieVersion != null)
  423. isIE8 = (ieVersion[0] >= 8);
  424. } catch (IOException e) {
  425. // ignore
  426. }
  427. if (isIE8)
  428. {
  429. // FB-22107: Tell IE to keep using the new process we are
  430. // launching, rather than merging the new process into the
  431. // old one. This allows us to terminate the new IE
  432. // debugging session.
  433. return new String[] { httpExe.toString(), "-noframemerging", uri }; //$NON-NLS-1$
  434. }
  435. }
  436. return new String[] { httpExe.toString(), uri };
  437. }
  438. /**
  439. * Gets the arguments needed for launching a swf that needs to run
  440. * in AIR.
  441. */
  442. private String[] getAIRLaunchArgs(String uri, AIRLaunchInfo airLaunchInfo)
  443. throws IOException
  444. {
  445. List<String> cmdList = new LinkedList<String>();
  446. cmdList.add(airLaunchInfo.airDebugLauncher.getPath());
  447. if (airLaunchInfo.airRuntimeDir != null)
  448. {
  449. cmdList.add("-runtime"); //$NON-NLS-1$
  450. cmdList.add(airLaunchInfo.airRuntimeDir.getPath());
  451. }
  452. if (airLaunchInfo.airSecurityPolicy != null)
  453. {
  454. cmdList.add("-security-policy"); //$NON-NLS-1$
  455. cmdList.add(airLaunchInfo.airSecurityPolicy.getPath());
  456. }
  457. if (airLaunchInfo.airPublisherID != null && airLaunchInfo.airPublisherID.length() > 0)
  458. {
  459. cmdList.add("-pubid"); //$NON-NLS-1$
  460. cmdList.add(airLaunchInfo.airPublisherID);
  461. }
  462. if (airLaunchInfo.profile != null && airLaunchInfo.profile.length() > 0)
  463. {
  464. cmdList.add("-profile"); //$NON-NLS-1$
  465. cmdList.add(airLaunchInfo.profile);
  466. }
  467. if (airLaunchInfo.screenSize != null && airLaunchInfo.screenSize.length() > 0)
  468. {
  469. cmdList.add("-screensize"); //$NON-NLS-1$
  470. cmdList.add(airLaunchInfo.screenSize);
  471. }
  472. if (airLaunchInfo.dpi > 0)
  473. {
  474. //TODO: this is apparently only going to be used in AIR 2.5.
  475. //Figure out permanent solution when AIR 3.0 comes along.
  476. cmdList.add("-XscreenDPI"); //$NON-NLS-1$
  477. cmdList.add(String.valueOf(airLaunchInfo.dpi));
  478. }
  479. if (airLaunchInfo.versionPlatform != null && airLaunchInfo.versionPlatform.length() > 0)
  480. {
  481. cmdList.add("-XversionPlatform"); //$NON-NLS-1$
  482. cmdList.add(airLaunchInfo.versionPlatform);
  483. }
  484. if (airLaunchInfo.extDir != null && airLaunchInfo.extDir.length() > 0) {
  485. cmdList.add("-extdir"); //$NON-NLS-1$
  486. cmdList.add(airLaunchInfo.extDir);
  487. }
  488. if (airLaunchInfo.deviceExtDir != null && airLaunchInfo.deviceExtDir.length() > 0) {
  489. cmdList.add("-XdeviceExtDir"); //$NON-NLS-1$
  490. cmdList.add(airLaunchInfo.deviceExtDir);
  491. }
  492. // If it's a "file:" URL, then pass the actual filename; otherwise, use the URL
  493. // ok, its not an http: type request therefore we should be able to see
  494. // it on the file system, right? If not then it's probably not valid
  495. File f = null;
  496. if (uri.startsWith("file:")) //$NON-NLS-1$
  497. {
  498. try
  499. {
  500. f = new File(new URI(uri));
  501. cmdList.add(f.getPath());
  502. }
  503. catch (URISyntaxException e)
  504. {
  505. IOException ioe = new IOException(e.getMessage());
  506. ioe.initCause(e);
  507. throw ioe;
  508. }
  509. }
  510. else
  511. {
  512. cmdList.add(uri);
  513. }
  514. if (airLaunchInfo.applicationContentRootDir != null)
  515. {
  516. cmdList.add(airLaunchInfo.applicationContentRootDir.getAbsolutePath());
  517. }
  518. List<String> args = null;
  519. if (airLaunchInfo.applicationArgumentsArray != null)
  520. {
  521. args = Arrays.asList(airLaunchInfo.applicationArgumentsArray);
  522. }
  523. else if (airLaunchInfo.applicationArguments != null)
  524. {
  525. args = splitArgs(airLaunchInfo.applicationArguments);
  526. }
  527. if (args != null && args.size() > 0)
  528. {
  529. cmdList.add("--"); //$NON-NLS-1$
  530. cmdList.addAll(args);
  531. }
  532. return cmdList.toArray(new String[cmdList.size()]);
  533. }
  534. /**
  535. * This is annoying: We must duplicate the operating system's behavior
  536. * with regard to splitting arguments.
  537. *
  538. * @param arguments A single string of arguments that are intended to
  539. * be passed to an AIR application. The tricky part is that some
  540. * of the arguments may be quoted, and if they are, then the quoting
  541. * will be in a way that is specific to the current platform. For
  542. * example, on Windows, strings are quoted with the double-quote character
  543. * ("); on Mac and Unix, strings can be quoted with either double-quote
  544. * or single-quote.
  545. * @return The equivalent
  546. */
  547. private List<String> splitArgs(String arguments)
  548. {
  549. List<String> retval = new ArrayList<String>();
  550. arguments = arguments.trim();
  551. // Windows quotes only with double-quote; Mac and Unix also allow single-quote.
  552. boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("win"); //$NON-NLS-1$ //$NON-NLS-2$
  553. boolean isMacOrUnix = !isWindows;
  554. int i=0;
  555. while (i<arguments.length()) {
  556. char ch = arguments.charAt(i);
  557. if (ch == ' ' || ch == '\t') {
  558. // keep looping
  559. i++;
  560. } else if (ch == '"' || (isMacOrUnix && ch == '\'')) {
  561. char quote = ch;
  562. int nextQuote = arguments.indexOf(quote, i+1);
  563. if (nextQuote == -1) {
  564. retval.add(arguments.substring(i+1));
  565. return retval;
  566. } else {
  567. retval.add(arguments.substring(i+1, nextQuote));
  568. i = nextQuote+1;
  569. }
  570. } else {
  571. int startPos = i;
  572. while (i<arguments.length()) {
  573. ch = arguments.charAt(i);
  574. if (ch == ' ' || ch == '\t') {
  575. break;
  576. }
  577. i++;
  578. }
  579. retval.add(arguments.substring(startPos, i));
  580. }
  581. }
  582. return retval;
  583. }
  584. /*
  585. * @see flash.tools.debugger.SessionManager#playerForUri(java.lang.String, flash.tools.debugger.AIRLaunchInfo)
  586. */
  587. public Player playerForUri(String url, AIRLaunchInfo airLaunchInfo)
  588. {
  589. LaunchInfo launchInfo = new LaunchInfo(url);
  590. if (airLaunchInfo != null)
  591. {
  592. return new AIRPlayer(airLaunchInfo.airDebugLauncher);
  593. }
  594. else if (launchInfo.isAIRLaunch())
  595. {
  596. return new AIRPlayer(null);
  597. }
  598. else
  599. {
  600. // Find the Netscape plugin
  601. if (getOS() == OS.Mac)
  602. {
  603. if (!launchInfo.isWebBrowserNativeLaunch())
  604. {
  605. File playerExe = m_debuggerCallbacks.getPlayerExe();
  606. return new StandalonePlayer(playerExe);
  607. }
  608. File flashPlugin = new File("/Library/Internet Plug-Ins/Flash Player.plugin"); //$NON-NLS-1$
  609. return new NetscapePluginPlayer(m_debuggerCallbacks.getHttpExe(), flashPlugin);
  610. }
  611. else
  612. {
  613. if (launchInfo.isWebBrowserNativeLaunch())
  614. {
  615. File httpExe = m_debuggerCallbacks.getHttpExe();
  616. if (httpExe.getName().equalsIgnoreCase("iexplore.exe")) //$NON-NLS-1$
  617. {
  618. // IE on Windows: Find the ActiveX control
  619. String activeXFile = null;
  620. try
  621. {
  622. activeXFile = m_debuggerCallbacks.queryWindowsRegistry("HKEY_CLASSES_ROOT\\CLSID\\{D27CDB6E-AE6D-11cf-96B8-444553540000}\\InprocServer32", null); //$NON-NLS-1$
  623. }
  624. catch (IOException e)
  625. {
  626. // ignore
  627. }
  628. if (activeXFile == null)
  629. return null; // we couldn't find the player
  630. File file = new File(activeXFile);
  631. return new ActiveXPlayer(httpExe, file);
  632. }
  633. else
  634. {
  635. // Find the Netscape plugin
  636. File browserDir = httpExe.getParentFile();
  637. // Opera puts plugins under "program\plugins" rather than under "plugins"
  638. if (httpExe.getName().equalsIgnoreCase("opera.exe")) //$NON-NLS-1$
  639. browserDir = new File(browserDir, "program"); //$NON-NLS-1$
  640. File pluginsDir = new File(browserDir, "plugins"); //$NON-NLS-1$
  641. File flashPlugin = new File(pluginsDir, "NPSWF32.dll"); // WARNING, Windows-specific //$NON-NLS-1$
  642. // Bug FB-4691: The player is now installed via a registry key, not
  643. // in the "plugins" directory.
  644. //
  645. // Although Mozilla does not document this, the actual behavior of
  646. // the browser seems to be that it looks first in the "plugins" directory,
  647. // and then, if the file is not found there, it looks in the registry.
  648. // So, we mimic that behavior.
  649. if (!flashPlugin.exists())
  650. {
  651. File pathFromRegistry = getWindowsMozillaPlayerPathFromRegistry();
  652. if (pathFromRegistry != null)
  653. flashPlugin = pathFromRegistry;
  654. }
  655. return new NetscapePluginPlayer(httpExe, flashPlugin);
  656. }
  657. }
  658. else if (launchInfo.isPlayerNativeLaunch())
  659. {
  660. File playerExe = m_debuggerCallbacks.getPlayerExe();
  661. return new StandalonePlayer(playerExe);
  662. }
  663. }
  664. }
  665. return null;
  666. }
  667. /**
  668. * Look in the Windows registry for the Mozilla version of the Flash player.
  669. */
  670. private File getWindowsMozillaPlayerPathFromRegistry()
  671. {
  672. final String KEY = "\\SOFTWARE\\MozillaPlugins\\@adobe.com/FlashPlayer"; //$NON-NLS-1$
  673. final String PATH = "Path"; //$NON-NLS-1$
  674. // According to
  675. //
  676. // http://developer.mozilla.org/en/docs/Plugins:_The_first_install_problem
  677. //
  678. // the MozillaPlugins key can be written to either HKEY_CURRENT_USER or
  679. // HKEY_LOCAL_MACHINE. Unfortunately, as of this writing, Firefox
  680. // (version 2.0.0.2) doesn't actually work that way -- it only checks
  681. // HKEY_LOCAL_MACHINE, but not HKEY_CURRENT_USER.
  682. //
  683. // But in hopeful anticipation of a fix for that, we are going to check both
  684. // locations. On current builds, that won't do any harm, because the
  685. // current Flash Player installer only writes to HKEY_LOCAL_MACHINE. In the
  686. // future, if Mozilla gets fixed and then the Flash player installer gets
  687. // updated, then our code will already work correctly.
  688. //
  689. // Another quirk: In my opinion, it would be better for Mozilla to look first
  690. // in HKEY_CURRENT_USER, and then in HKEY_LOCAL_MACHINE. However, according to
  691. //
  692. // http://developer.mozilla.org/en/docs/Installing_plugins_to_Gecko_embedding_browsers_on_Windows
  693. //
  694. // they don't agree with that -- they want HKEY_LOCAL_MACHINE first.
  695. String[] roots = { "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER" }; //$NON-NLS-1$ //$NON-NLS-2$
  696. for (int i=0; i<roots.length; ++i)
  697. {
  698. try
  699. {
  700. String path = m_debuggerCallbacks.queryWindowsRegistry(roots[i] + KEY, PATH);
  701. if (path != null)
  702. return new File(path);
  703. }
  704. catch (IOException e)
  705. {
  706. // ignore
  707. }
  708. }
  709. return null;
  710. }
  711. /*
  712. * @see flash.tools.debugger.SessionManager#supportsLaunch()
  713. */
  714. public boolean supportsLaunch()
  715. {
  716. return true;
  717. }
  718. /*
  719. * @see flash.tools.debugger.SessionManager#accept(flash.tools.debugger.IProgress)
  720. */
  721. public Session accept(IProgress waitReporter) throws IOException
  722. {
  723. return accept(null, waitReporter);
  724. }
  725. /**
  726. * A private variation on <code>accept()</code> that also has an argument
  727. * indicating that the process we are waiting for has terminated.
  728. *
  729. * @param pl
  730. * Optional process listener. If non-null, this is used to detect
  731. * if a process that was launched has terminated unexpectedly.
  732. * For example, if launch() launches adl, but adl exits, then we
  733. * don't want to continue to wait for a socket connection.
  734. */
  735. private Session accept(ProcessListener pl, IProgress waitReporter) throws IOException
  736. {
  737. // get timeout
  738. int timeout = getPreference(PREF_ACCEPT_TIMEOUT);
  739. int totalTimeout = timeout;
  740. int iterateOn = 100;
  741. PlayerSession session = null;
  742. try
  743. {
  744. m_serverSocket.setSoTimeout(iterateOn);
  745. // Wait 100ms per iteration. We have to do that so that we can report how long
  746. // we have been waiting.
  747. Socket s = null;
  748. while (s == null && !airAppTerminated(pl))
  749. {
  750. try
  751. {
  752. s = m_serverSocket.accept();
  753. }
  754. catch(IOException ste)
  755. {
  756. timeout -= iterateOn;
  757. if (timeout < 0 || m_serverSocket == null || m_serverSocket.isClosed())
  758. throw ste; // we reached the timeout, or someome called stopListening()
  759. }
  760. // Tell the progress monitor we've waited a little while longer,
  761. // so that the Eclipse progress bar can keep chugging along
  762. if (waitReporter != null)
  763. waitReporter.setProgress(totalTimeout - timeout, totalTimeout);
  764. }
  765. if (s == null && airAppTerminated(pl))
  766. {
  767. throw pl.createLaunchFailureException();
  768. }
  769. /* create a new session around this socket */
  770. session = PlayerSession.createFromSocketWithOptions(s, m_debuggerCallbacks, this);
  771. // transfer preferences
  772. session.setPreferences(m_prefs);
  773. }
  774. catch(NullPointerException npe)
  775. {
  776. throw new BindException(getLocalizationManager().getLocalizedTextString("serverSocketNotListening")); //$NON-NLS-1$
  777. }
  778. return session;
  779. }
  780. /**
  781. * Returns true if the passed-in process listener is for an AIR application
  782. * that has terminated. This is used by accept() in order to detect that it
  783. * should give up listening on the socket.
  784. *
  785. * The reason we can't do this for Flash player-based apps is that unlike
  786. * AIR apps, the process that we launched sometimes acts as just sort of a
  787. * "launcher" process that terminates quickly, and the actual Flash player
  788. * is in some other process. For example, on Mac, we often invoke the "open"
  789. * program to open a web browser; and on Windows, if you launch firefox.exe
  790. * but it detects that there is already a running instance of firefox.exe,
  791. * the new instance will just pass a message to the old instance, and then
  792. * the new instance will terminate.
  793. *
  794. * @param pl
  795. * a process listener, or <code>null</code>
  796. * @return true if pl refers to an AIR app that has terminated.
  797. */
  798. private boolean airAppTerminated(ProcessListener pl)
  799. {
  800. if (pl != null)
  801. {
  802. if (pl.isAIRApp())
  803. {
  804. if (pl.isProcessDead())
  805. {
  806. return true;
  807. }
  808. }
  809. }
  810. return false;
  811. }
  812. /*
  813. * @see flash.tools.debugger.SessionManager#getDebuggerCallbacks()
  814. */
  815. public IDebuggerCallbacks getDebuggerCallbacks()
  816. {
  817. return m_debuggerCallbacks;
  818. }
  819. /*
  820. * @see flash.tools.debugger.SessionManager#setDebuggerCallbacks(flash.tools.debugger.IDebuggerCallbacks)
  821. */
  822. public void setDebuggerCallbacks(IDebuggerCallbacks debuggerCallbacks)
  823. {
  824. m_debuggerCallbacks = debuggerCallbacks;
  825. }
  826. /**
  827. * A private variation on <code>connect()</code> that also has an argument
  828. * indicating that the process we are waiting for has terminated.
  829. *
  830. * @param pl
  831. * Optional process listener. If non-null, this is used to detect
  832. * if a process that was launched has terminated unexpectedly.
  833. * For example, if launch() launches adl, but adl exits, then we
  834. * don't want to continue to wait for a socket connection.
  835. */
  836. public Session connect(int port, IProgress waitReporter) throws IOException
  837. {
  838. final int waitTime = getPreference(PREF_CONNECT_WAIT_INTERVAL);
  839. final int maxRetryAttempts = getPreference(PREF_CONNECT_RETRY_ATTEMPTS);
  840. final int totalTimeout = getPreference(PREF_CONNECT_TIMEOUT);
  841. final long timeForConnectStart = System.currentTimeMillis();
  842. long elapsedTime = 0;
  843. int retryAttempts = -1;
  844. PlayerSession session = null;
  845. Socket s = null;
  846. m_cancelConnect = false;
  847. // Try to see if a connect happens till totalTimeout
  848. // If the connection was refused in between, retry
  849. // again after waitTime until totalTimeout is elapsed.
  850. // Retry mechanism is disabled if PREF_CONNECT_RETRY_ATTEMPTS
  851. // is 0.
  852. while (s == null)
  853. {
  854. try
  855. {
  856. InetSocketAddress localAddress = new InetSocketAddress(InetAddress.getByName(null), port);
  857. s = new Socket();
  858. //save the socket for canceling connect
  859. m_connectSocket = s;
  860. //connect to loopback address at the specified port
  861. s.connect(localAddress, totalTimeout);
  862. }
  863. catch(IOException ste)
  864. {
  865. if (ste instanceof SocketTimeoutException) {
  866. //if we timed out, abort connect
  867. abortConnect(ste);
  868. }
  869. safeCloseSocket(s);
  870. s = null;
  871. retryAttempts++;
  872. //if we should not retry, checkConnectTimeout
  873. //throws an exception
  874. elapsedTime = checkConnectTimeout(waitTime, maxRetryAttempts,
  875. totalTimeout, retryAttempts, timeForConnectStart, ste);
  876. }
  877. // Tell the progress monitor we've waited a little while longer,
  878. // so that the Eclipse progress bar can keep chugging along
  879. if (waitReporter != null)
  880. waitReporter.setProgress((int)elapsedTime, totalTimeout);
  881. if (s != null) {
  882. /** If we connected, make sure that we get some response
  883. * back after sending the handshake. This is required because
  884. * of the way port forwarding works. A connect will be successful
  885. * if port forwarding is set up, but we won't get any response
  886. * unless the application is actually listening.
  887. */
  888. /* create a new session around this socket */
  889. session = PlayerSession.createFromSocketWithOptions(s, m_debuggerCallbacks, this);
  890. // transfer preferences
  891. session.setPreferences(m_prefs);
  892. try {
  893. session.bind();
  894. }
  895. catch (VersionException ex) {
  896. session.unbind();
  897. safeCloseSocket(s);
  898. s = null;
  899. retryAttempts++;
  900. /** The VersionException here is considered as an IOException
  901. * because we do not know if there was even a valid application
  902. * listening on the port. Once the port is forwarded, connect
  903. * succeeds and we get a VersionException even if player is not
  904. * listening on that port.
  905. */
  906. elapsedTime = checkConnectTimeout(waitTime, maxRetryAttempts,
  907. totalTimeout, retryAttempts, timeForConnectStart,
  908. new IOException(ex.getLocalizedMessage()));
  909. }
  910. }
  911. }
  912. m_connectSocket = null;
  913. return session;
  914. }
  915. /**
  916. * @param waitTime
  917. * @param maxRetryAttempts
  918. * @param totalTimeout
  919. * @param retryAttempts
  920. * @param startTime
  921. * @param caughtException
  922. * @return
  923. * @throws IOException
  924. */
  925. private long checkConnectTimeout(final int waitTime,
  926. final int maxRetryAttempts, final int totalTimeout,
  927. int retryAttempts, final long startTime, IOException caughtException)
  928. throws IOException {
  929. long elapsedTime;
  930. long endTime = System.currentTimeMillis();
  931. elapsedTime = endTime - startTime;
  932. // check if we should retry
  933. boolean retryFinished = (maxRetryAttempts != -1 && retryAttempts >= maxRetryAttempts);
  934. // check if we timed out or somebody called stopConnecting()
  935. if (retryFinished ||
  936. elapsedTime > totalTimeout ||
  937. m_cancelConnect ) {
  938. abortConnect(caughtException);
  939. }
  940. //wait a bit before retrying
  941. try {
  942. Thread.sleep(waitTime);
  943. } catch (InterruptedException e) {
  944. abortConnect(caughtException);
  945. }
  946. //check cancel before resuming
  947. if (m_cancelConnect ) {
  948. abortConnect(caughtException);
  949. }
  950. return elapsedTime;
  951. }
  952. /**
  953. * @param ste
  954. * @throws IOException
  955. */
  956. private void abortConnect(IOException ste) throws IOException {
  957. m_connectSocket = null;
  958. m_cancelConnect = false;
  959. throw ste;
  960. }
  961. /**
  962. * @param s
  963. */
  964. private void safeCloseSocket(Socket s) {
  965. //clean up the socket
  966. if (s != null && !s.isClosed()) {
  967. try {
  968. s.close();
  969. }
  970. catch (IOException closeException) {
  971. //ignore
  972. }
  973. }
  974. }
  975. /*
  976. * @see flash.tools.debugger.SessionManager#stopConnecting()
  977. */
  978. public void stopConnecting() throws IOException
  979. {
  980. if (!m_cancelConnect) {
  981. m_cancelConnect = true;
  982. if (m_connectSocket != null)
  983. {
  984. m_connectSocket.close();
  985. m_connectSocket = null;
  986. }
  987. }
  988. }
  989. /*
  990. * @see flash.tools.debugger.SessionManager#isConnecting()
  991. */
  992. public boolean isConnecting()
  993. {
  994. return (m_connectSocket == null) ? false : true;
  995. }
  996. /**
  997. * Returns the localization manager. Use this for all localized strings.
  998. */
  999. public static LocalizationManager getLocalizationManager()
  1000. {
  1001. return m_localizationManager;
  1002. }
  1003. }