StylesSourceMapping.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  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.SourceMapping}
  33. * @param {WebInspector.CSSStyleModel} cssModel
  34. * @param {WebInspector.Workspace} workspace
  35. */
  36. WebInspector.StylesSourceMapping = function(cssModel, workspace)
  37. {
  38. this._cssModel = cssModel;
  39. this._workspace = workspace;
  40. this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectWillReset, this._projectWillReset, this);
  41. this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAddedToWorkspace, this);
  42. WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameCreatedOrNavigated, this._mainFrameCreatedOrNavigated, this);
  43. this._initialize();
  44. }
  45. WebInspector.StylesSourceMapping.prototype = {
  46. /**
  47. * @param {WebInspector.RawLocation} rawLocation
  48. * @return {WebInspector.UILocation}
  49. */
  50. rawLocationToUILocation: function(rawLocation)
  51. {
  52. var location = /** @type WebInspector.CSSLocation */ (rawLocation);
  53. var uiSourceCode = this._workspace.uiSourceCodeForURL(location.url);
  54. if (!uiSourceCode)
  55. return null;
  56. return new WebInspector.UILocation(uiSourceCode, location.lineNumber, location.columnNumber);
  57. },
  58. /**
  59. * @param {WebInspector.UISourceCode} uiSourceCode
  60. * @param {number} lineNumber
  61. * @param {number} columnNumber
  62. * @return {WebInspector.RawLocation}
  63. */
  64. uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
  65. {
  66. return new WebInspector.CSSLocation(uiSourceCode.url || "", lineNumber, columnNumber);
  67. },
  68. /**
  69. * @return {boolean}
  70. */
  71. isIdentity: function()
  72. {
  73. return true;
  74. },
  75. /**
  76. * @param {WebInspector.CSSStyleSheetHeader} header
  77. */
  78. addHeader: function(header)
  79. {
  80. var url = header.resourceURL();
  81. if (!url)
  82. return;
  83. header.pushSourceMapping(this);
  84. var map = this._urlToHeadersByFrameId[url];
  85. if (!map) {
  86. map = /** @type {!StringMap.<!StringMap.<!WebInspector.CSSStyleSheetHeader>>} */ (new StringMap());
  87. this._urlToHeadersByFrameId[url] = map;
  88. }
  89. var headersById = map.get(header.frameId);
  90. if (!headersById) {
  91. headersById = /** @type {!StringMap.<!WebInspector.CSSStyleSheetHeader>} */ (new StringMap());
  92. map.put(header.frameId, headersById);
  93. }
  94. headersById.put(header.id, header);
  95. var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
  96. if (uiSourceCode)
  97. this._bindUISourceCode(uiSourceCode, header);
  98. },
  99. /**
  100. * @param {WebInspector.CSSStyleSheetHeader} header
  101. */
  102. removeHeader: function(header)
  103. {
  104. var url = header.resourceURL();
  105. if (!url)
  106. return;
  107. var map = this._urlToHeadersByFrameId[url];
  108. console.assert(map);
  109. var headersById = map.get(header.frameId);
  110. console.assert(headersById);
  111. headersById.remove(header.id);
  112. if (!headersById.size()) {
  113. map.remove(header.frameId);
  114. if (!map.size()) {
  115. delete this._urlToHeadersByFrameId[url];
  116. var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
  117. if (uiSourceCode)
  118. this._unbindUISourceCode(uiSourceCode);
  119. }
  120. }
  121. },
  122. /**
  123. * @param {WebInspector.UISourceCode} uiSourceCode
  124. */
  125. _unbindUISourceCode: function(uiSourceCode)
  126. {
  127. if (uiSourceCode.styleFile()) {
  128. uiSourceCode.styleFile().dispose();
  129. uiSourceCode.setStyleFile(null);
  130. }
  131. uiSourceCode.setSourceMapping(null);
  132. },
  133. /**
  134. * @param {WebInspector.Event} event
  135. */
  136. _uiSourceCodeAddedToWorkspace: function(event)
  137. {
  138. var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.data);
  139. var url = uiSourceCode.url;
  140. if (!url || !this._urlToHeadersByFrameId[url])
  141. return;
  142. this._bindUISourceCode(uiSourceCode, this._urlToHeadersByFrameId[url].values()[0].values()[0]);
  143. },
  144. /**
  145. * @param {WebInspector.UISourceCode} uiSourceCode
  146. * @param {WebInspector.CSSStyleSheetHeader} header
  147. */
  148. _bindUISourceCode: function(uiSourceCode, header)
  149. {
  150. if (uiSourceCode.styleFile() || header.isInline)
  151. return;
  152. var url = uiSourceCode.url;
  153. uiSourceCode.setSourceMapping(this);
  154. uiSourceCode.setStyleFile(new WebInspector.StyleFile(uiSourceCode));
  155. header.updateLocations();
  156. },
  157. /**
  158. * @param {WebInspector.Event} event
  159. */
  160. _projectWillReset: function(event)
  161. {
  162. var project = /** @type {WebInspector.Project} */ (event.data);
  163. var uiSourceCodes = project.uiSourceCodes();
  164. for (var i = 0; i < uiSourceCodes; ++i)
  165. delete this._urlToHeadersByFrameId[uiSourceCodes[i].url];
  166. },
  167. _initialize: function()
  168. {
  169. /** @type {!Object.<string, !StringMap.<!StringMap.<!WebInspector.CSSStyleSheetHeader>>>} */
  170. this._urlToHeadersByFrameId = {};
  171. },
  172. /**
  173. * @param {WebInspector.Event} event
  174. */
  175. _mainFrameCreatedOrNavigated: function(event)
  176. {
  177. for (var url in this._urlToHeadersByFrameId) {
  178. var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
  179. if (!uiSourceCode)
  180. continue;
  181. this._unbindUISourceCode(uiSourceCode);
  182. }
  183. this._initialize();
  184. }
  185. }
  186. /**
  187. * @constructor
  188. * @param {WebInspector.UISourceCode} uiSourceCode
  189. */
  190. WebInspector.StyleFile = function(uiSourceCode)
  191. {
  192. this._uiSourceCode = uiSourceCode;
  193. this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
  194. this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
  195. }
  196. WebInspector.StyleFile.updateTimeout = 200;
  197. WebInspector.StyleFile.sourceURLRegex = /\n[\040\t]*\/\*#[\040\t]sourceURL=[\040\t]*([^\s]*)[\040\t]*\*\/[\040\t]*$/m;
  198. WebInspector.StyleFile.prototype = {
  199. _workingCopyCommitted: function(event)
  200. {
  201. if (this._isAddingRevision)
  202. return;
  203. this._commitIncrementalEdit(true);
  204. },
  205. _workingCopyChanged: function(event)
  206. {
  207. if (this._isAddingRevision)
  208. return;
  209. // FIXME: Extensions tests override updateTimeout because extensions don't have any control over applying changes to domain specific bindings.
  210. if (WebInspector.StyleFile.updateTimeout >= 0) {
  211. this._incrementalUpdateTimer = setTimeout(this._commitIncrementalEdit.bind(this, false), WebInspector.StyleFile.updateTimeout)
  212. } else
  213. this._commitIncrementalEdit(false);
  214. },
  215. /**
  216. * @param {boolean} majorChange
  217. */
  218. _commitIncrementalEdit: function(majorChange)
  219. {
  220. this._clearIncrementalUpdateTimer();
  221. WebInspector.styleContentBinding.setStyleContent(this._uiSourceCode, this._uiSourceCode.workingCopy(), majorChange, this._styleContentSet.bind(this));
  222. },
  223. /**
  224. * @param {?string} error
  225. */
  226. _styleContentSet: function(error)
  227. {
  228. if (error)
  229. WebInspector.showErrorMessage(error);
  230. },
  231. _clearIncrementalUpdateTimer: function()
  232. {
  233. if (!this._incrementalUpdateTimer)
  234. return;
  235. clearTimeout(this._incrementalUpdateTimer);
  236. delete this._incrementalUpdateTimer;
  237. },
  238. /**
  239. * @param {string} content
  240. */
  241. addRevision: function(content)
  242. {
  243. this._isAddingRevision = true;
  244. if (this._uiSourceCode.project().type() === WebInspector.projectTypes.FileSystem)
  245. content = content.replace(WebInspector.StyleFile.sourceURLRegex, "");
  246. this._uiSourceCode.addRevision(content);
  247. delete this._isAddingRevision;
  248. },
  249. dispose: function()
  250. {
  251. this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
  252. this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
  253. }
  254. }
  255. /**
  256. * @constructor
  257. * @param {WebInspector.CSSStyleModel} cssModel
  258. */
  259. WebInspector.StyleContentBinding = function(cssModel, workspace)
  260. {
  261. this._cssModel = cssModel;
  262. this._workspace = workspace;
  263. this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetChanged, this);
  264. }
  265. WebInspector.StyleContentBinding.prototype = {
  266. /**
  267. * @param {WebInspector.UISourceCode} uiSourceCode
  268. * @param {string} content
  269. * @param {boolean} majorChange
  270. * @param {function(?string)} userCallback
  271. */
  272. setStyleContent: function(uiSourceCode, content, majorChange, userCallback)
  273. {
  274. var styleSheetIds = this._cssModel.styleSheetIdsForURL(uiSourceCode.url);
  275. if (!styleSheetIds.length) {
  276. userCallback("No stylesheet found: " + uiSourceCode.url);
  277. return;
  278. }
  279. this._isSettingContent = true;
  280. function callback(error)
  281. {
  282. userCallback(error);
  283. delete this._isSettingContent;
  284. }
  285. this._cssModel.setStyleSheetText(styleSheetIds[0], content, majorChange, callback.bind(this));
  286. },
  287. /**
  288. * @param {WebInspector.Event} event
  289. */
  290. _styleSheetChanged: function(event)
  291. {
  292. if (this._isSettingContent)
  293. return;
  294. if (!event.data.majorChange)
  295. return;
  296. /**
  297. * @param {?string} error
  298. * @param {string} content
  299. */
  300. function callback(error, content)
  301. {
  302. if (!error)
  303. this._innerStyleSheetChanged(event.data.styleSheetId, content);
  304. }
  305. CSSAgent.getStyleSheetText(event.data.styleSheetId, callback.bind(this));
  306. },
  307. /**
  308. * @param {CSSAgent.StyleSheetId} styleSheetId
  309. * @param {string} content
  310. */
  311. _innerStyleSheetChanged: function(styleSheetId, content)
  312. {
  313. var header = this._cssModel.styleSheetHeaderForId(styleSheetId);
  314. if (!header)
  315. return;
  316. var styleSheetURL = header.resourceURL();
  317. if (!styleSheetURL)
  318. return;
  319. var uiSourceCode = this._workspace.uiSourceCodeForURL(styleSheetURL)
  320. if (!uiSourceCode)
  321. return;
  322. if (uiSourceCode.styleFile())
  323. uiSourceCode.styleFile().addRevision(content);
  324. }
  325. }
  326. /**
  327. * @type {?WebInspector.StyleContentBinding}
  328. */
  329. WebInspector.styleContentBinding = null;