PageRenderTime 69ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://github.com/code-orchestra/flex-sdk-livecoding
Java | 1126 lines | 930 code | 83 blank | 113 comment | 117 complexity | 22d3ec66df8e514cea62335bfc2a4a33 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<>();
  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, 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;
  106. Integer i = (Integer)m_prefs.get(pref);
  107. if (i == null)
  108. throw new NullPointerException();
  109. val = i;
  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;
  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;
  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;
  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;
  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. System.arraycopy(customParams, 0, launchCommand, 4, customParams.length);
  342. }
  343. }
  344. else if (launchInfo.isPlayerNativeLaunch())
  345. {
  346. File playerExe = m_debuggerCallbacks.getPlayerExe();
  347. launchCommand = new String[] { "/usr/bin/open", "-a", playerExe.toString(), uri }; //$NON-NLS-1$ //$NON-NLS-2$
  348. }
  349. else
  350. {
  351. launchCommand = new String[] { "/usr/bin/open", uri }; //$NON-NLS-1$
  352. }
  353. }
  354. else // Windows or *nix
  355. {
  356. if (launchInfo.isWebBrowserNativeLaunch())
  357. {
  358. File httpExe = m_debuggerCallbacks.getHttpExe();
  359. String[] customParams = m_debuggerCallbacks.getBrowserParameters(uri);
  360. if (customParams == null) {
  361. if (os == OS.Windows)
  362. launchCommand = getWindowsBrowserLaunchArgs(httpExe, uri);
  363. else
  364. launchCommand = new String[] { httpExe.toString(), uri };
  365. }
  366. else {
  367. final int prependLen = 1;
  368. launchCommand = new String[customParams.length + prependLen];
  369. launchCommand[0] = httpExe.toString();
  370. System.arraycopy(customParams, 0, launchCommand, 1, customParams.length);
  371. }
  372. }
  373. else if (launchInfo.isPlayerNativeLaunch())
  374. {
  375. File playerExe = m_debuggerCallbacks.getPlayerExe();
  376. launchCommand = new String[] { playerExe.toString(), uri };
  377. }
  378. else
  379. {
  380. if (os == OS.Windows)
  381. {
  382. // We must quote all ampersands in the URL; if we don't, then
  383. // cmd.exe will interpret the ampersand as a command separator.
  384. uri = uri.replaceAll("&", "\"&\""); //$NON-NLS-1$ //$NON-NLS-2$
  385. launchCommand = new String[] { "cmd", "/c", "start", uri }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
  386. }
  387. else
  388. {
  389. String exeName;
  390. if (launchInfo.isWebPage())
  391. exeName = m_debuggerCallbacks.getHttpExeName();
  392. else
  393. exeName = m_debuggerCallbacks.getPlayerExeName();
  394. throw new FileNotFoundException(exeName);
  395. }
  396. }
  397. }
  398. return launchCommand;
  399. }
  400. /**
  401. * Gets the arguments needed for launching a web browser on Windows.
  402. */
  403. private String[] getWindowsBrowserLaunchArgs(File httpExe, String uri)
  404. {
  405. if (httpExe.getName().equalsIgnoreCase("chrome.exe")) //$NON-NLS-1$
  406. {
  407. // FB-16779: Adding "--disable-hang-monitor" to prevent Chrome
  408. // from warning us that a plug-inappears to be hung; it does
  409. // that when the user hits a breakpoint.
  410. return new String[] { httpExe.toString(), "--disable-hang-monitor", uri }; //$NON-NLS-1$
  411. }
  412. else if (httpExe.getName().equalsIgnoreCase("iexplore.exe")) //$NON-NLS-1$
  413. {
  414. boolean isIE8 = false;
  415. try
  416. {
  417. int[] ieVersion = m_debuggerCallbacks.getAppVersion(httpExe);
  418. if (ieVersion != null)
  419. isIE8 = (ieVersion[0] >= 8);
  420. } catch (IOException e) {
  421. // ignore
  422. }
  423. if (isIE8)
  424. {
  425. // FB-22107: Tell IE to keep using the new process we are
  426. // launching, rather than merging the new process into the
  427. // old one. This allows us to terminate the new IE
  428. // debugging session.
  429. return new String[] { httpExe.toString(), "-noframemerging", uri }; //$NON-NLS-1$
  430. }
  431. }
  432. return new String[] { httpExe.toString(), uri };
  433. }
  434. /**
  435. * Gets the arguments needed for launching a swf that needs to run
  436. * in AIR.
  437. */
  438. private String[] getAIRLaunchArgs(String uri, AIRLaunchInfo airLaunchInfo)
  439. throws IOException
  440. {
  441. List<String> cmdList = new LinkedList<>();
  442. cmdList.add(airLaunchInfo.airDebugLauncher.getPath());
  443. if (airLaunchInfo.airRuntimeDir != null)
  444. {
  445. cmdList.add("-runtime"); //$NON-NLS-1$
  446. cmdList.add(airLaunchInfo.airRuntimeDir.getPath());
  447. }
  448. if (airLaunchInfo.airSecurityPolicy != null)
  449. {
  450. cmdList.add("-security-policy"); //$NON-NLS-1$
  451. cmdList.add(airLaunchInfo.airSecurityPolicy.getPath());
  452. }
  453. if (airLaunchInfo.airPublisherID != null && airLaunchInfo.airPublisherID.length() > 0)
  454. {
  455. cmdList.add("-pubid"); //$NON-NLS-1$
  456. cmdList.add(airLaunchInfo.airPublisherID);
  457. }
  458. if (airLaunchInfo.profile != null && airLaunchInfo.profile.length() > 0)
  459. {
  460. cmdList.add("-profile"); //$NON-NLS-1$
  461. cmdList.add(airLaunchInfo.profile);
  462. }
  463. if (airLaunchInfo.screenSize != null && airLaunchInfo.screenSize.length() > 0)
  464. {
  465. cmdList.add("-screensize"); //$NON-NLS-1$
  466. cmdList.add(airLaunchInfo.screenSize);
  467. }
  468. if (airLaunchInfo.dpi > 0)
  469. {
  470. //TODO: this is apparently only going to be used in AIR 2.5.
  471. //Figure out permanent solution when AIR 3.0 comes along.
  472. cmdList.add("-XscreenDPI"); //$NON-NLS-1$
  473. cmdList.add(String.valueOf(airLaunchInfo.dpi));
  474. }
  475. if (airLaunchInfo.versionPlatform != null && airLaunchInfo.versionPlatform.length() > 0)
  476. {
  477. cmdList.add("-XversionPlatform"); //$NON-NLS-1$
  478. cmdList.add(airLaunchInfo.versionPlatform);
  479. }
  480. if (airLaunchInfo.extDir != null && airLaunchInfo.extDir.length() > 0) {
  481. cmdList.add("-extdir"); //$NON-NLS-1$
  482. cmdList.add(airLaunchInfo.extDir);
  483. }
  484. if (airLaunchInfo.deviceExtDir != null && airLaunchInfo.deviceExtDir.length() > 0) {
  485. cmdList.add("-XdeviceExtDir"); //$NON-NLS-1$
  486. cmdList.add(airLaunchInfo.deviceExtDir);
  487. }
  488. // If it's a "file:" URL, then pass the actual filename; otherwise, use the URL
  489. // ok, its not an http: type request therefore we should be able to see
  490. // it on the file system, right? If not then it's probably not valid
  491. File f;
  492. if (uri.startsWith("file:")) //$NON-NLS-1$
  493. {
  494. try
  495. {
  496. f = new File(new URI(uri));
  497. cmdList.add(f.getPath());
  498. }
  499. catch (URISyntaxException e)
  500. {
  501. IOException ioe = new IOException(e.getMessage());
  502. ioe.initCause(e);
  503. throw ioe;
  504. }
  505. }
  506. else
  507. {
  508. cmdList.add(uri);
  509. }
  510. if (airLaunchInfo.applicationContentRootDir != null)
  511. {
  512. cmdList.add(airLaunchInfo.applicationContentRootDir.getAbsolutePath());
  513. }
  514. List<String> args = null;
  515. if (airLaunchInfo.applicationArgumentsArray != null)
  516. {
  517. args = Arrays.asList(airLaunchInfo.applicationArgumentsArray);
  518. }
  519. else if (airLaunchInfo.applicationArguments != null)
  520. {
  521. args = splitArgs(airLaunchInfo.applicationArguments);
  522. }
  523. if (args != null && args.size() > 0)
  524. {
  525. cmdList.add("--"); //$NON-NLS-1$
  526. cmdList.addAll(args);
  527. }
  528. return cmdList.toArray(new String[cmdList.size()]);
  529. }
  530. /**
  531. * This is annoying: We must duplicate the operating system's behavior
  532. * with regard to splitting arguments.
  533. *
  534. * @param arguments A single string of arguments that are intended to
  535. * be passed to an AIR application. The tricky part is that some
  536. * of the arguments may be quoted, and if they are, then the quoting
  537. * will be in a way that is specific to the current platform. For
  538. * example, on Windows, strings are quoted with the double-quote character
  539. * ("); on Mac and Unix, strings can be quoted with either double-quote
  540. * or single-quote.
  541. * @return The equivalent
  542. */
  543. private List<String> splitArgs(String arguments)
  544. {
  545. List<String> retval = new ArrayList<>();
  546. arguments = arguments.trim();
  547. // Windows quotes only with double-quote; Mac and Unix also allow single-quote.
  548. boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("win"); //$NON-NLS-1$ //$NON-NLS-2$
  549. boolean isMacOrUnix = !isWindows;
  550. int i=0;
  551. while (i<arguments.length()) {
  552. char ch = arguments.charAt(i);
  553. if (ch == ' ' || ch == '\t') {
  554. // keep looping
  555. i++;
  556. } else if (ch == '"' || (isMacOrUnix && ch == '\'')) {
  557. int nextQuote = arguments.indexOf(ch, i+1);
  558. if (nextQuote == -1) {
  559. retval.add(arguments.substring(i+1));
  560. return retval;
  561. } else {
  562. retval.add(arguments.substring(i+1, nextQuote));
  563. i = nextQuote+1;
  564. }
  565. } else {
  566. int startPos = i;
  567. while (i<arguments.length()) {
  568. ch = arguments.charAt(i);
  569. if (ch == ' ' || ch == '\t') {
  570. break;
  571. }
  572. i++;
  573. }
  574. retval.add(arguments.substring(startPos, i));
  575. }
  576. }
  577. return retval;
  578. }
  579. /*
  580. * @see flash.tools.debugger.SessionManager#playerForUri(java.lang.String, flash.tools.debugger.AIRLaunchInfo)
  581. */
  582. public Player playerForUri(String url, AIRLaunchInfo airLaunchInfo)
  583. {
  584. LaunchInfo launchInfo = new LaunchInfo(url);
  585. if (airLaunchInfo != null)
  586. {
  587. return new AIRPlayer(airLaunchInfo.airDebugLauncher);
  588. }
  589. else if (launchInfo.isAIRLaunch())
  590. {
  591. return new AIRPlayer(null);
  592. }
  593. else
  594. {
  595. // Find the Netscape plugin
  596. if (getOS() == OS.Mac)
  597. {
  598. if (!launchInfo.isWebBrowserNativeLaunch())
  599. {
  600. File playerExe = m_debuggerCallbacks.getPlayerExe();
  601. return new StandalonePlayer(playerExe);
  602. }
  603. File flashPlugin = new File("/Library/Internet Plug-Ins/Flash Player.plugin"); //$NON-NLS-1$
  604. return new NetscapePluginPlayer(m_debuggerCallbacks.getHttpExe(), flashPlugin);
  605. }
  606. else
  607. {
  608. if (launchInfo.isWebBrowserNativeLaunch())
  609. {
  610. File httpExe = m_debuggerCallbacks.getHttpExe();
  611. if (httpExe.getName().equalsIgnoreCase("iexplore.exe")) //$NON-NLS-1$
  612. {
  613. // IE on Windows: Find the ActiveX control
  614. String activeXFile = null;
  615. try
  616. {
  617. activeXFile = m_debuggerCallbacks.queryWindowsRegistry("HKEY_CLASSES_ROOT\\CLSID\\{D27CDB6E-AE6D-11cf-96B8-444553540000}\\InprocServer32", null); //$NON-NLS-1$
  618. }
  619. catch (IOException e)
  620. {
  621. // ignore
  622. }
  623. if (activeXFile == null)
  624. return null; // we couldn't find the player
  625. File file = new File(activeXFile);
  626. return new ActiveXPlayer(httpExe, file);
  627. }
  628. else
  629. {
  630. // Find the Netscape plugin
  631. File browserDir = httpExe.getParentFile();
  632. // Opera puts plugins under "program\plugins" rather than under "plugins"
  633. if (httpExe.getName().equalsIgnoreCase("opera.exe")) //$NON-NLS-1$
  634. browserDir = new File(browserDir, "program"); //$NON-NLS-1$
  635. File pluginsDir = new File(browserDir, "plugins"); //$NON-NLS-1$
  636. File flashPlugin = new File(pluginsDir, "NPSWF32.dll"); // WARNING, Windows-specific //$NON-NLS-1$
  637. // Bug FB-4691: The player is now installed via a registry key, not
  638. // in the "plugins" directory.
  639. //
  640. // Although Mozilla does not document this, the actual behavior of
  641. // the browser seems to be that it looks first in the "plugins" directory,
  642. // and then, if the file is not found there, it looks in the registry.
  643. // So, we mimic that behavior.
  644. if (!flashPlugin.exists())
  645. {
  646. File pathFromRegistry = getWindowsMozillaPlayerPathFromRegistry();
  647. if (pathFromRegistry != null)
  648. flashPlugin = pathFromRegistry;
  649. }
  650. return new NetscapePluginPlayer(httpExe, flashPlugin);
  651. }
  652. }
  653. else if (launchInfo.isPlayerNativeLaunch())
  654. {
  655. File playerExe = m_debuggerCallbacks.getPlayerExe();
  656. return new StandalonePlayer(playerExe);
  657. }
  658. }
  659. }
  660. return null;
  661. }
  662. /**
  663. * Look in the Windows registry for the Mozilla version of the Flash player.
  664. */
  665. private File getWindowsMozillaPlayerPathFromRegistry()
  666. {
  667. final String KEY = "\\SOFTWARE\\MozillaPlugins\\@adobe.com/FlashPlayer"; //$NON-NLS-1$
  668. final String PATH = "Path"; //$NON-NLS-1$
  669. // According to
  670. //
  671. // http://developer.mozilla.org/en/docs/Plugins:_The_first_install_problem
  672. //
  673. // the MozillaPlugins key can be written to either HKEY_CURRENT_USER or
  674. // HKEY_LOCAL_MACHINE. Unfortunately, as of this writing, Firefox
  675. // (version 2.0.0.2) doesn't actually work that way -- it only checks
  676. // HKEY_LOCAL_MACHINE, but not HKEY_CURRENT_USER.
  677. //
  678. // But in hopeful anticipation of a fix for that, we are going to check both
  679. // locations. On current builds, that won't do any harm, because the
  680. // current Flash Player installer only writes to HKEY_LOCAL_MACHINE. In the
  681. // future, if Mozilla gets fixed and then the Flash player installer gets
  682. // updated, then our code will already work correctly.
  683. //
  684. // Another quirk: In my opinion, it would be better for Mozilla to look first
  685. // in HKEY_CURRENT_USER, and then in HKEY_LOCAL_MACHINE. However, according to
  686. //
  687. // http://developer.mozilla.org/en/docs/Installing_plugins_to_Gecko_embedding_browsers_on_Windows
  688. //
  689. // they don't agree with that -- they want HKEY_LOCAL_MACHINE first.
  690. String[] roots = { "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER" }; //$NON-NLS-1$ //$NON-NLS-2$
  691. for (String root : roots) {
  692. try {
  693. String path = m_debuggerCallbacks.queryWindowsRegistry(root + KEY, PATH);
  694. if (path != null)
  695. return new File(path);
  696. } catch (IOException e) {
  697. // ignore
  698. }
  699. }
  700. return null;
  701. }
  702. /*
  703. * @see flash.tools.debugger.SessionManager#supportsLaunch()
  704. */
  705. public boolean supportsLaunch()
  706. {
  707. return true;
  708. }
  709. /*
  710. * @see flash.tools.debugger.SessionManager#accept(flash.tools.debugger.IProgress)
  711. */
  712. public Session accept(IProgress waitReporter) throws IOException
  713. {
  714. return accept(null, waitReporter);
  715. }
  716. /**
  717. * A private variation on <code>accept()</code> that also has an argument
  718. * indicating that the process we are waiting for has terminated.
  719. *
  720. * @param pl
  721. * Optional process listener. If non-null, this is used to detect
  722. * if a process that was launched has terminated unexpectedly.
  723. * For example, if launch() launches adl, but adl exits, then we
  724. * don't want to continue to wait for a socket connection.
  725. */
  726. private Session accept(ProcessListener pl, IProgress waitReporter) throws IOException
  727. {
  728. // get timeout
  729. int timeout = getPreference(PREF_ACCEPT_TIMEOUT);
  730. int totalTimeout = timeout;
  731. int iterateOn = 100;
  732. PlayerSession session;
  733. try
  734. {
  735. m_serverSocket.setSoTimeout(iterateOn);
  736. // Wait 100ms per iteration. We have to do that so that we can report how long
  737. // we have been waiting.
  738. Socket s = null;
  739. while (s == null && !airAppTerminated(pl))
  740. {
  741. try
  742. {
  743. s = m_serverSocket.accept();
  744. }
  745. catch(IOException ste)
  746. {
  747. timeout -= iterateOn;
  748. if (timeout < 0 || m_serverSocket == null || m_serverSocket.isClosed())
  749. throw ste; // we reached the timeout, or someome called stopListening()
  750. }
  751. // Tell the progress monitor we've waited a little while longer,
  752. // so that the Eclipse progress bar can keep chugging along
  753. if (waitReporter != null)
  754. waitReporter.setProgress(totalTimeout - timeout, totalTimeout);
  755. }
  756. if (s == null && airAppTerminated(pl))
  757. {
  758. throw pl.createLaunchFailureException();
  759. }
  760. /* create a new session around this socket */
  761. session = PlayerSession.createFromSocketWithOptions(s, m_debuggerCallbacks, this);
  762. // transfer preferences
  763. session.setPreferences(m_prefs);
  764. }
  765. catch(NullPointerException npe)
  766. {
  767. throw new BindException(getLocalizationManager().getLocalizedTextString("serverSocketNotListening")); //$NON-NLS-1$
  768. }
  769. return session;
  770. }
  771. /**
  772. * Returns true if the passed-in process listener is for an AIR application
  773. * that has terminated. This is used by accept() in order to detect that it
  774. * should give up listening on the socket.
  775. *
  776. * The reason we can't do this for Flash player-based apps is that unlike
  777. * AIR apps, the process that we launched sometimes acts as just sort of a
  778. * "launcher" process that terminates quickly, and the actual Flash player
  779. * is in some other process. For example, on Mac, we often invoke the "open"
  780. * program to open a web browser; and on Windows, if you launch firefox.exe
  781. * but it detects that there is already a running instance of firefox.exe,
  782. * the new instance will just pass a message to the old instance, and then
  783. * the new instance will terminate.
  784. *
  785. * @param pl
  786. * a process listener, or <code>null</code>
  787. * @return true if pl refers to an AIR app that has terminated.
  788. */
  789. private boolean airAppTerminated(ProcessListener pl)
  790. {
  791. if (pl != null)
  792. {
  793. if (pl.isAIRApp())
  794. {
  795. if (pl.isProcessDead())
  796. {
  797. return true;
  798. }
  799. }
  800. }
  801. return false;
  802. }
  803. /*
  804. * @see flash.tools.debugger.SessionManager#getDebuggerCallbacks()
  805. */
  806. public IDebuggerCallbacks getDebuggerCallbacks()
  807. {
  808. return m_debuggerCallbacks;
  809. }
  810. /*
  811. * @see flash.tools.debugger.SessionManager#setDebuggerCallbacks(flash.tools.debugger.IDebuggerCallbacks)
  812. */
  813. public void setDebuggerCallbacks(IDebuggerCallbacks debuggerCallbacks)
  814. {
  815. m_debuggerCallbacks = debuggerCallbacks;
  816. }
  817. /**
  818. * A private variation on <code>connect()</code> that also has an argument
  819. * indicating that the process we are waiting for has terminated.
  820. *
  821. * @param pl
  822. * Optional process listener. If non-null, this is used to detect
  823. * if a process that was launched has terminated unexpectedly.
  824. * For example, if launch() launches adl, but adl exits, then we
  825. * don't want to continue to wait for a socket connection.
  826. */
  827. public Session connect(int port, IProgress waitReporter) throws IOException
  828. {
  829. final int waitTime = getPreference(PREF_CONNECT_WAIT_INTERVAL);
  830. final int maxRetryAttempts = getPreference(PREF_CONNECT_RETRY_ATTEMPTS);
  831. final int totalTimeout = getPreference(PREF_CONNECT_TIMEOUT);
  832. final long timeForConnectStart = System.currentTimeMillis();
  833. long elapsedTime = 0;
  834. int retryAttempts = -1;
  835. PlayerSession session = null;
  836. Socket s = null;
  837. m_cancelConnect = false;
  838. // Try to see if a connect happens till totalTimeout
  839. // If the connection was refused in between, retry
  840. // again after waitTime until totalTimeout is elapsed.
  841. // Retry mechanism is disabled if PREF_CONNECT_RETRY_ATTEMPTS
  842. // is 0.
  843. while (s == null)
  844. {
  845. try
  846. {
  847. InetSocketAddress localAddress = new InetSocketAddress(InetAddress.getByName(null), port);
  848. s = new Socket();
  849. //save the socket for canceling connect
  850. m_connectSocket = s;
  851. //connect to loopback address at the specified port
  852. s.connect(localAddress, totalTimeout);
  853. }
  854. catch(IOException ste)
  855. {
  856. if (ste instanceof SocketTimeoutException) {
  857. //if we timed out, abort connect
  858. abortConnect(ste);
  859. }
  860. safeCloseSocket(s);
  861. s = null;
  862. retryAttempts++;
  863. //if we should not retry, checkConnectTimeout
  864. //throws an exception
  865. elapsedTime = checkConnectTimeout(waitTime, maxRetryAttempts,
  866. totalTimeout, retryAttempts, timeForConnectStart, ste);
  867. }
  868. // Tell the progress monitor we've waited a little while longer,
  869. // so that the Eclipse progress bar can keep chugging along
  870. if (waitReporter != null)
  871. waitReporter.setProgress((int)elapsedTime, totalTimeout);
  872. if (s != null) {
  873. /** If we connected, make sure that we get some response
  874. * back after sending the handshake. This is required because
  875. * of the way port forwarding works. A connect will be successful
  876. * if port forwarding is set up, but we won't get any response
  877. * unless the application is actually listening.
  878. */
  879. /* create a new session around this socket */
  880. session = PlayerSession.createFromSocketWithOptions(s, m_debuggerCallbacks, this);
  881. // transfer preferences
  882. session.setPreferences(m_prefs);
  883. try {
  884. session.bind();
  885. }
  886. catch (VersionException ex) {
  887. session.unbind();
  888. safeCloseSocket(s);
  889. s = null;
  890. retryAttempts++;
  891. /** The VersionException here is considered as an IOException
  892. * because we do not know if there was even a valid application
  893. * listening on the port. Once the port is forwarded, connect
  894. * succeeds and we get a VersionException even if player is not
  895. * listening on that port.
  896. */
  897. elapsedTime = checkConnectTimeout(waitTime, maxRetryAttempts,
  898. totalTimeout, retryAttempts, timeForConnectStart,
  899. new IOException(ex.getLocalizedMessage()));
  900. }
  901. }
  902. }
  903. m_connectSocket = null;
  904. return session;
  905. }
  906. /**
  907. * @param waitTime
  908. * @param maxRetryAttempts
  909. * @param totalTimeout
  910. * @param retryAttempts
  911. * @param startTime
  912. * @param caughtException
  913. * @return
  914. * @throws IOException
  915. */
  916. private long checkConnectTimeout(final int waitTime,
  917. final int maxRetryAttempts, final int totalTimeout,
  918. int retryAttempts, final long startTime, IOException caughtException)
  919. throws IOException {
  920. long elapsedTime;
  921. long endTime = System.currentTimeMillis();
  922. elapsedTime = endTime - startTime;
  923. // check if we should retry
  924. boolean retryFinished = (maxRetryAttempts != -1 && retryAttempts >= maxRetryAttempts);
  925. // check if we timed out or somebody called stopConnecting()
  926. if (retryFinished ||
  927. elapsedTime > totalTimeout ||
  928. m_cancelConnect ) {
  929. abortConnect(caughtException);
  930. }
  931. //wait a bit before retrying
  932. try {
  933. Thread.sleep(waitTime);
  934. } catch (InterruptedException e) {
  935. abortConnect(caughtException);
  936. }
  937. //check cancel before resuming
  938. if (m_cancelConnect ) {
  939. abortConnect(caughtException);
  940. }
  941. return elapsedTime;
  942. }
  943. /**
  944. * @param ste
  945. * @throws IOException
  946. */
  947. private void abortConnect(IOException ste) throws IOException {
  948. m_connectSocket = null;
  949. m_cancelConnect = false;
  950. throw ste;
  951. }
  952. /**
  953. * @param s
  954. */
  955. private void safeCloseSocket(Socket s) {
  956. //clean up the socket
  957. if (s != null && !s.isClosed()) {
  958. try {
  959. s.close();
  960. }
  961. catch (IOException closeException) {
  962. //ignore
  963. }
  964. }
  965. }
  966. /*
  967. * @see flash.tools.debugger.SessionManager#stopConnecting()
  968. */
  969. public void stopConnecting() throws IOException
  970. {
  971. if (!m_cancelConnect) {
  972. m_cancelConnect = true;
  973. if (m_connectSocket != null)
  974. {
  975. m_connectSocket.close();
  976. m_connectSocket = null;
  977. }
  978. }
  979. }
  980. /*
  981. * @see flash.tools.debugger.SessionManager#isConnecting()
  982. */
  983. public boolean isConnecting()
  984. {
  985. return m_connectSocket != null;
  986. }
  987. /**
  988. * Returns the localization manager. Use this for all localized strings.
  989. */
  990. public static LocalizationManager getLocalizationManager()
  991. {
  992. return m_localizationManager;
  993. }
  994. }