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 /** 32 * @constructor 33 * @implements {WebInspector.SourceMapping} 34 * @param {!WebInspector.CSSStyleModel} cssModel 35 * @param {!WebInspector.Workspace} workspace 36 */ 37 WebInspector.StylesSourceMapping = function(cssModel, workspace) 38 { 39 this._cssModel = cssModel; 40 this._workspace = workspace; 41 this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved, this); 42 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAddedToWorkspace, this); 43 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this); 44 45 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._mainFrameNavigated, this); 46 47 this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetChanged, this); 48 this._initialize(); 49 } 50 51 WebInspector.StylesSourceMapping.MinorChangeUpdateTimeoutMs = 1000; 52 53 WebInspector.StylesSourceMapping.prototype = { 54 /** 55 * @param {!WebInspector.RawLocation} rawLocation 56 * @return {?WebInspector.UILocation} 57 */ 58 rawLocationToUILocation: function(rawLocation) 59 { 60 var location = /** @type WebInspector.CSSLocation */ (rawLocation); 61 var uiSourceCode = this._workspace.uiSourceCodeForURL(location.url); 62 if (!uiSourceCode) 63 return null; 64 return uiSourceCode.uiLocation(location.lineNumber, location.columnNumber); 65 }, 66 67 /** 68 * @param {!WebInspector.UISourceCode} uiSourceCode 69 * @param {number} lineNumber 70 * @param {number} columnNumber 71 * @return {!WebInspector.RawLocation} 72 */ 73 uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) 74 { 75 return new WebInspector.CSSLocation(this._cssModel.target(), uiSourceCode.url || "", lineNumber, columnNumber); 76 }, 77 78 /** 79 * @return {boolean} 80 */ 81 isIdentity: function() 82 { 83 return true; 84 }, 85 86 /** 87 * @return {!WebInspector.Target} 88 */ 89 target: function() 90 { 91 return this._cssModel.target(); 92 }, 93 94 /** 95 * @param {!WebInspector.CSSStyleSheetHeader} header 96 */ 97 addHeader: function(header) 98 { 99 var url = header.resourceURL(); 100 if (!url) 101 return; 102 103 header.pushSourceMapping(this); 104 var map = this._urlToHeadersByFrameId[url]; 105 if (!map) { 106 map = /** @type {!StringMap.<!StringMap.<!WebInspector.CSSStyleSheetHeader>>} */ (new StringMap()); 107 this._urlToHeadersByFrameId[url] = map; 108 } 109 var headersById = map.get(header.frameId); 110 if (!headersById) { 111 headersById = /** @type {!StringMap.<!WebInspector.CSSStyleSheetHeader>} */ (new StringMap()); 112 map.put(header.frameId, headersById); 113 } 114 headersById.put(header.id, header); 115 var uiSourceCode = this._workspace.uiSourceCodeForURL(url); 116 if (uiSourceCode) 117 this._bindUISourceCode(uiSourceCode, header); 118 }, 119 120 /** 121 * @param {!WebInspector.CSSStyleSheetHeader} header 122 */ 123 removeHeader: function(header) 124 { 125 var url = header.resourceURL(); 126 if (!url) 127 return; 128 129 var map = this._urlToHeadersByFrameId[url]; 130 console.assert(map); 131 var headersById = map.get(header.frameId); 132 console.assert(headersById); 133 headersById.remove(header.id); 134 135 if (!headersById.size()) { 136 map.remove(header.frameId); 137 if (!map.size()) { 138 delete this._urlToHeadersByFrameId[url]; 139 var uiSourceCode = this._workspace.uiSourceCodeForURL(url); 140 if (uiSourceCode) 141 this._unbindUISourceCode(uiSourceCode); 142 } 143 } 144 }, 145 146 /** 147 * @param {!WebInspector.UISourceCode} uiSourceCode 148 */ 149 _unbindUISourceCode: function(uiSourceCode) 150 { 151 var styleFile = this._styleFiles.get(uiSourceCode); 152 if (!styleFile) 153 return; 154 styleFile.dispose(); 155 this._styleFiles.remove(uiSourceCode); 156 }, 157 158 /** 159 * @param {!WebInspector.Event} event 160 */ 161 _uiSourceCodeAddedToWorkspace: function(event) 162 { 163 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data); 164 var url = uiSourceCode.url; 165 if (!url || !this._urlToHeadersByFrameId[url]) 166 return; 167 this._bindUISourceCode(uiSourceCode, this._urlToHeadersByFrameId[url].values()[0].values()[0]); 168 }, 169 170 /** 171 * @param {!WebInspector.UISourceCode} uiSourceCode 172 * @param {!WebInspector.CSSStyleSheetHeader} header 173 */ 174 _bindUISourceCode: function(uiSourceCode, header) 175 { 176 if (this._styleFiles.get(uiSourceCode) || header.isInline) 177 return; 178 var url = uiSourceCode.url; 179 this._styleFiles.put(uiSourceCode, new WebInspector.StyleFile(uiSourceCode, this)); 180 header.updateLocations(); 181 }, 182 183 /** 184 * @param {!WebInspector.Event} event 185 */ 186 _projectRemoved: function(event) 187 { 188 var project = /** @type {!WebInspector.Project} */ (event.data); 189 var uiSourceCodes = project.uiSourceCodes(); 190 for (var i = 0; i < uiSourceCodes.length; ++i) 191 this._unbindUISourceCode(uiSourceCodes[i]); 192 }, 193 194 /** 195 * @param {!WebInspector.Event} event 196 */ 197 _uiSourceCodeRemoved: function(event) 198 { 199 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data); 200 this._unbindUISourceCode(uiSourceCode); 201 }, 202 203 _initialize: function() 204 { 205 /** @type {!Object.<string, !StringMap.<!StringMap.<!WebInspector.CSSStyleSheetHeader>>>} */ 206 this._urlToHeadersByFrameId = {}; 207 /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.StyleFile>} */ 208 this._styleFiles = new Map(); 209 }, 210 211 /** 212 * @param {!WebInspector.Event} event 213 */ 214 _mainFrameNavigated: function(event) 215 { 216 for (var url in this._urlToHeadersByFrameId) { 217 var uiSourceCode = this._workspace.uiSourceCodeForURL(url); 218 if (!uiSourceCode) 219 continue; 220 this._unbindUISourceCode(uiSourceCode); 221 } 222 this._initialize(); 223 }, 224 225 /** 226 * @param {!WebInspector.UISourceCode} uiSourceCode 227 * @param {string} content 228 * @param {boolean} majorChange 229 * @param {function(?string)} userCallback 230 */ 231 _setStyleContent: function(uiSourceCode, content, majorChange, userCallback) 232 { 233 var styleSheetIds = this._cssModel.styleSheetIdsForURL(uiSourceCode.url); 234 if (!styleSheetIds.length) { 235 userCallback("No stylesheet found: " + uiSourceCode.url); 236 return; 237 } 238 239 this._isSettingContent = true; 240 241 /** 242 * @param {?Protocol.Error} error 243 * @this {WebInspector.StylesSourceMapping} 244 */ 245 function callback(error) 246 { 247 userCallback(error); 248 delete this._isSettingContent; 249 } 250 this._cssModel.setStyleSheetText(styleSheetIds[0], content, majorChange, callback.bind(this)); 251 }, 252 253 /** 254 * @param {!WebInspector.Event} event 255 */ 256 _styleSheetChanged: function(event) 257 { 258 if (this._isSettingContent) 259 return; 260 261 if (event.data.majorChange) { 262 this._updateStyleSheetText(event.data.styleSheetId); 263 return; 264 } 265 266 this._updateStyleSheetTextSoon(event.data.styleSheetId); 267 }, 268 269 /** 270 * @param {!CSSAgent.StyleSheetId} styleSheetId 271 */ 272 _updateStyleSheetTextSoon: function(styleSheetId) 273 { 274 if (this._updateStyleSheetTextTimer) 275 clearTimeout(this._updateStyleSheetTextTimer); 276 277 this._updateStyleSheetTextTimer = setTimeout(this._updateStyleSheetText.bind(this, styleSheetId), WebInspector.StylesSourceMapping.MinorChangeUpdateTimeoutMs); 278 }, 279 280 /** 281 * @param {!CSSAgent.StyleSheetId} styleSheetId 282 */ 283 _updateStyleSheetText: function(styleSheetId) 284 { 285 if (this._updateStyleSheetTextTimer) { 286 clearTimeout(this._updateStyleSheetTextTimer); 287 delete this._updateStyleSheetTextTimer; 288 } 289 290 var header = this._cssModel.styleSheetHeaderForId(styleSheetId); 291 if (!header) 292 return; 293 var styleSheetURL = header.resourceURL(); 294 if (!styleSheetURL) 295 return; 296 var uiSourceCode = this._workspace.uiSourceCodeForURL(styleSheetURL) 297 if (!uiSourceCode) 298 return; 299 header.requestContent(callback.bind(this, uiSourceCode)); 300 301 /** 302 * @param {!WebInspector.UISourceCode} uiSourceCode 303 * @param {?string} content 304 * @this {WebInspector.StylesSourceMapping} 305 */ 306 function callback(uiSourceCode, content) 307 { 308 var styleFile = this._styleFiles.get(uiSourceCode); 309 if (styleFile) 310 styleFile.addRevision(content || ""); 311 } 312 } 313 } 314 315 /** 316 * @constructor 317 * @param {!WebInspector.UISourceCode} uiSourceCode 318 * @param {!WebInspector.StylesSourceMapping} mapping 319 */ 320 WebInspector.StyleFile = function(uiSourceCode, mapping) 321 { 322 this._uiSourceCode = uiSourceCode; 323 this._mapping = mapping; 324 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 325 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this); 326 this._commitThrottler = new WebInspector.Throttler(WebInspector.StyleFile.updateTimeout); 327 } 328 329 WebInspector.StyleFile.updateTimeout = 200; 330 331 WebInspector.StyleFile.prototype = { 332 /** 333 * @param {!WebInspector.Event} event 334 */ 335 _workingCopyCommitted: function(event) 336 { 337 if (this._isAddingRevision) 338 return; 339 340 this._isMajorChangePending = true; 341 this._commitThrottler.schedule(this._commitIncrementalEdit.bind(this), true); 342 }, 343 344 /** 345 * @param {!WebInspector.Event} event 346 */ 347 _workingCopyChanged: function(event) 348 { 349 if (this._isAddingRevision) 350 return; 351 352 this._commitThrottler.schedule(this._commitIncrementalEdit.bind(this), false); 353 }, 354 355 /** 356 * @param {!WebInspector.Throttler.FinishCallback} finishCallback 357 */ 358 _commitIncrementalEdit: function(finishCallback) 359 { 360 this._mapping._setStyleContent(this._uiSourceCode, this._uiSourceCode.workingCopy(), this._isMajorChangePending, this._styleContentSet.bind(this, finishCallback)); 361 this._isMajorChangePending = false; 362 }, 363 364 /** 365 * @param {!WebInspector.Throttler.FinishCallback} finishCallback 366 * @param {?string} error 367 */ 368 _styleContentSet: function(finishCallback, error) 369 { 370 if (error) 371 this._mapping._cssModel.target().consoleModel.showErrorMessage(error); 372 finishCallback(); 373 }, 374 375 /** 376 * @param {string} content 377 */ 378 addRevision: function(content) 379 { 380 this._isAddingRevision = true; 381 this._uiSourceCode.addRevision(content); 382 delete this._isAddingRevision; 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 } 391