1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 /** 6 * @constructor 7 * @implements {WebInspector.TargetManager.Observer} 8 */ 9 WebInspector.CSSWorkspaceBinding = function() 10 { 11 /** @type {!Map.<!WebInspector.Target, !WebInspector.CSSWorkspaceBinding.TargetInfo>} */ 12 this._targetToTargetInfo = new Map(); 13 WebInspector.targetManager.observeTargets(this); 14 15 WebInspector.targetManager.addModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._mainFrameCreatedOrNavigated, this); 16 } 17 18 WebInspector.CSSWorkspaceBinding.prototype = { 19 /** 20 * @param {!WebInspector.Target} target 21 */ 22 targetAdded: function(target) 23 { 24 this._targetToTargetInfo.set(target, new WebInspector.CSSWorkspaceBinding.TargetInfo(target, WebInspector.workspace, WebInspector.networkWorkspaceBinding)); 25 }, 26 27 /** 28 * @param {!WebInspector.Target} target 29 */ 30 targetRemoved: function(target) 31 { 32 this._targetToTargetInfo.remove(target)._dispose(); 33 }, 34 35 /** 36 * @param {!WebInspector.CSSStyleSheetHeader} header 37 * @param {!WebInspector.CSSSourceMapping} mapping 38 */ 39 pushSourceMapping: function(header, mapping) 40 { 41 this._ensureInfoForHeader(header)._pushSourceMapping(mapping); 42 }, 43 44 /** 45 * @param {!WebInspector.CSSStyleSheetHeader} header 46 * @return {?WebInspector.CSSWorkspaceBinding.HeaderInfo} 47 */ 48 _headerInfo: function(header) 49 { 50 var map = this._targetToTargetInfo.get(header.target()); 51 return map._headerInfo(header.id) || null; 52 }, 53 54 /** 55 * @param {!WebInspector.CSSStyleSheetHeader} header 56 * @return {!WebInspector.CSSWorkspaceBinding.HeaderInfo} 57 */ 58 _ensureInfoForHeader: function(header) 59 { 60 var targetInfo = this._targetToTargetInfo.get(header.target()); 61 if (!targetInfo) { 62 targetInfo = new WebInspector.CSSWorkspaceBinding.TargetInfo(header.target(), WebInspector.workspace, WebInspector.networkWorkspaceBinding); 63 this._targetToTargetInfo.set(header.target(), targetInfo); 64 } 65 return targetInfo._ensureInfoForHeader(header); 66 }, 67 68 /** 69 * @param {!WebInspector.Event} event 70 */ 71 _mainFrameCreatedOrNavigated: function(event) 72 { 73 var target = /** @type {!WebInspector.ResourceTreeModel} */ (event.target).target(); 74 this._targetToTargetInfo.get(target)._reset(); 75 }, 76 77 /** 78 * @param {!WebInspector.CSSStyleSheetHeader} header 79 */ 80 updateLocations: function(header) 81 { 82 var info = this._headerInfo(header); 83 if (info) 84 info._updateLocations(); 85 }, 86 87 /** 88 * @param {!WebInspector.CSSLocation} rawLocation 89 * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate 90 * @return {!WebInspector.CSSWorkspaceBinding.LiveLocation} 91 */ 92 createLiveLocation: function(rawLocation, updateDelegate) 93 { 94 var header = rawLocation.styleSheetId ? rawLocation.target().cssModel.styleSheetHeaderForId(rawLocation.styleSheetId) : null; 95 return new WebInspector.CSSWorkspaceBinding.LiveLocation(rawLocation.target().cssModel, header, rawLocation, updateDelegate); 96 }, 97 98 /** 99 * @param {!WebInspector.CSSWorkspaceBinding.LiveLocation} location 100 */ 101 _addLiveLocation: function(location) 102 { 103 this._ensureInfoForHeader(location._header)._addLocation(location); 104 }, 105 106 /** 107 * @param {!WebInspector.CSSWorkspaceBinding.LiveLocation} location 108 */ 109 _removeLiveLocation: function(location) 110 { 111 var info = this._headerInfo(location._header); 112 if (info) 113 info._removeLocation(location); 114 }, 115 116 /** 117 * @param {!WebInspector.CSSProperty} cssProperty 118 * @param {boolean} forName 119 * @return {?WebInspector.UILocation} 120 */ 121 propertyUILocation: function(cssProperty, forName) 122 { 123 var style = cssProperty.ownerStyle; 124 if (!style || !style.parentRule || !style.styleSheetId) 125 return null; 126 127 var range = cssProperty.range; 128 if (!range) 129 return null; 130 131 var url = style.parentRule.resourceURL(); 132 if (!url) 133 return null; 134 135 var line = forName ? range.startLine : range.endLine; 136 // End of range is exclusive, so subtract 1 from the end offset. 137 var column = forName ? range.startColumn : range.endColumn - (cssProperty.text && cssProperty.text.endsWith(";") ? 2 : 1); 138 var rawLocation = new WebInspector.CSSLocation(style.target(), style.styleSheetId, url, line, column); 139 return this.rawLocationToUILocation(rawLocation); 140 }, 141 142 /** 143 * @param {?WebInspector.CSSLocation} rawLocation 144 * @return {?WebInspector.UILocation} 145 */ 146 rawLocationToUILocation: function(rawLocation) 147 { 148 if (!rawLocation) 149 return null; 150 var cssModel = rawLocation.target().cssModel; 151 var frameIdToSheetIds = cssModel.styleSheetIdsByFrameIdForURL(rawLocation.url); 152 if (!Object.values(frameIdToSheetIds).length) 153 return null; 154 var styleSheetIds = []; 155 for (var frameId in frameIdToSheetIds) 156 styleSheetIds = styleSheetIds.concat(frameIdToSheetIds[frameId]); 157 var uiLocation; 158 for (var i = 0; !uiLocation && i < styleSheetIds.length; ++i) { 159 var header = cssModel.styleSheetHeaderForId(styleSheetIds[i]); 160 if (!header) 161 continue; 162 var info = this._headerInfo(header); 163 if (info) 164 uiLocation = info._rawLocationToUILocation(rawLocation.lineNumber, rawLocation.columnNumber); 165 } 166 return uiLocation || null; 167 } 168 } 169 170 /** 171 * @constructor 172 * @param {!WebInspector.Target} target 173 * @param {!WebInspector.Workspace} workspace 174 * @param {!WebInspector.NetworkWorkspaceBinding} networkWorkspaceBinding 175 */ 176 WebInspector.CSSWorkspaceBinding.TargetInfo = function(target, workspace, networkWorkspaceBinding) 177 { 178 this._target = target; 179 this._workspace = workspace; 180 181 var cssModel = target.cssModel; 182 this._stylesSourceMapping = new WebInspector.StylesSourceMapping(cssModel, workspace); 183 this._sassSourceMapping = new WebInspector.SASSSourceMapping(cssModel, workspace, networkWorkspaceBinding); 184 185 /** @type {!StringMap.<!WebInspector.CSSWorkspaceBinding.HeaderInfo>} */ 186 this._headerInfoById = new StringMap(); 187 188 cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetAdded, this); 189 cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this); 190 } 191 192 WebInspector.CSSWorkspaceBinding.TargetInfo.prototype = { 193 /** 194 * @param {!WebInspector.Event} event 195 */ 196 _styleSheetAdded: function(event) 197 { 198 var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.data); 199 this._stylesSourceMapping.addHeader(header); 200 this._sassSourceMapping.addHeader(header); 201 }, 202 203 /** 204 * @param {!WebInspector.Event} event 205 */ 206 _styleSheetRemoved: function(event) 207 { 208 var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.data); 209 this._stylesSourceMapping.removeHeader(header); 210 this._sassSourceMapping.removeHeader(header); 211 this._headerInfoById.remove(header.id); 212 }, 213 214 /** 215 * @param {!CSSAgent.StyleSheetId} id 216 */ 217 _headerInfo: function(id) 218 { 219 return this._headerInfoById.get(id); 220 }, 221 222 _ensureInfoForHeader: function(header) 223 { 224 var info = this._headerInfoById.get(header.id); 225 if (!info) { 226 info = new WebInspector.CSSWorkspaceBinding.HeaderInfo(header); 227 this._headerInfoById.set(header.id, info); 228 } 229 return info; 230 }, 231 232 _dispose: function() 233 { 234 this._reset(); 235 this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetAdded, this); 236 this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this); 237 }, 238 239 _reset: function() 240 { 241 this._headerInfoById.clear(); 242 } 243 } 244 245 /** 246 * @constructor 247 * @param {!WebInspector.CSSStyleSheetHeader} header 248 */ 249 WebInspector.CSSWorkspaceBinding.HeaderInfo = function(header) 250 { 251 this._header = header; 252 253 /** @type {!Array.<!WebInspector.CSSSourceMapping>} */ 254 this._sourceMappings = []; 255 256 /** @type {!Set.<!WebInspector.LiveLocation>} */ 257 this._locations = new Set(); 258 } 259 260 WebInspector.CSSWorkspaceBinding.HeaderInfo.prototype = { 261 /** 262 * @param {!WebInspector.LiveLocation} location 263 */ 264 _addLocation: function(location) 265 { 266 this._locations.add(location); 267 location.update(); 268 }, 269 270 /** 271 * @param {!WebInspector.LiveLocation} location 272 */ 273 _removeLocation: function(location) 274 { 275 this._locations.remove(location); 276 }, 277 278 _updateLocations: function() 279 { 280 var items = this._locations.values(); 281 for (var i = 0; i < items.length; ++i) 282 items[i].update(); 283 }, 284 285 /** 286 * @param {number} lineNumber 287 * @param {number=} columnNumber 288 * @return {?WebInspector.UILocation} 289 */ 290 _rawLocationToUILocation: function(lineNumber, columnNumber) 291 { 292 var uiLocation = null; 293 var rawLocation = new WebInspector.CSSLocation(this._header.target(), this._header.id, this._header.resourceURL(), lineNumber, columnNumber); 294 for (var i = this._sourceMappings.length - 1; !uiLocation && i >= 0; --i) 295 uiLocation = this._sourceMappings[i].rawLocationToUILocation(rawLocation); 296 return uiLocation; 297 }, 298 299 /** 300 * @param {!WebInspector.CSSSourceMapping} sourceMapping 301 */ 302 _pushSourceMapping: function(sourceMapping) 303 { 304 this._sourceMappings.push(sourceMapping); 305 this._updateLocations(); 306 } 307 } 308 309 /** 310 * @constructor 311 * @extends {WebInspector.LiveLocation} 312 * @param {!WebInspector.CSSStyleModel} cssModel 313 * @param {?WebInspector.CSSStyleSheetHeader} header 314 * @param {!WebInspector.CSSLocation} rawLocation 315 * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate 316 */ 317 WebInspector.CSSWorkspaceBinding.LiveLocation = function(cssModel, header, rawLocation, updateDelegate) 318 { 319 WebInspector.LiveLocation.call(this, updateDelegate); 320 this._cssModel = cssModel; 321 this._rawLocation = rawLocation; 322 if (!header) 323 this._clearStyleSheet(); 324 else 325 this._setStyleSheet(header); 326 } 327 328 WebInspector.CSSWorkspaceBinding.LiveLocation.prototype = { 329 /** 330 * @param {!WebInspector.Event} event 331 */ 332 _styleSheetAdded: function(event) 333 { 334 console.assert(!this._header); 335 var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.data); 336 if (header.sourceURL && header.sourceURL === this._rawLocation.url) 337 this._setStyleSheet(header); 338 }, 339 340 /** 341 * @param {!WebInspector.Event} event 342 */ 343 _styleSheetRemoved: function(event) 344 { 345 console.assert(this._header); 346 var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.data); 347 if (this._header !== header) 348 return; 349 WebInspector.cssWorkspaceBinding._removeLiveLocation(this); 350 this._clearStyleSheet(); 351 }, 352 353 /** 354 * @param {!WebInspector.CSSStyleSheetHeader} header 355 */ 356 _setStyleSheet: function(header) 357 { 358 this._header = header; 359 WebInspector.cssWorkspaceBinding._addLiveLocation(this); 360 this._cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetAdded, this); 361 this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this); 362 }, 363 364 _clearStyleSheet: function() 365 { 366 delete this._header; 367 this._cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this); 368 this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetAdded, this); 369 }, 370 371 /** 372 * @return {?WebInspector.UILocation} 373 */ 374 uiLocation: function() 375 { 376 var cssLocation = this._rawLocation; 377 if (this._header) { 378 var headerInfo = WebInspector.cssWorkspaceBinding._headerInfo(this._header); 379 return headerInfo._rawLocationToUILocation(cssLocation.lineNumber, cssLocation.columnNumber); 380 } 381 var uiSourceCode = WebInspector.workspace.uiSourceCodeForURL(cssLocation.url); 382 if (!uiSourceCode) 383 return null; 384 return uiSourceCode.uiLocation(cssLocation.lineNumber, cssLocation.columnNumber); 385 }, 386 387 dispose: function() 388 { 389 WebInspector.LiveLocation.prototype.dispose.call(this); 390 if (this._header) 391 WebInspector.cssWorkspaceBinding._removeLiveLocation(this); 392 this._cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetAdded, this); 393 this._cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this); 394 }, 395 396 __proto__: WebInspector.LiveLocation.prototype 397 } 398 399 /** 400 * @interface 401 */ 402 WebInspector.CSSSourceMapping = function() 403 { 404 } 405 406 WebInspector.CSSSourceMapping.prototype = { 407 /** 408 * @param {!WebInspector.CSSLocation} rawLocation 409 * @return {?WebInspector.UILocation} 410 */ 411 rawLocationToUILocation: function(rawLocation) { }, 412 413 /** 414 * @param {!WebInspector.UISourceCode} uiSourceCode 415 * @param {number} lineNumber 416 * @param {number} columnNumber 417 * @return {?WebInspector.CSSLocation} 418 */ 419 uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) { }, 420 421 /** 422 * @return {boolean} 423 */ 424 isIdentity: function() { }, 425 426 /** 427 * @param {!WebInspector.UISourceCode} uiSourceCode 428 * @param {number} lineNumber 429 * @return {boolean} 430 */ 431 uiLineHasMapping: function(uiSourceCode, lineNumber) { } 432 } 433 434 /** 435 * @type {!WebInspector.CSSWorkspaceBinding} 436 */ 437 WebInspector.cssWorkspaceBinding; 438