DuktapeDebuggerExtension.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. //
  2. // Copyright (c) 2014-2016 THUNDERBEAST GAMES LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. export interface Breakpoint {
  23. fileName: string;
  24. lineNumber: number;
  25. }
  26. class BreakpointList {
  27. // list of breakpoints
  28. private breakpoints: Breakpoint[] = [];
  29. private indexOfBreakpoint(fileName: string, lineNumber: number): number {
  30. let curr: Breakpoint;
  31. for (let i = 0, iEnd = this.breakpoints.length; i < iEnd; i++) {
  32. curr = this.breakpoints[i];
  33. if (curr.fileName == fileName && curr.lineNumber == lineNumber) {
  34. return i;
  35. }
  36. }
  37. return -1;
  38. }
  39. removeAllBreakpoints() {
  40. this.breakpoints.length = 0;
  41. }
  42. removeBreakpoint(fileName: string, lineNumber: number) {
  43. const idx = this.indexOfBreakpoint(fileName, lineNumber);
  44. if (idx != -1) {
  45. this.breakpoints.splice(idx);
  46. }
  47. }
  48. addBreakpoint(fileName: string, lineNumber: number) {
  49. const idx = this.indexOfBreakpoint(fileName, lineNumber);
  50. if (idx == -1) {
  51. console.log("Adding breakpoint: " + fileName + ":" + lineNumber);
  52. this.breakpoints.push({
  53. fileName,
  54. lineNumber
  55. });
  56. }
  57. }
  58. getBreakpoint(fileName: string, lineNumber: number): Breakpoint {
  59. const idx = this.indexOfBreakpoint(fileName, lineNumber);
  60. return this.breakpoints[idx];
  61. }
  62. toggleBreakpoint(fileName: string, lineNumber: number) {
  63. let decoration = this.getBreakpoint(fileName, lineNumber);
  64. if (decoration) {
  65. this.removeBreakpoint(fileName, lineNumber);
  66. } else {
  67. this.addBreakpoint(fileName, lineNumber);
  68. }
  69. }
  70. getBreakpoints(): Breakpoint[] {
  71. return this.breakpoints;
  72. }
  73. }
  74. interface ClientProxyMappings {
  75. toggleBreakpoint: string;
  76. addBreakpoint: string;
  77. removeBreakpoint: string;
  78. }
  79. interface ClientListener {
  80. name: string;
  81. frame: WebView.WebClient;
  82. callbacks: ClientProxyMappings;
  83. }
  84. /**
  85. * extension that will communicate with the duktape debugger
  86. */
  87. export default class DuktapeDebuggerExtension extends Atomic.ScriptObject implements Editor.HostExtensions.HostEditorService {
  88. name: string = "HostDebuggerExtension";
  89. description: string = "This service supports the duktape debugger interface";
  90. private serviceRegistry: Editor.HostExtensions.HostServiceLocator = null;
  91. private listeners: ClientListener[] = [];
  92. private breakpointList = new BreakpointList();
  93. /**
  94. * Inject this language service into the registry
  95. * @return {[type]} True if successful
  96. */
  97. initialize(serviceLocator: Editor.HostExtensions.HostServiceLocator) {
  98. // We care about both resource events as well as project events
  99. serviceLocator.resourceServices.register(this);
  100. serviceLocator.projectServices.register(this);
  101. serviceLocator.uiServices.register(this);
  102. this.serviceRegistry = serviceLocator;
  103. }
  104. /**
  105. * Handle messages that are submitted via Atomic.Query from within a web view editor.
  106. * @param message The message type that was submitted to be used to determine what the data contains if present
  107. * @param data any additional data that needs to be submitted with the message
  108. */
  109. handleWebMessage(webMessage: WebView.WebMessageEvent, messageType: string, data: any) {
  110. switch (messageType) {
  111. case "Debugger.AddBreakpoint":
  112. this.addBreakpoint(data);
  113. this.webMessageEventResponse(webMessage);
  114. break;
  115. case "Debugger.RemoveBreakpoint":
  116. this.removeBreakpoint(data);
  117. this.webMessageEventResponse(webMessage);
  118. break;
  119. case "Debugger.ToggleBreakpoint":
  120. this.toggleBreakpoint(data, webMessage.handler.webClient);
  121. this.webMessageEventResponse(webMessage);
  122. break;
  123. case "Debugger.RemoveAllBreakpoints":
  124. this.breakpointList.removeAllBreakpoints();
  125. this.webMessageEventResponse(webMessage);
  126. break;
  127. case "Debugger.GetBreakpoints":
  128. this.webMessageEventResponse(webMessage, this.breakpointList.getBreakpoints());
  129. break;
  130. case "Debugger.RegisterDebuggerListener":
  131. this.registerDebuggerListener(webMessage, data);
  132. this.webMessageEventResponse(webMessage);
  133. break;
  134. }
  135. }
  136. webMessageEventResponse<T>(originalMessage: WebView.WebMessageEvent, response?: T) {
  137. if (response) {
  138. const wrappedResponse: WebView.WebMessageEventResponse<T> = {
  139. response
  140. };
  141. originalMessage.handler.success(JSON.stringify(wrappedResponse));
  142. } else {
  143. originalMessage.handler.success();
  144. }
  145. }
  146. registerDebuggerListener(originalMessage: WebView.WebMessageEvent, messageData: {
  147. name: string,
  148. callbacks: any
  149. }) {
  150. console.log(`Registering debug listener: ${messageData.name}`);
  151. const listenerReference: ClientListener = {
  152. name: messageData.name,
  153. frame: originalMessage.handler.webClient,
  154. callbacks: messageData.callbacks
  155. };
  156. this.listeners.push(listenerReference);
  157. // clean up any stale ones
  158. this.listeners = this.listeners.filter(l => l.frame);
  159. }
  160. addBreakpoint(bp: Breakpoint) {
  161. this.breakpointList.addBreakpoint(bp.fileName, bp.lineNumber);
  162. for (let listener of this.listeners) {
  163. if (listener.frame) {
  164. console.log(`Adding breakpoint ${bp.fileName}:${bp.lineNumber} to ${listener.name}`);
  165. this.proxyWebClientMethod(
  166. listener.frame,
  167. listener.callbacks.addBreakpoint,
  168. bp.fileName,
  169. bp.lineNumber,
  170. false);
  171. }
  172. }
  173. }
  174. removeBreakpoint(bp: Breakpoint) {
  175. this.breakpointList.removeBreakpoint(bp.fileName, bp.lineNumber);
  176. for (let listener of this.listeners) {
  177. if (listener.frame) {
  178. console.log(`Remove breakpoint ${bp.fileName}:${bp.lineNumber} to ${listener.name}`);
  179. this.proxyWebClientMethod(
  180. listener.frame,
  181. listener.callbacks.removeBreakpoint,
  182. bp.fileName,
  183. bp.lineNumber,
  184. false);
  185. }
  186. }
  187. }
  188. toggleBreakpoint(bp: Breakpoint, sender: WebView.WebClient) {
  189. this.breakpointList.toggleBreakpoint(bp.fileName, bp.lineNumber);
  190. for (let listener of this.listeners) {
  191. if (listener.frame && listener.frame != sender) {
  192. console.log(`Sending Toggle breakpoint ${bp.fileName}:${bp.lineNumber} to ${listener.name}`);
  193. this.proxyWebClientMethod(
  194. listener.frame,
  195. listener.callbacks.toggleBreakpoint,
  196. bp.fileName,
  197. bp.lineNumber,
  198. false);
  199. }
  200. }
  201. }
  202. resume() {
  203. }
  204. attach() {
  205. }
  206. detach() {
  207. }
  208. /**
  209. * Utility function that will compose a method call to the web client and execute it
  210. * It will construct it in the form of "MethodName(....);"
  211. * @param {WebView.WebClient} webClient
  212. * @param {string} methodName
  213. * @param {any} ...params
  214. */
  215. proxyWebClientMethod(webClient: WebView.WebClient, methodName: string, ...params) {
  216. let paramBuilder = [];
  217. for (let p of params) {
  218. switch (typeof (p)) {
  219. case "boolean":
  220. case "number":
  221. paramBuilder.push(p.toString());
  222. break;
  223. default:
  224. paramBuilder.push(`"${p}"`);
  225. break;
  226. }
  227. }
  228. const methodCall = `${methodName}(${paramBuilder.join(",")});`;
  229. webClient.executeJavaScript(methodCall);
  230. }
  231. }