ScriptManager.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. var events = require('events'),
  2. async = require('async'),
  3. debug = require('debug')('node-inspector:ScriptManager'),
  4. convert = require('./convert.js');
  5. // see Blink inspector > ContentSearchUtils.cpp > findMagicComment()
  6. var SOURCE_MAP_URL_REGEX = /\/\/[@#][ \t]sourceMappingURL=[ \t]*([^\s'"]*)[ \t]*$/m;
  7. /**
  8. * @param {{hidden}} config
  9. * @param {FrontendClient} frontendClient
  10. * @param {DebuggerClient} debuggerClient
  11. * @constructor
  12. */
  13. function ScriptManager(config, frontendClient, debuggerClient) {
  14. config = config || {};
  15. var self = Object.create(ScriptManager.prototype, {
  16. _sources: { value: {}, writable: true },
  17. _hidden: { value: config.hidden || [] },
  18. _frontendClient: { value: frontendClient },
  19. _debuggerClient: { value: debuggerClient }
  20. });
  21. self._registerDebuggerEventHandlers();
  22. return self;
  23. }
  24. ScriptManager.prototype = Object.create(events.EventEmitter.prototype, {
  25. mainAppScript: { value: null, writable: true },
  26. _registerDebuggerEventHandlers: {
  27. value: function() {
  28. this._debuggerClient.on(
  29. 'afterCompile',
  30. this._onAfterCompile.bind(this)
  31. );
  32. }
  33. },
  34. _onAfterCompile: {
  35. value: function(event) {
  36. if (!event.script) {
  37. console.log(
  38. 'Unexpected error: debugger emitted afterCompile event' +
  39. 'with no script data.'
  40. );
  41. return;
  42. }
  43. this.addScript(event.script);
  44. }
  45. },
  46. _isNodeInternal: {
  47. value: function(scriptName) {
  48. // node.js internal scripts have no path, just a filename
  49. // regular scripts have always a full path
  50. // (i.e their name contains at least one path separator)
  51. var isFullPath = /[\/\\]/.test(scriptName);
  52. return !isFullPath;
  53. }
  54. },
  55. _listAllSources: {
  56. value: function() {
  57. var self = this;
  58. return Object.keys(this._sources).map(function fnSelectValue(key) {
  59. return self._sources[key];
  60. });
  61. }
  62. },
  63. isScriptHidden: {
  64. /**
  65. * @param {string} scriptPath.
  66. * @return {boolean}
  67. */
  68. value: function(scriptPath) {
  69. return this._hidden.some(function fnHiddenScriptMatchesPath(r) {
  70. return r.test(scriptPath);
  71. });
  72. }
  73. },
  74. findScriptByID: {
  75. /**
  76. * @param {string} id script id.
  77. * @return {{hidden: boolean, path: string, url: string}}
  78. */
  79. value: function(id) {
  80. return this._sources[id];
  81. }
  82. },
  83. addScript: {
  84. value: function(v8data) {
  85. var localPath = v8data.name;
  86. var hidden = this.isScriptHidden(localPath) && localPath != this.mainAppScript;
  87. var inspectorScriptData = this._doAddScript(v8data, hidden);
  88. debug('addScript id: %s localPath: %s hidden? %s source? %s',
  89. v8data.id, localPath, hidden, !!v8data.source);
  90. if (hidden || this._isNodeInternal(localPath)) {
  91. notifyFrontEnd.call(this);
  92. } else {
  93. this._getSourceMapUrl(
  94. v8data.id,
  95. v8data.source,
  96. function onGetSourceMapUrlReturn(err, sourceMapUrl) {
  97. if (err) {
  98. console.log(
  99. 'Warning: cannot parse SourceMap URL for script %s (id %d). %s',
  100. localPath,
  101. v8data.id,
  102. err);
  103. }
  104. debug('sourceMapUrl for script %s:%s is %s',
  105. v8data.id, localPath, sourceMapUrl);
  106. inspectorScriptData.sourceMapURL = sourceMapUrl;
  107. notifyFrontEnd.call(this);
  108. }.bind(this)
  109. );
  110. }
  111. function notifyFrontEnd() {
  112. if (hidden) return;
  113. this._frontendClient.sendEvent(
  114. 'Debugger.scriptParsed',
  115. inspectorScriptData
  116. );
  117. }
  118. }
  119. },
  120. _doAddScript: {
  121. value: function(v8data, hidden) {
  122. var inspectorUrl = convert.v8NameToInspectorUrl(v8data.name);
  123. var inspectorScriptData = {
  124. scriptId: String(v8data.id),
  125. url: inspectorUrl,
  126. startLine: v8data.lineOffset,
  127. startColumn: v8data.columnOffset
  128. /* Properties not set:
  129. endLine: undefined,
  130. endColumn: undefined,
  131. isContentScript: undefined,
  132. hasSourceURL: undefined,
  133. */
  134. };
  135. var item = {
  136. hidden: hidden,
  137. v8name: v8data.name,
  138. url: inspectorUrl
  139. };
  140. this._sources[inspectorScriptData.scriptId] = item;
  141. return inspectorScriptData;
  142. }
  143. },
  144. _getSourceMapUrl: {
  145. value: function(scriptId, scriptSource, callback) {
  146. var getSource;
  147. if (scriptSource == null) {
  148. debug('_getSourceMapUrl(%s) - fetching source from V8', scriptId);
  149. getSource = this._debuggerClient.getScriptSourceById
  150. .bind(this._debuggerClient, scriptId);
  151. } else {
  152. debug('_getSourceMapUrl(%s) - using the suplied source', scriptId);
  153. getSource = function(cb) { cb(null, scriptSource); };
  154. }
  155. async.waterfall(
  156. [
  157. getSource,
  158. this._parseSourceMapUrlFromScriptSource.bind(this)
  159. ],
  160. callback
  161. );
  162. }
  163. },
  164. _parseSourceMapUrlFromScriptSource: {
  165. value: function(source, callback) {
  166. var match = SOURCE_MAP_URL_REGEX.exec(source);
  167. callback(null, match ? match[1] : undefined);
  168. }
  169. },
  170. reset: {
  171. value: function() {
  172. this._sources = {};
  173. }
  174. }
  175. });
  176. exports.ScriptManager = ScriptManager;