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