ResourceScriptMapping.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. /*
  2. * Copyright (C) 2012 Google Inc. All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions are
  6. * met:
  7. *
  8. * * Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * * Redistributions in binary form must reproduce the above
  11. * copyright notice, this list of conditions and the following disclaimer
  12. * in the documentation and/or other materials provided with the
  13. * distribution.
  14. * * Neither the name of Google Inc. nor the names of its
  15. * contributors may be used to endorse or promote products derived from
  16. * this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. */
  30. /**
  31. * @constructor
  32. * @implements {WebInspector.ScriptSourceMapping}
  33. * @param {WebInspector.Workspace} workspace
  34. */
  35. WebInspector.ResourceScriptMapping = function(workspace)
  36. {
  37. this._workspace = workspace;
  38. this._workspace.addEventListener(WebInspector.UISourceCodeProvider.Events.UISourceCodeAdded, this._uiSourceCodeAddedToWorkspace, this);
  39. WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._debuggerReset, this);
  40. this._initialize();
  41. }
  42. WebInspector.ResourceScriptMapping.prototype = {
  43. /**
  44. * @param {WebInspector.RawLocation} rawLocation
  45. * @return {WebInspector.UILocation}
  46. */
  47. rawLocationToUILocation: function(rawLocation)
  48. {
  49. var debuggerModelLocation = /** @type {WebInspector.DebuggerModel.Location} */ (rawLocation);
  50. var script = WebInspector.debuggerModel.scriptForId(debuggerModelLocation.scriptId);
  51. var uiSourceCode = this._workspaceUISourceCodeForScript(script);
  52. if (!uiSourceCode)
  53. return null;
  54. var scriptFile = uiSourceCode.scriptFile();
  55. if (scriptFile && ((scriptFile.hasDivergedFromVM() && !scriptFile.isMergingToVM()) || scriptFile.isDivergingFromVM()))
  56. return null;
  57. return new WebInspector.UILocation(uiSourceCode, debuggerModelLocation.lineNumber, debuggerModelLocation.columnNumber || 0);
  58. },
  59. /**
  60. * @param {WebInspector.UISourceCode} uiSourceCode
  61. * @param {number} lineNumber
  62. * @param {number} columnNumber
  63. * @return {WebInspector.DebuggerModel.Location}
  64. */
  65. uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
  66. {
  67. var scripts = this._scriptsForUISourceCode(uiSourceCode);
  68. console.assert(scripts.length);
  69. return WebInspector.debuggerModel.createRawLocation(scripts[0], lineNumber, columnNumber);
  70. },
  71. /**
  72. * @return {boolean}
  73. */
  74. isIdentity: function()
  75. {
  76. return true;
  77. },
  78. /**
  79. * @param {WebInspector.Script} script
  80. */
  81. addScript: function(script)
  82. {
  83. if (script.isAnonymousScript() || script.isDynamicScript())
  84. return;
  85. script.pushSourceMapping(this);
  86. var scriptsForSourceURL = script.isInlineScript() ? this._inlineScriptsForSourceURL : this._nonInlineScriptsForSourceURL;
  87. scriptsForSourceURL[script.sourceURL] = scriptsForSourceURL[script.sourceURL] || [];
  88. scriptsForSourceURL[script.sourceURL].push(script);
  89. var uiSourceCode = this._workspaceUISourceCodeForScript(script);
  90. if (!uiSourceCode)
  91. return;
  92. this._bindUISourceCodeToScripts(uiSourceCode, [script]);
  93. },
  94. _uiSourceCodeAddedToWorkspace: function(event)
  95. {
  96. var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.data);
  97. if (!uiSourceCode.url)
  98. return;
  99. var scripts = this._scriptsForUISourceCode(uiSourceCode);
  100. if (!scripts.length)
  101. return;
  102. this._bindUISourceCodeToScripts(uiSourceCode, scripts);
  103. },
  104. /**
  105. * @param {WebInspector.UISourceCode} uiSourceCode
  106. */
  107. _hasMergedToVM: function(uiSourceCode)
  108. {
  109. var scripts = this._scriptsForUISourceCode(uiSourceCode);
  110. if (!scripts.length)
  111. return;
  112. for (var i = 0; i < scripts.length; ++i)
  113. scripts[i].updateLocations();
  114. },
  115. /**
  116. * @param {WebInspector.UISourceCode} uiSourceCode
  117. */
  118. _hasDivergedFromVM: function(uiSourceCode)
  119. {
  120. var scripts = this._scriptsForUISourceCode(uiSourceCode);
  121. if (!scripts.length)
  122. return;
  123. for (var i = 0; i < scripts.length; ++i)
  124. scripts[i].updateLocations();
  125. },
  126. /**
  127. * @param {WebInspector.Script} script
  128. * @return {WebInspector.UISourceCode}
  129. */
  130. _workspaceUISourceCodeForScript: function(script)
  131. {
  132. if (script.isAnonymousScript() || script.isDynamicScript())
  133. return null;
  134. // FIXME: workaround for script.isDynamicScript() being unreliable.
  135. if (!script.isInlineScript() && this._inlineScriptsForSourceURL[script.sourceURL])
  136. return null;
  137. return this._workspace.uiSourceCodeForURL(script.sourceURL);
  138. },
  139. /**
  140. * @param {WebInspector.UISourceCode} uiSourceCode
  141. * @return {Array.<WebInspector.Script>}
  142. */
  143. _scriptsForUISourceCode: function(uiSourceCode)
  144. {
  145. var isInlineScript;
  146. switch (uiSourceCode.contentType()) {
  147. case WebInspector.resourceTypes.Document:
  148. isInlineScript = true;
  149. break;
  150. case WebInspector.resourceTypes.Script:
  151. isInlineScript = false;
  152. break;
  153. default:
  154. return [];
  155. }
  156. if (!uiSourceCode.url)
  157. return [];
  158. var scriptsForSourceURL = isInlineScript ? this._inlineScriptsForSourceURL : this._nonInlineScriptsForSourceURL;
  159. return scriptsForSourceURL[uiSourceCode.url] || [];
  160. },
  161. /**
  162. * @param {WebInspector.UISourceCode} uiSourceCode
  163. * @param {Array.<WebInspector.Script>} scripts
  164. */
  165. _bindUISourceCodeToScripts: function(uiSourceCode, scripts)
  166. {
  167. console.assert(scripts.length);
  168. var scriptFile = new WebInspector.ResourceScriptFile(this, uiSourceCode, scripts);
  169. uiSourceCode.setScriptFile(scriptFile);
  170. for (var i = 0; i < scripts.length; ++i)
  171. scripts[i].updateLocations();
  172. uiSourceCode.setSourceMapping(this);
  173. },
  174. /**
  175. * @param {WebInspector.UISourceCode} uiSourceCode
  176. * @param {Array.<WebInspector.Script>} scripts
  177. */
  178. _unbindUISourceCodeFromScripts: function(uiSourceCode, scripts)
  179. {
  180. console.assert(scripts.length);
  181. var scriptFile = /** @type {WebInspector.ResourceScriptFile} */ (uiSourceCode.scriptFile());
  182. scriptFile.dispose();
  183. uiSourceCode.setScriptFile(null);
  184. uiSourceCode.setSourceMapping(null);
  185. },
  186. _initialize: function()
  187. {
  188. /** @type {!Object.<string, !Array.<!WebInspector.UISourceCode>>} */
  189. this._inlineScriptsForSourceURL = {};
  190. /** @type {!Object.<string, !Array.<!WebInspector.UISourceCode>>} */
  191. this._nonInlineScriptsForSourceURL = {};
  192. },
  193. _debuggerReset: function()
  194. {
  195. /**
  196. * @param {!Object.<string, !Array.<!WebInspector.UISourceCode>>} scriptsForSourceURL
  197. */
  198. function unbindUISourceCodes(scriptsForSourceURL)
  199. {
  200. for (var sourceURL in scriptsForSourceURL) {
  201. var scripts = scriptsForSourceURL[sourceURL];
  202. if (!scripts.length)
  203. continue;
  204. var uiSourceCode = this._workspaceUISourceCodeForScript(scripts[0]);
  205. if (!uiSourceCode)
  206. continue;
  207. this._unbindUISourceCodeFromScripts(uiSourceCode, scripts);
  208. }
  209. }
  210. unbindUISourceCodes.call(this, this._inlineScriptsForSourceURL);
  211. unbindUISourceCodes.call(this, this._nonInlineScriptsForSourceURL);
  212. this._initialize();
  213. },
  214. }
  215. /**
  216. * @interface
  217. * @extends {WebInspector.EventTarget}
  218. */
  219. WebInspector.ScriptFile = function()
  220. {
  221. }
  222. WebInspector.ScriptFile.Events = {
  223. DidMergeToVM: "DidMergeToVM",
  224. DidDivergeFromVM: "DidDivergeFromVM",
  225. }
  226. WebInspector.ScriptFile.prototype = {
  227. /**
  228. * @return {boolean}
  229. */
  230. hasDivergedFromVM: function() { return false; },
  231. /**
  232. * @return {boolean}
  233. */
  234. isDivergingFromVM: function() { return false; },
  235. /**
  236. * @return {boolean}
  237. */
  238. isMergingToVM: function() { return false; },
  239. checkMapping: function() { },
  240. }
  241. /**
  242. * @constructor
  243. * @implements {WebInspector.ScriptFile}
  244. * @extends {WebInspector.Object}
  245. * @param {WebInspector.ResourceScriptMapping} resourceScriptMapping
  246. * @param {WebInspector.UISourceCode} uiSourceCode
  247. */
  248. WebInspector.ResourceScriptFile = function(resourceScriptMapping, uiSourceCode, scripts)
  249. {
  250. console.assert(scripts.length);
  251. WebInspector.ScriptFile.call(this);
  252. this._resourceScriptMapping = resourceScriptMapping;
  253. this._uiSourceCode = uiSourceCode;
  254. if (this._uiSourceCode.contentType() === WebInspector.resourceTypes.Script)
  255. this._script = scripts[0];
  256. this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
  257. this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
  258. this._update();
  259. }
  260. WebInspector.ResourceScriptFile.prototype = {
  261. _workingCopyCommitted: function(event)
  262. {
  263. /**
  264. * @param {?string} error
  265. */
  266. function innerCallback(error)
  267. {
  268. if (error) {
  269. this._update();
  270. WebInspector.showErrorMessage(error);
  271. return;
  272. }
  273. this._scriptSource = source;
  274. this._update();
  275. }
  276. if (!this._script)
  277. return;
  278. var source = this._uiSourceCode.workingCopy();
  279. if (this._script.hasSourceURL && !this._sourceEndsWithSourceURL(source))
  280. source += "\n //@ sourceURL=" + this._script.sourceURL;
  281. WebInspector.debuggerModel.setScriptSource(this._script.scriptId, source, innerCallback.bind(this));
  282. },
  283. /**
  284. * @return {boolean}
  285. */
  286. _isDiverged: function()
  287. {
  288. if (this._uiSourceCode.isDirty())
  289. return true;
  290. if (!this._script)
  291. return false;
  292. if (typeof this._scriptSource === "undefined")
  293. return false;
  294. return !this._sourceMatchesScriptSource(this._uiSourceCode.workingCopy(), this._scriptSource);
  295. },
  296. /**
  297. * @param {string} source
  298. * @param {string} scriptSource
  299. * @return {boolean}
  300. */
  301. _sourceMatchesScriptSource: function(source, scriptSource)
  302. {
  303. if (!scriptSource.startsWith(source))
  304. return false;
  305. var scriptSourceTail = scriptSource.substr(source.length).trim();
  306. return !scriptSourceTail || !!scriptSourceTail.match(/^\/\/@\ssourceURL=\s*(\S*?)\s*$/m);
  307. },
  308. /**
  309. * @param {string} source
  310. * @return {boolean}
  311. */
  312. _sourceEndsWithSourceURL: function(source)
  313. {
  314. return !!source.match(/\/\/@\ssourceURL=\s*(\S*?)\s*$/m);
  315. },
  316. /**
  317. * @param {WebInspector.Event} event
  318. */
  319. _workingCopyChanged: function(event)
  320. {
  321. this._update();
  322. },
  323. _update: function()
  324. {
  325. if (this._isDiverged() && !this._hasDivergedFromVM)
  326. this._divergeFromVM();
  327. else if (!this._isDiverged() && this._hasDivergedFromVM)
  328. this._mergeToVM();
  329. },
  330. _divergeFromVM: function()
  331. {
  332. this._isDivergingFromVM = true;
  333. this._resourceScriptMapping._hasDivergedFromVM(this._uiSourceCode);
  334. delete this._isDivergingFromVM;
  335. this._hasDivergedFromVM = true;
  336. this.dispatchEventToListeners(WebInspector.ScriptFile.Events.DidDivergeFromVM, this._uiSourceCode);
  337. },
  338. _mergeToVM: function()
  339. {
  340. delete this._hasDivergedFromVM;
  341. this._isMergingToVM = true;
  342. this._resourceScriptMapping._hasMergedToVM(this._uiSourceCode);
  343. delete this._isMergingToVM;
  344. this.dispatchEventToListeners(WebInspector.ScriptFile.Events.DidMergeToVM, this._uiSourceCode);
  345. },
  346. /**
  347. * @return {boolean}
  348. */
  349. hasDivergedFromVM: function()
  350. {
  351. return this._hasDivergedFromVM;
  352. },
  353. /**
  354. * @return {boolean}
  355. */
  356. isDivergingFromVM: function()
  357. {
  358. return this._isDivergingFromVM;
  359. },
  360. /**
  361. * @return {boolean}
  362. */
  363. isMergingToVM: function()
  364. {
  365. return this._isMergingToVM;
  366. },
  367. checkMapping: function()
  368. {
  369. if (!this._script)
  370. return;
  371. if (typeof this._scriptSource !== "undefined")
  372. return;
  373. this._script.requestContent(callback.bind(this));
  374. /**
  375. * @param {?string} source
  376. * @param {boolean} encoded
  377. * @param {string} contentType
  378. */
  379. function callback(source, encoded, contentType)
  380. {
  381. this._scriptSource = source;
  382. this._update();
  383. }
  384. },
  385. dispose: function()
  386. {
  387. this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
  388. this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
  389. },
  390. __proto__: WebInspector.Object.prototype
  391. }