UISourceCode.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963
  1. /*
  2. * Copyright (C) 2011 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. * @extends {WebInspector.Object}
  33. * @implements {WebInspector.ContentProvider}
  34. * @param {WebInspector.Project} project
  35. * @param {Array.<string>} path
  36. * @param {string} url
  37. * @param {WebInspector.ResourceType} contentType
  38. * @param {boolean} isEditable
  39. */
  40. WebInspector.UISourceCode = function(project, path, originURL, url, contentType, isEditable)
  41. {
  42. this._project = project;
  43. this._path = path;
  44. this._originURL = originURL;
  45. this._url = url;
  46. this._contentType = contentType;
  47. this._isEditable = isEditable;
  48. /**
  49. * @type Array.<function(?string,boolean,string)>
  50. */
  51. this._requestContentCallbacks = [];
  52. this._liveLocations = new Set();
  53. /**
  54. * @type {Array.<WebInspector.PresentationConsoleMessage>}
  55. */
  56. this._consoleMessages = [];
  57. /**
  58. * @type {Array.<WebInspector.Revision>}
  59. */
  60. this.history = [];
  61. if (this.isEditable() && this._url)
  62. this._restoreRevisionHistory();
  63. this._formatterMapping = new WebInspector.IdentityFormatterSourceMapping();
  64. }
  65. WebInspector.UISourceCode.Events = {
  66. FormattedChanged: "FormattedChanged",
  67. WorkingCopyChanged: "WorkingCopyChanged",
  68. WorkingCopyCommitted: "WorkingCopyCommitted",
  69. TitleChanged: "TitleChanged",
  70. ConsoleMessageAdded: "ConsoleMessageAdded",
  71. ConsoleMessageRemoved: "ConsoleMessageRemoved",
  72. ConsoleMessagesCleared: "ConsoleMessagesCleared",
  73. SourceMappingChanged: "SourceMappingChanged",
  74. }
  75. WebInspector.UISourceCode.prototype = {
  76. /**
  77. * @return {string}
  78. */
  79. get url()
  80. {
  81. return this._url;
  82. },
  83. /**
  84. * @return {Array.<string>}
  85. */
  86. path: function()
  87. {
  88. return this._path;
  89. },
  90. /**
  91. * @return {string}
  92. */
  93. name: function()
  94. {
  95. return this._path[this._path.length - 1];
  96. },
  97. /**
  98. * @return {string}
  99. */
  100. displayName: function()
  101. {
  102. var displayName = this.name() || (this._project.displayName() + "/" + this._path.join("/"));
  103. return displayName.trimEnd(100);
  104. },
  105. /**
  106. * @return {string}
  107. */
  108. uri: function()
  109. {
  110. if (!this._project.id())
  111. return this._path.join("/");
  112. if (!this._path.length)
  113. return this._project.id();
  114. return this._project.id() + "/" + this._path.join("/");
  115. },
  116. /**
  117. * @return {string}
  118. */
  119. originURL: function()
  120. {
  121. return this._originURL;
  122. },
  123. /**
  124. * @param {string} newName
  125. */
  126. rename: function(newName)
  127. {
  128. if (!this._path.length)
  129. return;
  130. this._path[this._path.length - 1] = newName;
  131. this._url = newName;
  132. this._originURL = newName;
  133. this.dispatchEventToListeners(WebInspector.UISourceCode.Events.TitleChanged, null);
  134. },
  135. /**
  136. * @return {string}
  137. */
  138. contentURL: function()
  139. {
  140. return this.originURL();
  141. },
  142. /**
  143. * @return {WebInspector.ResourceType}
  144. */
  145. contentType: function()
  146. {
  147. return this._contentType;
  148. },
  149. /**
  150. * @return {WebInspector.ScriptFile}
  151. */
  152. scriptFile: function()
  153. {
  154. return this._scriptFile;
  155. },
  156. /**
  157. * @param {WebInspector.ScriptFile} scriptFile
  158. */
  159. setScriptFile: function(scriptFile)
  160. {
  161. this._scriptFile = scriptFile;
  162. },
  163. /**
  164. * @return {WebInspector.StyleFile}
  165. */
  166. styleFile: function()
  167. {
  168. return this._styleFile;
  169. },
  170. /**
  171. * @param {WebInspector.StyleFile} styleFile
  172. */
  173. setStyleFile: function(styleFile)
  174. {
  175. this._styleFile = styleFile;
  176. },
  177. /**
  178. * @return {WebInspector.Project}
  179. */
  180. project: function()
  181. {
  182. return this._project;
  183. },
  184. /**
  185. * @param {function(?string,boolean,string)} callback
  186. */
  187. requestContent: function(callback)
  188. {
  189. if (this._content || this._contentLoaded) {
  190. callback(this._content, false, this._mimeType);
  191. return;
  192. }
  193. this._requestContentCallbacks.push(callback);
  194. if (this._requestContentCallbacks.length === 1)
  195. this._project.requestFileContent(this, this._fireContentAvailable.bind(this));
  196. },
  197. checkContentUpdated: function()
  198. {
  199. if (!this._project.canSetFileContent())
  200. return;
  201. if (this._checkingContent)
  202. return;
  203. this._checkingContent = true;
  204. this._project.requestFileContent(this, contentLoaded.bind(this));
  205. function contentLoaded(updatedContent)
  206. {
  207. if (updatedContent === null) {
  208. var workingCopy = this.workingCopy();
  209. this._commitContent("", false);
  210. this.setWorkingCopy(workingCopy);
  211. delete this._checkingContent;
  212. return;
  213. }
  214. if (typeof this._lastAcceptedContent === "string" && this._lastAcceptedContent === updatedContent) {
  215. delete this._checkingContent;
  216. return;
  217. }
  218. if (this._content === updatedContent) {
  219. delete this._lastAcceptedContent;
  220. delete this._checkingContent;
  221. return;
  222. }
  223. if (!this.isDirty()) {
  224. this._commitContent(updatedContent, false);
  225. delete this._checkingContent;
  226. return;
  227. }
  228. var shouldUpdate = window.confirm(WebInspector.UIString("This file was changed externally. Would you like to reload it?"));
  229. if (shouldUpdate)
  230. this._commitContent(updatedContent, false);
  231. else
  232. this._lastAcceptedContent = updatedContent;
  233. delete this._checkingContent;
  234. }
  235. },
  236. /**
  237. * @param {function(?string,boolean,string)} callback
  238. */
  239. requestOriginalContent: function(callback)
  240. {
  241. this._project.requestFileContent(this, callback);
  242. },
  243. /**
  244. * @param {string} content
  245. * @param {boolean} shouldSetContentInProject
  246. */
  247. _commitContent: function(content, shouldSetContentInProject)
  248. {
  249. delete this._lastAcceptedContent;
  250. this._content = content;
  251. this._contentLoaded = true;
  252. var lastRevision = this.history.length ? this.history[this.history.length - 1] : null;
  253. if (!lastRevision || lastRevision._content !== this._content) {
  254. var revision = new WebInspector.Revision(this, this._content, new Date());
  255. this.history.push(revision);
  256. revision._persist();
  257. }
  258. var oldWorkingCopy = this._workingCopy;
  259. delete this._workingCopy;
  260. this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyCommitted, {oldWorkingCopy: oldWorkingCopy, workingCopy: this.workingCopy()});
  261. if (this._url && WebInspector.fileManager.isURLSaved(this._url)) {
  262. WebInspector.fileManager.save(this._url, this._content, false);
  263. WebInspector.fileManager.close(this._url);
  264. }
  265. if (shouldSetContentInProject)
  266. this._project.setFileContent(this, this._content, function() { });
  267. },
  268. /**
  269. * @param {string} content
  270. */
  271. addRevision: function(content)
  272. {
  273. this._commitContent(content, true);
  274. },
  275. _restoreRevisionHistory: function()
  276. {
  277. if (!window.localStorage)
  278. return;
  279. var registry = WebInspector.Revision._revisionHistoryRegistry();
  280. var historyItems = registry[this.url];
  281. if (!historyItems || !historyItems.length)
  282. return;
  283. for (var i = 0; i < historyItems.length; ++i) {
  284. var content = window.localStorage[historyItems[i].key];
  285. var timestamp = new Date(historyItems[i].timestamp);
  286. var revision = new WebInspector.Revision(this, content, timestamp);
  287. this.history.push(revision);
  288. }
  289. this._content = this.history[this.history.length - 1].content;
  290. this._contentLoaded = true;
  291. this._mimeType = this.canonicalMimeType();
  292. },
  293. _clearRevisionHistory: function()
  294. {
  295. if (!window.localStorage)
  296. return;
  297. var registry = WebInspector.Revision._revisionHistoryRegistry();
  298. var historyItems = registry[this.url];
  299. for (var i = 0; historyItems && i < historyItems.length; ++i)
  300. delete window.localStorage[historyItems[i].key];
  301. delete registry[this.url];
  302. window.localStorage["revision-history"] = JSON.stringify(registry);
  303. },
  304. revertToOriginal: function()
  305. {
  306. /**
  307. * @this {WebInspector.UISourceCode}
  308. * @param {?string} content
  309. * @param {boolean} contentEncoded
  310. * @param {string} mimeType
  311. */
  312. function callback(content, contentEncoded, mimeType)
  313. {
  314. if (typeof content !== "string")
  315. return;
  316. this.addRevision(content);
  317. }
  318. this.requestOriginalContent(callback.bind(this));
  319. WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
  320. action: WebInspector.UserMetrics.UserActionNames.ApplyOriginalContent,
  321. url: this.url
  322. });
  323. },
  324. /**
  325. * @param {function(WebInspector.UISourceCode)} callback
  326. */
  327. revertAndClearHistory: function(callback)
  328. {
  329. /**
  330. * @this {WebInspector.UISourceCode}
  331. * @param {?string} content
  332. * @param {boolean} contentEncoded
  333. * @param {string} mimeType
  334. */
  335. function revert(content, contentEncoded, mimeType)
  336. {
  337. if (typeof content !== "string")
  338. return;
  339. this.addRevision(content);
  340. this._clearRevisionHistory();
  341. this.history = [];
  342. callback(this);
  343. }
  344. this.requestOriginalContent(revert.bind(this));
  345. WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
  346. action: WebInspector.UserMetrics.UserActionNames.RevertRevision,
  347. url: this.url
  348. });
  349. },
  350. /**
  351. * @return {boolean}
  352. */
  353. isEditable: function()
  354. {
  355. return this._isEditable;
  356. },
  357. /**
  358. * @return {string}
  359. */
  360. workingCopy: function()
  361. {
  362. if (this.isDirty())
  363. return this._workingCopy;
  364. return this._content;
  365. },
  366. resetWorkingCopy: function()
  367. {
  368. this.setWorkingCopy(this._content);
  369. },
  370. /**
  371. * @param {string} newWorkingCopy
  372. */
  373. setWorkingCopy: function(newWorkingCopy)
  374. {
  375. var wasDirty = this.isDirty();
  376. this._mimeType = this.canonicalMimeType();
  377. var oldWorkingCopy = this._workingCopy;
  378. if (this._content === newWorkingCopy)
  379. delete this._workingCopy;
  380. else
  381. this._workingCopy = newWorkingCopy;
  382. this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyChanged, {oldWorkingCopy: oldWorkingCopy, workingCopy: this.workingCopy(), wasDirty: wasDirty});
  383. },
  384. /**
  385. * @param {function(?string)} callback
  386. */
  387. commitWorkingCopy: function(callback)
  388. {
  389. if (!this.isDirty()) {
  390. callback(null);
  391. return;
  392. }
  393. this._commitContent(this._workingCopy, true);
  394. callback(null);
  395. WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
  396. action: WebInspector.UserMetrics.UserActionNames.FileSaved,
  397. url: this.url
  398. });
  399. },
  400. /**
  401. * @return {boolean}
  402. */
  403. isDirty: function()
  404. {
  405. return typeof this._workingCopy !== "undefined" && this._workingCopy !== this._content;
  406. },
  407. /**
  408. * @return {string}
  409. */
  410. mimeType: function()
  411. {
  412. return this._mimeType;
  413. },
  414. /**
  415. * @return {string}
  416. */
  417. canonicalMimeType: function()
  418. {
  419. return this.contentType().canonicalMimeType() || this._mimeType;
  420. },
  421. /**
  422. * @return {?string}
  423. */
  424. content: function()
  425. {
  426. return this._content;
  427. },
  428. /**
  429. * @param {string} query
  430. * @param {boolean} caseSensitive
  431. * @param {boolean} isRegex
  432. * @param {function(Array.<WebInspector.ContentProvider.SearchMatch>)} callback
  433. */
  434. searchInContent: function(query, caseSensitive, isRegex, callback)
  435. {
  436. var content = this.content();
  437. if (content) {
  438. var provider = new WebInspector.StaticContentProvider(this.contentType(), content);
  439. provider.searchInContent(query, caseSensitive, isRegex, callback);
  440. return;
  441. }
  442. this._project.searchInFileContent(this, query, caseSensitive, isRegex, callback);
  443. },
  444. /**
  445. * @param {?string} content
  446. * @param {boolean} contentEncoded
  447. * @param {string} mimeType
  448. */
  449. _fireContentAvailable: function(content, contentEncoded, mimeType)
  450. {
  451. this._contentLoaded = true;
  452. this._mimeType = mimeType;
  453. this._content = content;
  454. var callbacks = this._requestContentCallbacks.slice();
  455. this._requestContentCallbacks = [];
  456. for (var i = 0; i < callbacks.length; ++i)
  457. callbacks[i](content, contentEncoded, mimeType);
  458. if (this._formatOnLoad) {
  459. delete this._formatOnLoad;
  460. this.setFormatted(true);
  461. }
  462. },
  463. /**
  464. * @return {boolean}
  465. */
  466. contentLoaded: function()
  467. {
  468. return this._contentLoaded;
  469. },
  470. /**
  471. * @param {number} lineNumber
  472. * @param {number} columnNumber
  473. * @return {WebInspector.RawLocation}
  474. */
  475. uiLocationToRawLocation: function(lineNumber, columnNumber)
  476. {
  477. if (!this._sourceMapping)
  478. return null;
  479. var location = this._formatterMapping.formattedToOriginal(lineNumber, columnNumber);
  480. return this._sourceMapping.uiLocationToRawLocation(this, location[0], location[1]);
  481. },
  482. /**
  483. * @param {!WebInspector.LiveLocation} liveLocation
  484. */
  485. addLiveLocation: function(liveLocation)
  486. {
  487. this._liveLocations.add(liveLocation);
  488. },
  489. /**
  490. * @param {!WebInspector.LiveLocation} liveLocation
  491. */
  492. removeLiveLocation: function(liveLocation)
  493. {
  494. this._liveLocations.remove(liveLocation);
  495. },
  496. updateLiveLocations: function()
  497. {
  498. var items = this._liveLocations.items();
  499. for (var i = 0; i < items.length; ++i)
  500. items[i].update();
  501. },
  502. /**
  503. * @param {WebInspector.UILocation} uiLocation
  504. */
  505. overrideLocation: function(uiLocation)
  506. {
  507. var location = this._formatterMapping.originalToFormatted(uiLocation.lineNumber, uiLocation.columnNumber);
  508. uiLocation.lineNumber = location[0];
  509. uiLocation.columnNumber = location[1];
  510. return uiLocation;
  511. },
  512. /**
  513. * @return {Array.<WebInspector.PresentationConsoleMessage>}
  514. */
  515. consoleMessages: function()
  516. {
  517. return this._consoleMessages;
  518. },
  519. /**
  520. * @param {WebInspector.PresentationConsoleMessage} message
  521. */
  522. consoleMessageAdded: function(message)
  523. {
  524. this._consoleMessages.push(message);
  525. this.dispatchEventToListeners(WebInspector.UISourceCode.Events.ConsoleMessageAdded, message);
  526. },
  527. /**
  528. * @param {WebInspector.PresentationConsoleMessage} message
  529. */
  530. consoleMessageRemoved: function(message)
  531. {
  532. this._consoleMessages.remove(message);
  533. this.dispatchEventToListeners(WebInspector.UISourceCode.Events.ConsoleMessageRemoved, message);
  534. },
  535. consoleMessagesCleared: function()
  536. {
  537. this._consoleMessages = [];
  538. this.dispatchEventToListeners(WebInspector.UISourceCode.Events.ConsoleMessagesCleared);
  539. },
  540. /**
  541. * @return {boolean}
  542. */
  543. formatted: function()
  544. {
  545. return !!this._formatted;
  546. },
  547. /**
  548. * @param {boolean} formatted
  549. */
  550. setFormatted: function(formatted)
  551. {
  552. if (!this.contentLoaded()) {
  553. this._formatOnLoad = formatted;
  554. return;
  555. }
  556. if (this._formatted === formatted)
  557. return;
  558. this._formatted = formatted;
  559. // Re-request content
  560. this._contentLoaded = false;
  561. this._content = false;
  562. WebInspector.UISourceCode.prototype.requestContent.call(this, didGetContent.bind(this));
  563. /**
  564. * @this {WebInspector.UISourceCode}
  565. * @param {?string} content
  566. * @param {boolean} contentEncoded
  567. * @param {string} mimeType
  568. */
  569. function didGetContent(content, contentEncoded, mimeType)
  570. {
  571. var formatter;
  572. if (!formatted)
  573. formatter = new WebInspector.IdentityFormatter();
  574. else
  575. formatter = WebInspector.Formatter.createFormatter(this.contentType());
  576. formatter.formatContent(mimeType, content || "", formattedChanged.bind(this));
  577. /**
  578. * @this {WebInspector.UISourceCode}
  579. * @param {string} content
  580. * @param {WebInspector.FormatterSourceMapping} formatterMapping
  581. */
  582. function formattedChanged(content, formatterMapping)
  583. {
  584. this._content = content;
  585. delete this._workingCopy;
  586. this._formatterMapping = formatterMapping;
  587. this.dispatchEventToListeners(WebInspector.UISourceCode.Events.FormattedChanged, {content: content});
  588. this.updateLiveLocations();
  589. }
  590. }
  591. },
  592. /**
  593. * @return {WebInspector.Formatter} formatter
  594. */
  595. createFormatter: function()
  596. {
  597. // overridden by subclasses.
  598. return null;
  599. },
  600. /**
  601. * @param {WebInspector.SourceMapping} sourceMapping
  602. */
  603. setSourceMapping: function(sourceMapping)
  604. {
  605. var wasIdentity = this._sourceMapping ? this._sourceMapping.isIdentity() : true;
  606. this._sourceMapping = sourceMapping;
  607. var data = {}
  608. data.isIdentity = sourceMapping ? sourceMapping.isIdentity() : true;
  609. data.identityHasChanged = data.isIdentity !== wasIdentity;
  610. this.dispatchEventToListeners(WebInspector.UISourceCode.Events.SourceMappingChanged, data);
  611. },
  612. __proto__: WebInspector.Object.prototype
  613. }
  614. /**
  615. * @interface
  616. * @extends {WebInspector.EventTarget}
  617. */
  618. WebInspector.UISourceCodeProvider = function()
  619. {
  620. }
  621. WebInspector.UISourceCodeProvider.Events = {
  622. UISourceCodeAdded: "UISourceCodeAdded",
  623. UISourceCodeRemoved: "UISourceCodeRemoved"
  624. }
  625. WebInspector.UISourceCodeProvider.prototype = {
  626. /**
  627. * @return {Array.<WebInspector.UISourceCode>}
  628. */
  629. uiSourceCodes: function() {},
  630. }
  631. /**
  632. * @constructor
  633. * @param {WebInspector.UISourceCode} uiSourceCode
  634. * @param {number} lineNumber
  635. * @param {number} columnNumber
  636. */
  637. WebInspector.UILocation = function(uiSourceCode, lineNumber, columnNumber)
  638. {
  639. this.uiSourceCode = uiSourceCode;
  640. this.lineNumber = lineNumber;
  641. this.columnNumber = columnNumber;
  642. }
  643. WebInspector.UILocation.prototype = {
  644. /**
  645. * @return {WebInspector.RawLocation}
  646. */
  647. uiLocationToRawLocation: function()
  648. {
  649. return this.uiSourceCode.uiLocationToRawLocation(this.lineNumber, this.columnNumber);
  650. },
  651. /**
  652. * @return {?string}
  653. */
  654. url: function()
  655. {
  656. return this.uiSourceCode.contentURL();
  657. }
  658. }
  659. /**
  660. * @interface
  661. */
  662. WebInspector.RawLocation = function()
  663. {
  664. }
  665. /**
  666. * @constructor
  667. * @param {WebInspector.RawLocation} rawLocation
  668. * @param {function(WebInspector.UILocation):(boolean|undefined)} updateDelegate
  669. */
  670. WebInspector.LiveLocation = function(rawLocation, updateDelegate)
  671. {
  672. this._rawLocation = rawLocation;
  673. this._updateDelegate = updateDelegate;
  674. this._uiSourceCodes = [];
  675. }
  676. WebInspector.LiveLocation.prototype = {
  677. update: function()
  678. {
  679. var uiLocation = this.uiLocation();
  680. if (uiLocation) {
  681. var uiSourceCode = uiLocation.uiSourceCode;
  682. if (this._uiSourceCodes.indexOf(uiSourceCode) === -1) {
  683. uiSourceCode.addLiveLocation(this);
  684. this._uiSourceCodes.push(uiSourceCode);
  685. }
  686. var oneTime = this._updateDelegate(uiLocation);
  687. if (oneTime)
  688. this.dispose();
  689. }
  690. },
  691. /**
  692. * @return {WebInspector.RawLocation}
  693. */
  694. rawLocation: function()
  695. {
  696. return this._rawLocation;
  697. },
  698. /**
  699. * @return {WebInspector.UILocation}
  700. */
  701. uiLocation: function()
  702. {
  703. // Should be overridden by subclasses.
  704. },
  705. dispose: function()
  706. {
  707. for (var i = 0; i < this._uiSourceCodes.length; ++i)
  708. this._uiSourceCodes[i].removeLiveLocation(this);
  709. this._uiSourceCodes = [];
  710. }
  711. }
  712. /**
  713. * @constructor
  714. * @implements {WebInspector.ContentProvider}
  715. * @param {WebInspector.UISourceCode} uiSourceCode
  716. * @param {?string|undefined} content
  717. * @param {Date} timestamp
  718. */
  719. WebInspector.Revision = function(uiSourceCode, content, timestamp)
  720. {
  721. this._uiSourceCode = uiSourceCode;
  722. this._content = content;
  723. this._timestamp = timestamp;
  724. }
  725. WebInspector.Revision._revisionHistoryRegistry = function()
  726. {
  727. if (!WebInspector.Revision._revisionHistoryRegistryObject) {
  728. if (window.localStorage) {
  729. var revisionHistory = window.localStorage["revision-history"];
  730. try {
  731. WebInspector.Revision._revisionHistoryRegistryObject = revisionHistory ? JSON.parse(revisionHistory) : {};
  732. } catch (e) {
  733. WebInspector.Revision._revisionHistoryRegistryObject = {};
  734. }
  735. } else
  736. WebInspector.Revision._revisionHistoryRegistryObject = {};
  737. }
  738. return WebInspector.Revision._revisionHistoryRegistryObject;
  739. }
  740. WebInspector.Revision.filterOutStaleRevisions = function()
  741. {
  742. if (!window.localStorage)
  743. return;
  744. var registry = WebInspector.Revision._revisionHistoryRegistry();
  745. var filteredRegistry = {};
  746. for (var url in registry) {
  747. var historyItems = registry[url];
  748. var filteredHistoryItems = [];
  749. for (var i = 0; historyItems && i < historyItems.length; ++i) {
  750. var historyItem = historyItems[i];
  751. if (historyItem.loaderId === WebInspector.resourceTreeModel.mainFrame.loaderId) {
  752. filteredHistoryItems.push(historyItem);
  753. filteredRegistry[url] = filteredHistoryItems;
  754. } else
  755. delete window.localStorage[historyItem.key];
  756. }
  757. }
  758. WebInspector.Revision._revisionHistoryRegistryObject = filteredRegistry;
  759. function persist()
  760. {
  761. window.localStorage["revision-history"] = JSON.stringify(filteredRegistry);
  762. }
  763. // Schedule async storage.
  764. setTimeout(persist, 0);
  765. }
  766. WebInspector.Revision.prototype = {
  767. /**
  768. * @return {WebInspector.UISourceCode}
  769. */
  770. get uiSourceCode()
  771. {
  772. return this._uiSourceCode;
  773. },
  774. /**
  775. * @return {Date}
  776. */
  777. get timestamp()
  778. {
  779. return this._timestamp;
  780. },
  781. /**
  782. * @return {?string}
  783. */
  784. get content()
  785. {
  786. return this._content || null;
  787. },
  788. revertToThis: function()
  789. {
  790. function revert(content)
  791. {
  792. if (this._uiSourceCode._content !== content)
  793. this._uiSourceCode.addRevision(content);
  794. }
  795. this.requestContent(revert.bind(this));
  796. },
  797. /**
  798. * @return {string}
  799. */
  800. contentURL: function()
  801. {
  802. return this._uiSourceCode.originURL();
  803. },
  804. /**
  805. * @return {WebInspector.ResourceType}
  806. */
  807. contentType: function()
  808. {
  809. return this._uiSourceCode.contentType();
  810. },
  811. /**
  812. * @param {function(?string, boolean, string)} callback
  813. */
  814. requestContent: function(callback)
  815. {
  816. callback(this._content || "", false, this.uiSourceCode.canonicalMimeType());
  817. },
  818. /**
  819. * @param {string} query
  820. * @param {boolean} caseSensitive
  821. * @param {boolean} isRegex
  822. * @param {function(Array.<WebInspector.ContentProvider.SearchMatch>)} callback
  823. */
  824. searchInContent: function(query, caseSensitive, isRegex, callback)
  825. {
  826. callback([]);
  827. },
  828. _persist: function()
  829. {
  830. if (!window.localStorage)
  831. return;
  832. var url = this.contentURL();
  833. if (!url || url.startsWith("inspector://"))
  834. return;
  835. var loaderId = WebInspector.resourceTreeModel.mainFrame.loaderId;
  836. var timestamp = this.timestamp.getTime();
  837. var key = "revision-history|" + url + "|" + loaderId + "|" + timestamp;
  838. var registry = WebInspector.Revision._revisionHistoryRegistry();
  839. var historyItems = registry[url];
  840. if (!historyItems) {
  841. historyItems = [];
  842. registry[url] = historyItems;
  843. }
  844. historyItems.push({url: url, loaderId: loaderId, timestamp: timestamp, key: key});
  845. function persist()
  846. {
  847. window.localStorage[key] = this._content;
  848. window.localStorage["revision-history"] = JSON.stringify(registry);
  849. }
  850. // Schedule async storage.
  851. setTimeout(persist.bind(this), 0);
  852. }
  853. }