SourceCodeLocation.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. /*
  2. * Copyright (C) 2013 Apple 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
  6. * are met:
  7. * 1. Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * 2. Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. *
  13. * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
  14. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  15. * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  16. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
  17. * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  18. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  19. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  20. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  21. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  22. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  23. * THE POSSIBILITY OF SUCH DAMAGE.
  24. */
  25. WebInspector.SourceCodeLocation = function(sourceCode, lineNumber, columnNumber)
  26. {
  27. WebInspector.Object.call(this);
  28. console.assert(sourceCode === null || sourceCode instanceof WebInspector.SourceCode);
  29. console.assert(!(sourceCode instanceof WebInspector.SourceMapResource));
  30. console.assert(typeof lineNumber === "number" && !isNaN(lineNumber) && lineNumber >= 0);
  31. console.assert(typeof columnNumber === "number" && !isNaN(columnNumber) && columnNumber >= 0);
  32. this._sourceCode = sourceCode || null;
  33. this._lineNumber = lineNumber;
  34. this._columnNumber = columnNumber;
  35. this._resolveFormattedLocation();
  36. if (this._sourceCode) {
  37. this._sourceCode.addEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
  38. this._sourceCode.addEventListener(WebInspector.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this);
  39. }
  40. this._resetMappedLocation();
  41. };
  42. WebInspector.SourceCodeLocation.DisplayLocationClassName = "display-location";
  43. WebInspector.SourceCodeLocation.LargeColumnNumber = 80;
  44. WebInspector.SourceCodeLocation.ColumnStyle = {
  45. Hidden: "hidden", // column numbers are not included.
  46. OnlyIfLarge: "only-if-large", // column numbers greater than 80 are shown.
  47. Shown: "shown" // non-zero column numbers are shown.
  48. }
  49. WebInspector.SourceCodeLocation.Event = {
  50. LocationChanged: "source-code-location-location-changed",
  51. DisplayLocationChanged: "source-code-location-display-location-changed"
  52. };
  53. WebInspector.SourceCodeLocation.prototype = {
  54. constructor: WebInspector.SourceCodeLocation,
  55. // Public
  56. isEqual: function(other)
  57. {
  58. if (!other)
  59. return false;
  60. return this._sourceCode === other._sourceCode && this._lineNumber === other._lineNumber && this._columnNumber === other._columnNumber;
  61. },
  62. get sourceCode()
  63. {
  64. return this._sourceCode;
  65. },
  66. set sourceCode(sourceCode)
  67. {
  68. console.assert((this._sourceCode === null && sourceCode instanceof WebInspector.SourceCode) || (this._sourceCode instanceof WebInspector.SourceCode && sourceCode === null));
  69. if (sourceCode === this._sourceCode)
  70. return;
  71. this._makeChangeAndDispatchChangeEventIfNeeded(function() {
  72. if (this._sourceCode) {
  73. this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
  74. this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this);
  75. }
  76. this._sourceCode = sourceCode;
  77. if (this._sourceCode) {
  78. this._sourceCode.addEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
  79. this._sourceCode.addEventListener(WebInspector.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this);
  80. }
  81. });
  82. },
  83. // Raw line and column in the original source code.
  84. get lineNumber()
  85. {
  86. return this._lineNumber;
  87. },
  88. get columnNumber()
  89. {
  90. return this._columnNumber;
  91. },
  92. position: function()
  93. {
  94. return new WebInspector.SourceCodePosition(this.lineNumber, this.columnNumber);
  95. },
  96. // Formatted line and column if the original source code is pretty printed.
  97. // This is the same as the raw location if there is no formatter.
  98. get formattedLineNumber()
  99. {
  100. return this._formattedLineNumber;
  101. },
  102. get formattedColumnNumber()
  103. {
  104. return this._formattedColumnNumber;
  105. },
  106. formattedPosition: function()
  107. {
  108. return new WebInspector.SourceCodePosition(this.formattedLineNumber, this.formattedColumnNumber);
  109. },
  110. // Display line and column:
  111. // - Mapped line and column if the original source code has a source map.
  112. // - Otherwise this is the formatted / raw line and column.
  113. get displaySourceCode()
  114. {
  115. this._resolveMappedLocation();
  116. return this._mappedResource || this._sourceCode;
  117. },
  118. get displayLineNumber()
  119. {
  120. this._resolveMappedLocation();
  121. return isNaN(this._mappedLineNumber) ? this._formattedLineNumber : this._mappedLineNumber;
  122. },
  123. get displayColumnNumber()
  124. {
  125. this._resolveMappedLocation();
  126. return isNaN(this._mappedColumnNumber) ? this._formattedColumnNumber : this._mappedColumnNumber;
  127. },
  128. displayPosition: function()
  129. {
  130. return new WebInspector.SourceCodePosition(this.displayLineNumber, this.displayColumnNumber);
  131. },
  132. // User presentable location strings: "file:lineNumber:columnNumber".
  133. originalLocationString: function(columnStyle, fullURL)
  134. {
  135. return this._locationString(this.sourceCode, this.lineNumber, this.columnNumber, columnStyle, fullURL);
  136. },
  137. formattedLocationString: function(columnStyle, fullURL)
  138. {
  139. return this._locationString(this.sourceCode, this.formattedLineNumber, this.formattedColumn, columnStyle, fullURL);
  140. },
  141. displayLocationString: function(columnStyle, fullURL)
  142. {
  143. return this._locationString(this.displaySourceCode, this.displayLineNumber, this.displayColumnNumber, columnStyle, fullURL);
  144. },
  145. tooltipString: function()
  146. {
  147. if (!this.hasDifferentDisplayLocation())
  148. return this.originalLocationString(WebInspector.SourceCodeLocation.ColumnStyle.Shown, true);
  149. var tooltip = WebInspector.UIString("Located at %s").format(this.displayLocationString(WebInspector.SourceCodeLocation.ColumnStyle.Shown, true));
  150. tooltip += "\n" + WebInspector.UIString("Originally %s").format(this.originalLocationString(WebInspector.SourceCodeLocation.ColumnStyle.Shown, true));
  151. return tooltip;
  152. },
  153. hasMappedLocation: function()
  154. {
  155. this._resolveMappedLocation();
  156. return this._mappedResource !== null;
  157. },
  158. hasFormattedLocation: function()
  159. {
  160. return this._formattedLineNumber !== this._lineNumber || this._formattedColumnNumber !== this._columnNumber;
  161. },
  162. hasDifferentDisplayLocation: function()
  163. {
  164. return this.hasMappedLocation() || this.hasFormattedLocation();
  165. },
  166. update: function(sourceCode, lineNumber, columnNumber)
  167. {
  168. console.assert(sourceCode === this._sourceCode || (this._mappedResource && sourceCode === this._mappedResource));
  169. console.assert(typeof lineNumber === "number" && !isNaN(lineNumber) && lineNumber >= 0);
  170. console.assert(typeof columnNumber === "number" && !isNaN(columnNumber) && columnNumber >= 0);
  171. if (sourceCode === this._sourceCode && lineNumber === this._lineNumber && columnNumber === this._columnNumber)
  172. return;
  173. else if (this._mappedResource && sourceCode === this._mappedResource && lineNumber === this._mappedLineNumber && columnNumber === this._mappedColumnNumber)
  174. return;
  175. var newSourceCodeLocation = sourceCode.createSourceCodeLocation(lineNumber, columnNumber);
  176. console.assert(newSourceCodeLocation.sourceCode === this._sourceCode);
  177. this._makeChangeAndDispatchChangeEventIfNeeded(function() {
  178. this._lineNumber = newSourceCodeLocation._lineNumber;
  179. this._columnNumber = newSourceCodeLocation._columnNumber;
  180. if (newSourceCodeLocation._mappedLocationIsResolved) {
  181. this._mappedLocationIsResolved = true;
  182. this._mappedResource = newSourceCodeLocation._mappedResource;
  183. this._mappedLineNumber = newSourceCodeLocation._mappedLineNumber;
  184. this._mappedColumnNumber = newSourceCodeLocation._mappedColumnNumber;
  185. }
  186. });
  187. },
  188. populateLiveDisplayLocationTooltip: function(element, prefix)
  189. {
  190. prefix = prefix || "";
  191. element.title = prefix + this.tooltipString();
  192. this.addEventListener(WebInspector.SourceCodeLocation.Event.DisplayLocationChanged, function(event) {
  193. element.title = prefix + this.tooltipString();
  194. }, this);
  195. },
  196. populateLiveDisplayLocationString: function(element, propertyName)
  197. {
  198. var currentDisplay = undefined;
  199. function updateDisplayString(showAlternativeLocation, forceUpdate)
  200. {
  201. if (!forceUpdate && currentDisplay === showAlternativeLocation)
  202. return;
  203. currentDisplay = showAlternativeLocation;
  204. if (!showAlternativeLocation) {
  205. element[propertyName] = this.displayLocationString();
  206. if (this.hasDifferentDisplayLocation())
  207. element.classList.add(WebInspector.SourceCodeLocation.DisplayLocationClassName);
  208. else
  209. element.classList.remove(WebInspector.SourceCodeLocation.DisplayLocationClassName);
  210. } else {
  211. if (this.hasDifferentDisplayLocation()) {
  212. element[propertyName] = this.originalLocationString();
  213. element.classList.remove(WebInspector.SourceCodeLocation.DisplayLocationClassName);
  214. }
  215. }
  216. }
  217. function mouseOverOrMove(event)
  218. {
  219. updateDisplayString.call(this, event.metaKey && !event.altKey && !event.shiftKey);
  220. }
  221. updateDisplayString.call(this, false);
  222. this.addEventListener(WebInspector.SourceCodeLocation.Event.DisplayLocationChanged, function(event) {
  223. updateDisplayString.call(this, currentDisplay, true);
  224. }, this);
  225. var boundMouseOverOrMove = mouseOverOrMove.bind(this);
  226. element.addEventListener("mouseover", boundMouseOverOrMove);
  227. element.addEventListener("mousemove", boundMouseOverOrMove);
  228. element.addEventListener("mouseout", function(event) {
  229. updateDisplayString.call(this, false);
  230. }.bind(this));
  231. },
  232. // Private
  233. _locationString: function(sourceCode, lineNumber, columnNumber, columnStyle, fullURL)
  234. {
  235. console.assert(sourceCode);
  236. if (!sourceCode)
  237. return "";
  238. columnStyle = columnStyle || WebInspector.SourceCodeLocation.ColumnStyle.OnlyIfLarge;
  239. var string = fullURL && sourceCode.url ? sourceCode.url : sourceCode.displayName;
  240. if (sourceCode.url) {
  241. string += ":" + (lineNumber + 1); // The user visible line number is 1-based.
  242. if (columnStyle === WebInspector.SourceCodeLocation.ColumnStyle.Shown && columnNumber > 0)
  243. string += ":" + (columnNumber + 1); // The user visible column number is 1-based.
  244. else if (columnStyle === WebInspector.SourceCodeLocation.ColumnStyle.OnlyIfLarge && columnNumber > WebInspector.SourceCodeLocation.LargeColumnNumber)
  245. string += ":" + (columnNumber + 1); // The user visible column number is 1-based.
  246. } else
  247. string += WebInspector.UIString(" (line %d)").format(lineNumber + 1); // The user visible line number is 1-based.
  248. return string;
  249. },
  250. _resetMappedLocation: function()
  251. {
  252. this._mappedLocationIsResolved = false;
  253. this._mappedResource = null;
  254. this._mappedLineNumber = NaN;
  255. this._mappedColumnNumber = NaN;
  256. },
  257. _setMappedLocation: function(mappedResource, mappedLineNumber, mappedColumnNumber)
  258. {
  259. // Called by SourceMapResource when it creates a SourceCodeLocation and already knows the resolved location.
  260. this._mappedLocationIsResolved = true;
  261. this._mappedResource = mappedResource;
  262. this._mappedLineNumber = mappedLineNumber;
  263. this._mappedColumnNumber = mappedColumnNumber;
  264. },
  265. _resolveMappedLocation: function()
  266. {
  267. if (this._mappedLocationIsResolved)
  268. return;
  269. console.assert(this._mappedResource === null);
  270. console.assert(isNaN(this._mappedLineNumber));
  271. console.assert(isNaN(this._mappedColumnNumber));
  272. this._mappedLocationIsResolved = true;
  273. if (!this._sourceCode)
  274. return;
  275. var sourceMaps = this._sourceCode.sourceMaps;
  276. if (!sourceMaps.length)
  277. return;
  278. for (var i = 0; i < sourceMaps.length; ++i) {
  279. var sourceMap = sourceMaps[i];
  280. var entry = sourceMap.findEntry(this._lineNumber, this._columnNumber);
  281. if (!entry || entry.length === 2)
  282. continue;
  283. console.assert(entry.length === 5);
  284. var url = entry[2];
  285. var sourceMapResource = sourceMap.resourceForURL(url);
  286. if (!sourceMapResource)
  287. return;
  288. this._mappedResource = sourceMapResource;
  289. this._mappedLineNumber = entry[3];
  290. this._mappedColumnNumber = entry[4];
  291. return;
  292. }
  293. },
  294. _resolveFormattedLocation: function()
  295. {
  296. if (this._sourceCode && this._sourceCode.formatterSourceMap) {
  297. var formattedLocation = this._sourceCode.formatterSourceMap.originalToFormatted(this._lineNumber, this._columnNumber);
  298. this._formattedLineNumber = formattedLocation.lineNumber;
  299. this._formattedColumnNumber = formattedLocation.columnNumber;
  300. } else {
  301. this._formattedLineNumber = this._lineNumber;
  302. this._formattedColumnNumber = this._columnNumber;
  303. }
  304. },
  305. _makeChangeAndDispatchChangeEventIfNeeded: function(changeFunction)
  306. {
  307. var oldSourceCode = this._sourceCode;
  308. var oldLineNumber = this._lineNumber;
  309. var oldColumnNumber = this._columnNumber;
  310. var oldFormattedLineNumber = this._formattedLineNumber;
  311. var oldFormattedColumnNumber = this._formattedColumnNumber;
  312. var oldDisplaySourceCode = this.displaySourceCode;
  313. var oldDisplayLineNumber = this.displayLineNumber;
  314. var oldDisplayColumnNumber = this.displayColumnNumber;
  315. this._resetMappedLocation();
  316. if (changeFunction)
  317. changeFunction.call(this);
  318. this._resolveMappedLocation();
  319. this._resolveFormattedLocation();
  320. // If the display source code is non-null then the addresses are not NaN and can be compared.
  321. var displayLocationChanged = false;
  322. var newDisplaySourceCode = this.displaySourceCode;
  323. if (oldDisplaySourceCode !== newDisplaySourceCode)
  324. displayLocationChanged = true;
  325. else if (newDisplaySourceCode && (oldDisplayLineNumber !== this.displayLineNumber || oldDisplayColumnNumber !== this.displayColumnNumber))
  326. displayLocationChanged = true;
  327. var anyLocationChanged = false;
  328. if (displayLocationChanged)
  329. anyLocationChanged = true;
  330. else if (oldSourceCode !== this._sourceCode)
  331. anyLocationChanged = true;
  332. else if (this._sourceCode && (oldLineNumber !== this._lineNumber || oldColumnNumber !== this._columnNumber))
  333. anyLocationChanged = true;
  334. else if (this._sourceCode && (oldFormattedLineNumber !== this._formattedLineNumber || oldFormattedColumnNumber !== this._formattedColumnNumber))
  335. anyLocationChanged = true;
  336. if (displayLocationChanged || anyLocationChanged) {
  337. var oldData = {
  338. oldSourceCode: oldSourceCode,
  339. oldLineNumber: oldLineNumber,
  340. oldColumnNumber: oldColumnNumber,
  341. oldFormattedLineNumber: oldFormattedLineNumber,
  342. oldFormattedColumnNumber: oldFormattedColumnNumber,
  343. oldDisplaySourceCode: oldDisplaySourceCode,
  344. oldDisplayLineNumber: oldDisplayLineNumber,
  345. oldDisplayColumnNumber: oldDisplayColumnNumber
  346. };
  347. if (displayLocationChanged)
  348. this.dispatchEventToListeners(WebInspector.SourceCodeLocation.Event.DisplayLocationChanged, oldData);
  349. if (anyLocationChanged)
  350. this.dispatchEventToListeners(WebInspector.SourceCodeLocation.Event.LocationChanged, oldData);
  351. }
  352. },
  353. _sourceCodeSourceMapAdded: function()
  354. {
  355. this._makeChangeAndDispatchChangeEventIfNeeded(null);
  356. },
  357. _sourceCodeFormatterDidChange: function()
  358. {
  359. this._makeChangeAndDispatchChangeEventIfNeeded(null);
  360. }
  361. };
  362. WebInspector.SourceCodeLocation.prototype.__proto__ = WebInspector.Object.prototype;