1 /* 2 * Copyright (C) 2010 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 * @extends {WebInspector.Object} 34 * @param {WebInspector.Workspace} workspace 35 */ 36 WebInspector.CSSStyleModel = function(workspace) 37 { 38 this._workspace = workspace; 39 this._pendingCommandsMajorState = []; 40 WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.UndoRedoRequested, this._undoRedoRequested, this); 41 WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.UndoRedoCompleted, this._undoRedoCompleted, this); 42 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameCreatedOrNavigated, this._mainFrameCreatedOrNavigated, this); 43 this._namedFlowCollections = {}; 44 WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.DocumentUpdated, this._resetNamedFlowCollections, this); 45 InspectorBackend.registerCSSDispatcher(new WebInspector.CSSDispatcher(this)); 46 CSSAgent.enable(); 47 this._resetStyleSheets(); 48 } 49 50 /** 51 * @param {Array.<CSSAgent.CSSRule>} ruleArray 52 */ 53 WebInspector.CSSStyleModel.parseRuleArrayPayload = function(ruleArray) 54 { 55 var result = []; 56 for (var i = 0; i < ruleArray.length; ++i) 57 result.push(WebInspector.CSSRule.parsePayload(ruleArray[i])); 58 return result; 59 } 60 61 /** 62 * @param {Array.<CSSAgent.RuleMatch>} matchArray 63 */ 64 WebInspector.CSSStyleModel.parseRuleMatchArrayPayload = function(matchArray) 65 { 66 var result = []; 67 for (var i = 0; i < matchArray.length; ++i) 68 result.push(WebInspector.CSSRule.parsePayload(matchArray[i].rule, matchArray[i].matchingSelectors)); 69 return result; 70 } 71 72 WebInspector.CSSStyleModel.Events = { 73 StyleSheetAdded: "StyleSheetAdded", 74 StyleSheetChanged: "StyleSheetChanged", 75 StyleSheetRemoved: "StyleSheetRemoved", 76 MediaQueryResultChanged: "MediaQueryResultChanged", 77 NamedFlowCreated: "NamedFlowCreated", 78 NamedFlowRemoved: "NamedFlowRemoved", 79 RegionLayoutUpdated: "RegionLayoutUpdated", 80 RegionOversetChanged: "RegionOversetChanged" 81 } 82 83 WebInspector.CSSStyleModel.MediaTypes = ["all", "braille", "embossed", "handheld", "print", "projection", "screen", "speech", "tty", "tv"]; 84 85 WebInspector.CSSStyleModel.prototype = { 86 /** 87 * @param {DOMAgent.NodeId} nodeId 88 * @param {boolean} needPseudo 89 * @param {boolean} needInherited 90 * @param {function(?*)} userCallback 91 */ 92 getMatchedStylesAsync: function(nodeId, needPseudo, needInherited, userCallback) 93 { 94 /** 95 * @param {function(?*)} userCallback 96 * @param {?Protocol.Error} error 97 * @param {Array.<CSSAgent.RuleMatch>=} matchedPayload 98 * @param {Array.<CSSAgent.PseudoIdMatches>=} pseudoPayload 99 * @param {Array.<CSSAgent.InheritedStyleEntry>=} inheritedPayload 100 */ 101 function callback(userCallback, error, matchedPayload, pseudoPayload, inheritedPayload) 102 { 103 if (error) { 104 if (userCallback) 105 userCallback(null); 106 return; 107 } 108 109 var result = {}; 110 if (matchedPayload) 111 result.matchedCSSRules = WebInspector.CSSStyleModel.parseRuleMatchArrayPayload(matchedPayload); 112 113 if (pseudoPayload) { 114 result.pseudoElements = []; 115 for (var i = 0; i < pseudoPayload.length; ++i) { 116 var entryPayload = pseudoPayload[i]; 117 result.pseudoElements.push({ pseudoId: entryPayload.pseudoId, rules: WebInspector.CSSStyleModel.parseRuleMatchArrayPayload(entryPayload.matches) }); 118 } 119 } 120 121 if (inheritedPayload) { 122 result.inherited = []; 123 for (var i = 0; i < inheritedPayload.length; ++i) { 124 var entryPayload = inheritedPayload[i]; 125 var entry = {}; 126 if (entryPayload.inlineStyle) 127 entry.inlineStyle = WebInspector.CSSStyleDeclaration.parsePayload(entryPayload.inlineStyle); 128 if (entryPayload.matchedCSSRules) 129 entry.matchedCSSRules = WebInspector.CSSStyleModel.parseRuleMatchArrayPayload(entryPayload.matchedCSSRules); 130 result.inherited.push(entry); 131 } 132 } 133 134 if (userCallback) 135 userCallback(result); 136 } 137 138 CSSAgent.getMatchedStylesForNode(nodeId, needPseudo, needInherited, callback.bind(null, userCallback)); 139 }, 140 141 /** 142 * @param {DOMAgent.NodeId} nodeId 143 * @param {function(?WebInspector.CSSStyleDeclaration)} userCallback 144 */ 145 getComputedStyleAsync: function(nodeId, userCallback) 146 { 147 /** 148 * @param {function(?WebInspector.CSSStyleDeclaration)} userCallback 149 */ 150 function callback(userCallback, error, computedPayload) 151 { 152 if (error || !computedPayload) 153 userCallback(null); 154 else 155 userCallback(WebInspector.CSSStyleDeclaration.parseComputedStylePayload(computedPayload)); 156 } 157 158 CSSAgent.getComputedStyleForNode(nodeId, callback.bind(null, userCallback)); 159 }, 160 161 /** 162 * @param {DOMAgent.NodeId} nodeId 163 * @param {function(?WebInspector.CSSStyleDeclaration, ?WebInspector.CSSStyleDeclaration)} userCallback 164 */ 165 getInlineStylesAsync: function(nodeId, userCallback) 166 { 167 /** 168 * @param {function(?WebInspector.CSSStyleDeclaration, ?WebInspector.CSSStyleDeclaration)} userCallback 169 * @param {?Protocol.Error} error 170 * @param {?CSSAgent.CSSStyle=} inlinePayload 171 * @param {?CSSAgent.CSSStyle=} attributesStylePayload 172 */ 173 function callback(userCallback, error, inlinePayload, attributesStylePayload) 174 { 175 if (error || !inlinePayload) 176 userCallback(null, null); 177 else 178 userCallback(WebInspector.CSSStyleDeclaration.parsePayload(inlinePayload), attributesStylePayload ? WebInspector.CSSStyleDeclaration.parsePayload(attributesStylePayload) : null); 179 } 180 181 CSSAgent.getInlineStylesForNode(nodeId, callback.bind(null, userCallback)); 182 }, 183 184 /** 185 * @param {DOMAgent.NodeId} nodeId 186 * @param {?Array.<string>|undefined} forcedPseudoClasses 187 * @param {function()=} userCallback 188 */ 189 forcePseudoState: function(nodeId, forcedPseudoClasses, userCallback) 190 { 191 CSSAgent.forcePseudoState(nodeId, forcedPseudoClasses || [], userCallback); 192 }, 193 194 /** 195 * @param {DOMAgent.NodeId} documentNodeId 196 * @param {function(?WebInspector.NamedFlowCollection)} userCallback 197 */ 198 getNamedFlowCollectionAsync: function(documentNodeId, userCallback) 199 { 200 var namedFlowCollection = this._namedFlowCollections[documentNodeId]; 201 if (namedFlowCollection) { 202 userCallback(namedFlowCollection); 203 return; 204 } 205 206 /** 207 * @param {function(?WebInspector.NamedFlowCollection)} userCallback 208 * @param {?Protocol.Error} error 209 * @param {?Array.<CSSAgent.NamedFlow>} namedFlowPayload 210 */ 211 function callback(userCallback, error, namedFlowPayload) 212 { 213 if (error || !namedFlowPayload) 214 userCallback(null); 215 else { 216 var namedFlowCollection = new WebInspector.NamedFlowCollection(namedFlowPayload); 217 this._namedFlowCollections[documentNodeId] = namedFlowCollection; 218 userCallback(namedFlowCollection); 219 } 220 } 221 222 CSSAgent.getNamedFlowCollection(documentNodeId, callback.bind(this, userCallback)); 223 }, 224 225 /** 226 * @param {DOMAgent.NodeId} documentNodeId 227 * @param {string} flowName 228 * @param {function(?WebInspector.NamedFlow)} userCallback 229 */ 230 getFlowByNameAsync: function(documentNodeId, flowName, userCallback) 231 { 232 var namedFlowCollection = this._namedFlowCollections[documentNodeId]; 233 if (namedFlowCollection) { 234 userCallback(namedFlowCollection.flowByName(flowName)); 235 return; 236 } 237 238 /** 239 * @param {function(?WebInspector.NamedFlow)} userCallback 240 * @param {?WebInspector.NamedFlowCollection} namedFlowCollection 241 */ 242 function callback(userCallback, namedFlowCollection) 243 { 244 if (!namedFlowCollection) 245 userCallback(null); 246 else 247 userCallback(namedFlowCollection.flowByName(flowName)); 248 } 249 250 this.getNamedFlowCollectionAsync(documentNodeId, callback.bind(this, userCallback)); 251 }, 252 253 /** 254 * @param {CSSAgent.CSSRuleId} ruleId 255 * @param {DOMAgent.NodeId} nodeId 256 * @param {string} newSelector 257 * @param {function(WebInspector.CSSRule, boolean)} successCallback 258 * @param {function()} failureCallback 259 */ 260 setRuleSelector: function(ruleId, nodeId, newSelector, successCallback, failureCallback) 261 { 262 /** 263 * @param {DOMAgent.NodeId} nodeId 264 * @param {function(WebInspector.CSSRule, boolean)} successCallback 265 * @param {CSSAgent.CSSRule} rulePayload 266 * @param {?Array.<DOMAgent.NodeId>} selectedNodeIds 267 */ 268 function checkAffectsCallback(nodeId, successCallback, rulePayload, selectedNodeIds) 269 { 270 if (!selectedNodeIds) 271 return; 272 var doesAffectSelectedNode = (selectedNodeIds.indexOf(nodeId) >= 0); 273 var rule = WebInspector.CSSRule.parsePayload(rulePayload); 274 successCallback(rule, doesAffectSelectedNode); 275 } 276 277 /** 278 * @param {DOMAgent.NodeId} nodeId 279 * @param {function(WebInspector.CSSRule, boolean)} successCallback 280 * @param {function()} failureCallback 281 * @param {?Protocol.Error} error 282 * @param {string} newSelector 283 * @param {?CSSAgent.CSSRule} rulePayload 284 */ 285 function callback(nodeId, successCallback, failureCallback, newSelector, error, rulePayload) 286 { 287 this._pendingCommandsMajorState.pop(); 288 if (error) 289 failureCallback(); 290 else { 291 WebInspector.domAgent.markUndoableState(); 292 var ownerDocumentId = this._ownerDocumentId(nodeId); 293 if (ownerDocumentId) 294 WebInspector.domAgent.querySelectorAll(ownerDocumentId, newSelector, checkAffectsCallback.bind(this, nodeId, successCallback, rulePayload)); 295 else 296 failureCallback(); 297 } 298 } 299 300 this._pendingCommandsMajorState.push(true); 301 CSSAgent.setRuleSelector(ruleId, newSelector, callback.bind(this, nodeId, successCallback, failureCallback, newSelector)); 302 }, 303 304 /** 305 * @param {DOMAgent.NodeId} nodeId 306 * @param {string} selector 307 * @param {function(WebInspector.CSSRule, boolean)} successCallback 308 * @param {function()} failureCallback 309 */ 310 addRule: function(nodeId, selector, successCallback, failureCallback) 311 { 312 /** 313 * @param {DOMAgent.NodeId} nodeId 314 * @param {function(WebInspector.CSSRule, boolean)} successCallback 315 * @param {CSSAgent.CSSRule} rulePayload 316 * @param {?Array.<DOMAgent.NodeId>} selectedNodeIds 317 */ 318 function checkAffectsCallback(nodeId, successCallback, rulePayload, selectedNodeIds) 319 { 320 if (!selectedNodeIds) 321 return; 322 323 var doesAffectSelectedNode = (selectedNodeIds.indexOf(nodeId) >= 0); 324 var rule = WebInspector.CSSRule.parsePayload(rulePayload); 325 successCallback(rule, doesAffectSelectedNode); 326 } 327 328 /** 329 * @param {function(WebInspector.CSSRule, boolean)} successCallback 330 * @param {function()} failureCallback 331 * @param {string} selector 332 * @param {?Protocol.Error} error 333 * @param {?CSSAgent.CSSRule} rulePayload 334 */ 335 function callback(successCallback, failureCallback, selector, error, rulePayload) 336 { 337 this._pendingCommandsMajorState.pop(); 338 if (error) { 339 // Invalid syntax for a selector 340 failureCallback(); 341 } else { 342 WebInspector.domAgent.markUndoableState(); 343 var ownerDocumentId = this._ownerDocumentId(nodeId); 344 if (ownerDocumentId) 345 WebInspector.domAgent.querySelectorAll(ownerDocumentId, selector, checkAffectsCallback.bind(this, nodeId, successCallback, rulePayload)); 346 else 347 failureCallback(); 348 } 349 } 350 351 this._pendingCommandsMajorState.push(true); 352 CSSAgent.addRule(nodeId, selector, callback.bind(this, successCallback, failureCallback, selector)); 353 }, 354 355 mediaQueryResultChanged: function() 356 { 357 this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged); 358 }, 359 360 /** 361 * @param {!CSSAgent.StyleSheetId} id 362 * @return {WebInspector.CSSStyleSheetHeader} 363 */ 364 styleSheetHeaderForId: function(id) 365 { 366 return this._styleSheetIdToHeader[id]; 367 }, 368 369 /** 370 * @return {Array.<WebInspector.CSSStyleSheetHeader>} 371 */ 372 styleSheetHeaders: function() 373 { 374 return Object.values(this._styleSheetIdToHeader); 375 }, 376 377 /** 378 * @param {DOMAgent.NodeId} nodeId 379 */ 380 _ownerDocumentId: function(nodeId) 381 { 382 var node = WebInspector.domAgent.nodeForId(nodeId); 383 if (!node) 384 return null; 385 return node.ownerDocument ? node.ownerDocument.id : null; 386 }, 387 388 /** 389 * @param {CSSAgent.StyleSheetId} styleSheetId 390 */ 391 _fireStyleSheetChanged: function(styleSheetId) 392 { 393 if (!this._pendingCommandsMajorState.length) 394 return; 395 396 var majorChange = this._pendingCommandsMajorState[this._pendingCommandsMajorState.length - 1]; 397 398 if (!majorChange || !styleSheetId || !this.hasEventListeners(WebInspector.CSSStyleModel.Events.StyleSheetChanged)) 399 return; 400 401 this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.StyleSheetChanged, { styleSheetId: styleSheetId, majorChange: majorChange }); 402 }, 403 404 /** 405 * @param {!CSSAgent.CSSStyleSheetHeader} header 406 */ 407 _styleSheetAdded: function(header) 408 { 409 console.assert(!this._styleSheetIdToHeader[header.styleSheetId]); 410 var styleSheetHeader = new WebInspector.CSSStyleSheetHeader(header); 411 this._styleSheetIdToHeader[header.styleSheetId] = styleSheetHeader; 412 var url = styleSheetHeader.resourceURL(); 413 if (!this._styleSheetIdsForURL[url]) 414 this._styleSheetIdsForURL[url] = {}; 415 var frameIdToStyleSheetIds = this._styleSheetIdsForURL[url]; 416 var styleSheetIds = frameIdToStyleSheetIds[styleSheetHeader.frameId]; 417 if (!styleSheetIds) { 418 styleSheetIds = []; 419 frameIdToStyleSheetIds[styleSheetHeader.frameId] = styleSheetIds; 420 } 421 styleSheetIds.push(styleSheetHeader.id); 422 this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.StyleSheetAdded, styleSheetHeader); 423 }, 424 425 /** 426 * @param {!CSSAgent.StyleSheetId} id 427 */ 428 _styleSheetRemoved: function(id) 429 { 430 var header = this._styleSheetIdToHeader[id]; 431 console.assert(header); 432 delete this._styleSheetIdToHeader[id]; 433 var url = header.resourceURL(); 434 var frameIdToStyleSheetIds = this._styleSheetIdsForURL[url]; 435 frameIdToStyleSheetIds[header.frameId].remove(id); 436 if (!frameIdToStyleSheetIds[header.frameId].length) { 437 delete frameIdToStyleSheetIds[header.frameId]; 438 if (!Object.keys(this._styleSheetIdsForURL[url]).length) 439 delete this._styleSheetIdsForURL[url]; 440 } 441 this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, header); 442 }, 443 444 /** 445 * @param {string} url 446 * @return {Array.<CSSAgent.StyleSheetId>} 447 */ 448 styleSheetIdsForURL: function(url) 449 { 450 var frameIdToStyleSheetIds = this._styleSheetIdsForURL[url]; 451 if (!frameIdToStyleSheetIds) 452 return []; 453 454 var result = []; 455 for (var frameId in frameIdToStyleSheetIds) 456 result = result.concat(frameIdToStyleSheetIds[frameId]); 457 return result; 458 }, 459 460 /** 461 * @param {string} url 462 * @return {Object.<NetworkAgent.FrameId, Array.<CSSAgent.StyleSheetId>>} 463 */ 464 styleSheetIdsByFrameIdForURL: function(url) 465 { 466 var styleSheetIdsForFrame = this._styleSheetIdsForURL[url]; 467 if (!styleSheetIdsForFrame) 468 return {}; 469 return styleSheetIdsForFrame; 470 }, 471 472 /** 473 * @param {CSSAgent.NamedFlow} namedFlowPayload 474 */ 475 _namedFlowCreated: function(namedFlowPayload) 476 { 477 var namedFlow = WebInspector.NamedFlow.parsePayload(namedFlowPayload); 478 var namedFlowCollection = this._namedFlowCollections[namedFlow.documentNodeId]; 479 480 if (!namedFlowCollection) 481 return; 482 483 namedFlowCollection._appendNamedFlow(namedFlow); 484 this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.NamedFlowCreated, namedFlow); 485 }, 486 487 /** 488 * @param {DOMAgent.NodeId} documentNodeId 489 * @param {string} flowName 490 */ 491 _namedFlowRemoved: function(documentNodeId, flowName) 492 { 493 var namedFlowCollection = this._namedFlowCollections[documentNodeId]; 494 495 if (!namedFlowCollection) 496 return; 497 498 namedFlowCollection._removeNamedFlow(flowName); 499 this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.NamedFlowRemoved, { documentNodeId: documentNodeId, flowName: flowName }); 500 }, 501 502 /** 503 * @param {CSSAgent.NamedFlow} namedFlowPayload 504 */ 505 _regionLayoutUpdated: function(namedFlowPayload) 506 { 507 var namedFlow = WebInspector.NamedFlow.parsePayload(namedFlowPayload); 508 var namedFlowCollection = this._namedFlowCollections[namedFlow.documentNodeId]; 509 510 if (!namedFlowCollection) 511 return; 512 513 namedFlowCollection._appendNamedFlow(namedFlow); 514 this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.RegionLayoutUpdated, namedFlow); 515 }, 516 517 /** 518 * @param {CSSAgent.NamedFlow} namedFlowPayload 519 */ 520 _regionOversetChanged: function(namedFlowPayload) 521 { 522 var namedFlow = WebInspector.NamedFlow.parsePayload(namedFlowPayload); 523 var namedFlowCollection = this._namedFlowCollections[namedFlow.documentNodeId]; 524 525 if (!namedFlowCollection) 526 return; 527 528 namedFlowCollection._appendNamedFlow(namedFlow); 529 this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.RegionOversetChanged, namedFlow); 530 }, 531 532 /** 533 * @param {CSSAgent.StyleSheetId} styleSheetId 534 * @param {string} newText 535 * @param {boolean} majorChange 536 * @param {function(?string)} userCallback 537 */ 538 setStyleSheetText: function(styleSheetId, newText, majorChange, userCallback) 539 { 540 function callback(error) 541 { 542 this._pendingCommandsMajorState.pop(); 543 if (!error && majorChange) 544 WebInspector.domAgent.markUndoableState(); 545 546 if (!error && userCallback) 547 userCallback(error); 548 } 549 this._pendingCommandsMajorState.push(majorChange); 550 CSSAgent.setStyleSheetText(styleSheetId, newText, callback.bind(this)); 551 }, 552 553 _undoRedoRequested: function() 554 { 555 this._pendingCommandsMajorState.push(true); 556 }, 557 558 _undoRedoCompleted: function() 559 { 560 this._pendingCommandsMajorState.pop(); 561 }, 562 563 _mainFrameCreatedOrNavigated: function() 564 { 565 this._resetStyleSheets(); 566 }, 567 568 _resetStyleSheets: function() 569 { 570 /** @type {!Object.<string, !Object.<NetworkAgent.FrameId, !Array.<!CSSAgent.StyleSheetId>>>} */ 571 this._styleSheetIdsForURL = {}; 572 /** @type {!Object.<CSSAgent.StyleSheetId, !WebInspector.CSSStyleSheetHeader>} */ 573 this._styleSheetIdToHeader = {}; 574 }, 575 576 _resetNamedFlowCollections: function() 577 { 578 this._namedFlowCollections = {}; 579 }, 580 581 updateLocations: function() 582 { 583 var headers = Object.values(this._styleSheetIdToHeader); 584 for (var i = 0; i < headers.length; ++i) 585 headers[i].updateLocations(); 586 }, 587 588 /** 589 * @param {CSSAgent.StyleSheetId} styleSheetId 590 * @param {WebInspector.CSSLocation} rawLocation 591 * @param {function(WebInspector.UILocation):(boolean|undefined)} updateDelegate 592 * @return {?WebInspector.LiveLocation} 593 */ 594 createLiveLocation: function(styleSheetId, rawLocation, updateDelegate) 595 { 596 if (!rawLocation) 597 return null; 598 var header = this.styleSheetHeaderForId(styleSheetId); 599 if (!header) 600 return null; 601 return header.createLiveLocation(rawLocation, updateDelegate); 602 }, 603 604 /** 605 * @param {WebInspector.CSSLocation} rawLocation 606 * @return {?WebInspector.UILocation} 607 */ 608 rawLocationToUILocation: function(rawLocation) 609 { 610 var frameIdToSheetIds = this._styleSheetIdsForURL[rawLocation.url]; 611 if (!frameIdToSheetIds) 612 return null; 613 var styleSheetIds = []; 614 for (var frameId in frameIdToSheetIds) 615 styleSheetIds = styleSheetIds.concat(frameIdToSheetIds[frameId]); 616 var uiLocation; 617 for (var i = 0; !uiLocation && i < styleSheetIds.length; ++i) { 618 var header = this.styleSheetHeaderForId(styleSheetIds[i]); 619 console.assert(header); 620 uiLocation = header.rawLocationToUILocation(rawLocation.lineNumber, rawLocation.columnNumber); 621 } 622 return uiLocation || null; 623 }, 624 625 __proto__: WebInspector.Object.prototype 626 } 627 628 /** 629 * @constructor 630 * @extends {WebInspector.LiveLocation} 631 * @param {WebInspector.CSSLocation} rawLocation 632 * @param {function(WebInspector.UILocation):(boolean|undefined)} updateDelegate 633 */ 634 WebInspector.CSSStyleModel.LiveLocation = function(rawLocation, updateDelegate, header) 635 { 636 WebInspector.LiveLocation.call(this, rawLocation, updateDelegate); 637 this._header = header; 638 } 639 640 WebInspector.CSSStyleModel.LiveLocation.prototype = { 641 /** 642 * @return {WebInspector.UILocation} 643 */ 644 uiLocation: function() 645 { 646 var cssLocation = /** @type WebInspector.CSSLocation */ (this.rawLocation()); 647 return this._header.rawLocationToUILocation(cssLocation.lineNumber, cssLocation.columnNumber); 648 }, 649 650 dispose: function() 651 { 652 WebInspector.LiveLocation.prototype.dispose.call(this); 653 this._header._removeLocation(this); 654 }, 655 656 __proto__: WebInspector.LiveLocation.prototype 657 } 658 659 /** 660 * @constructor 661 * @implements {WebInspector.RawLocation} 662 * @param {string} url 663 * @param {number} lineNumber 664 * @param {number=} columnNumber 665 */ 666 WebInspector.CSSLocation = function(url, lineNumber, columnNumber) 667 { 668 this.url = url; 669 this.lineNumber = lineNumber; 670 this.columnNumber = columnNumber || 0; 671 } 672 673 /** 674 * @constructor 675 * @param {CSSAgent.CSSStyle} payload 676 */ 677 WebInspector.CSSStyleDeclaration = function(payload) 678 { 679 this.id = payload.styleId; 680 this.width = payload.width; 681 this.height = payload.height; 682 this.range = payload.range; 683 this._shorthandValues = WebInspector.CSSStyleDeclaration.buildShorthandValueMap(payload.shorthandEntries); 684 this._livePropertyMap = {}; // LIVE properties (source-based or style-based) : { name -> CSSProperty } 685 this._allProperties = []; // ALL properties: [ CSSProperty ] 686 this.__disabledProperties = {}; // DISABLED properties: { index -> CSSProperty } 687 var payloadPropertyCount = payload.cssProperties.length; 688 689 var propertyIndex = 0; 690 for (var i = 0; i < payloadPropertyCount; ++i) { 691 var property = WebInspector.CSSProperty.parsePayload(this, i, payload.cssProperties[i]); 692 this._allProperties.push(property); 693 if (property.disabled) 694 this.__disabledProperties[i] = property; 695 if (!property.active && !property.styleBased) 696 continue; 697 var name = property.name; 698 this[propertyIndex] = name; 699 this._livePropertyMap[name] = property; 700 ++propertyIndex; 701 } 702 this.length = propertyIndex; 703 if ("cssText" in payload) 704 this.cssText = payload.cssText; 705 } 706 707 /** 708 * @param {Array.<CSSAgent.ShorthandEntry>} shorthandEntries 709 * @return {Object} 710 */ 711 WebInspector.CSSStyleDeclaration.buildShorthandValueMap = function(shorthandEntries) 712 { 713 var result = {}; 714 for (var i = 0; i < shorthandEntries.length; ++i) 715 result[shorthandEntries[i].name] = shorthandEntries[i].value; 716 return result; 717 } 718 719 /** 720 * @param {CSSAgent.CSSStyle} payload 721 * @return {WebInspector.CSSStyleDeclaration} 722 */ 723 WebInspector.CSSStyleDeclaration.parsePayload = function(payload) 724 { 725 return new WebInspector.CSSStyleDeclaration(payload); 726 } 727 728 /** 729 * @param {Array.<CSSAgent.CSSComputedStyleProperty>} payload 730 * @return {WebInspector.CSSStyleDeclaration} 731 */ 732 WebInspector.CSSStyleDeclaration.parseComputedStylePayload = function(payload) 733 { 734 var newPayload = /** @type {CSSAgent.CSSStyle} */ ({ cssProperties: [], shorthandEntries: [], width: "", height: "" }); 735 if (payload) 736 newPayload.cssProperties = payload; 737 738 return new WebInspector.CSSStyleDeclaration(newPayload); 739 } 740 741 WebInspector.CSSStyleDeclaration.prototype = { 742 get allProperties() 743 { 744 return this._allProperties; 745 }, 746 747 /** 748 * @param {string} name 749 * @return {WebInspector.CSSProperty|undefined} 750 */ 751 getLiveProperty: function(name) 752 { 753 return this._livePropertyMap[name]; 754 }, 755 756 /** 757 * @param {string} name 758 * @return {string} 759 */ 760 getPropertyValue: function(name) 761 { 762 var property = this._livePropertyMap[name]; 763 return property ? property.value : ""; 764 }, 765 766 /** 767 * @param {string} name 768 * @return {string} 769 */ 770 getPropertyPriority: function(name) 771 { 772 var property = this._livePropertyMap[name]; 773 return property ? property.priority : ""; 774 }, 775 776 /** 777 * @param {string} name 778 * @return {boolean} 779 */ 780 isPropertyImplicit: function(name) 781 { 782 var property = this._livePropertyMap[name]; 783 return property ? property.implicit : ""; 784 }, 785 786 /** 787 * @param {string} name 788 * @return {Array.<WebInspector.CSSProperty>} 789 */ 790 longhandProperties: function(name) 791 { 792 var longhands = WebInspector.CSSMetadata.cssPropertiesMetainfo.longhands(name); 793 var result = []; 794 for (var i = 0; longhands && i < longhands.length; ++i) { 795 var property = this._livePropertyMap[longhands[i]]; 796 if (property) 797 result.push(property); 798 } 799 return result; 800 }, 801 802 /** 803 * @param {string} shorthandProperty 804 * @return {string} 805 */ 806 shorthandValue: function(shorthandProperty) 807 { 808 return this._shorthandValues[shorthandProperty]; 809 }, 810 811 /** 812 * @param {number} index 813 * @return {?WebInspector.CSSProperty} 814 */ 815 propertyAt: function(index) 816 { 817 return (index < this.allProperties.length) ? this.allProperties[index] : null; 818 }, 819 820 /** 821 * @return {number} 822 */ 823 pastLastSourcePropertyIndex: function() 824 { 825 for (var i = this.allProperties.length - 1; i >= 0; --i) { 826 var property = this.allProperties[i]; 827 if (property.active || property.disabled) 828 return i + 1; 829 } 830 return 0; 831 }, 832 833 /** 834 * @param {number=} index 835 */ 836 newBlankProperty: function(index) 837 { 838 index = (typeof index === "undefined") ? this.pastLastSourcePropertyIndex() : index; 839 return new WebInspector.CSSProperty(this, index, "", "", "", "active", true, false, ""); 840 }, 841 842 /** 843 * @param {number} index 844 * @param {string} name 845 * @param {string} value 846 * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback 847 */ 848 insertPropertyAt: function(index, name, value, userCallback) 849 { 850 /** 851 * @param {?string} error 852 * @param {CSSAgent.CSSStyle} payload 853 */ 854 function callback(error, payload) 855 { 856 WebInspector.cssModel._pendingCommandsMajorState.pop(); 857 if (!userCallback) 858 return; 859 860 if (error) { 861 console.error(error); 862 userCallback(null); 863 } else 864 userCallback(WebInspector.CSSStyleDeclaration.parsePayload(payload)); 865 } 866 867 if (!this.id) 868 throw "No style id"; 869 870 WebInspector.cssModel._pendingCommandsMajorState.push(true); 871 CSSAgent.setPropertyText(this.id, index, name + ": " + value + ";", false, callback.bind(this)); 872 }, 873 874 /** 875 * @param {string} name 876 * @param {string} value 877 * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback 878 */ 879 appendProperty: function(name, value, userCallback) 880 { 881 this.insertPropertyAt(this.allProperties.length, name, value, userCallback); 882 }, 883 884 /** 885 * @param {string} text 886 * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback 887 */ 888 setText: function(text, userCallback) 889 { 890 /** 891 * @param {?string} error 892 * @param {CSSAgent.CSSStyle} payload 893 */ 894 function callback(error, payload) 895 { 896 WebInspector.cssModel._pendingCommandsMajorState.pop(); 897 if (!userCallback) 898 return; 899 900 if (error) { 901 console.error(error); 902 userCallback(null); 903 } else 904 userCallback(WebInspector.CSSStyleDeclaration.parsePayload(payload)); 905 } 906 907 if (!this.id) 908 throw "No style id"; 909 910 if (typeof this.cssText === "undefined") { 911 userCallback(null); 912 return; 913 } 914 915 WebInspector.cssModel._pendingCommandsMajorState.push(true); 916 CSSAgent.setStyleText(this.id, text, callback); 917 } 918 } 919 920 /** 921 * @constructor 922 * @param {CSSAgent.CSSRule} payload 923 * @param {Array.<number>=} matchingSelectors 924 */ 925 WebInspector.CSSRule = function(payload, matchingSelectors) 926 { 927 this.id = payload.ruleId; 928 if (matchingSelectors) 929 this.matchingSelectors = matchingSelectors; 930 this.selectors = payload.selectorList.selectors; 931 this.selectorText = this.selectors.join(", "); 932 this.selectorRange = payload.selectorList.range; 933 this.sourceURL = payload.sourceURL; 934 this.origin = payload.origin; 935 this.style = WebInspector.CSSStyleDeclaration.parsePayload(payload.style); 936 this.style.parentRule = this; 937 if (payload.media) 938 this.media = WebInspector.CSSMedia.parseMediaArrayPayload(payload.media); 939 this._setRawLocationAndFrameId(); 940 } 941 942 /** 943 * @param {CSSAgent.CSSRule} payload 944 * @param {Array.<number>=} matchingIndices 945 * @return {WebInspector.CSSRule} 946 */ 947 WebInspector.CSSRule.parsePayload = function(payload, matchingIndices) 948 { 949 return new WebInspector.CSSRule(payload, matchingIndices); 950 } 951 952 WebInspector.CSSRule.prototype = { 953 _setRawLocationAndFrameId: function() 954 { 955 if (!this.id) 956 return; 957 var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(this.id.styleSheetId); 958 this.frameId = styleSheetHeader.frameId; 959 var url = styleSheetHeader.resourceURL(); 960 if (!url) 961 return; 962 this.rawLocation = new WebInspector.CSSLocation(url, this.lineNumberInSource(), this.columnNumberInSource()); 963 }, 964 965 /** 966 * @return {string} 967 */ 968 resourceURL: function() 969 { 970 if (!this.id) 971 return ""; 972 var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(this.id.styleSheetId); 973 return styleSheetHeader.resourceURL(); 974 }, 975 976 /** 977 * @return {number} 978 */ 979 lineNumberInSource: function() 980 { 981 if (!this.selectorRange) 982 return 0; 983 var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(this.id.styleSheetId); 984 return styleSheetHeader.lineNumberInSource(this.selectorRange.startLine); 985 }, 986 987 /** 988 * @return {number|undefined} 989 */ 990 columnNumberInSource: function() 991 { 992 if (!this.selectorRange) 993 return undefined; 994 var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(this.id.styleSheetId); 995 console.assert(styleSheetHeader); 996 return styleSheetHeader.columnNumberInSource(this.selectorRange.startLine, this.selectorRange.startColumn); 997 }, 998 999 get isUserAgent() 1000 { 1001 return this.origin === "user-agent"; 1002 }, 1003 1004 get isUser() 1005 { 1006 return this.origin === "user"; 1007 }, 1008 1009 get isViaInspector() 1010 { 1011 return this.origin === "inspector"; 1012 }, 1013 1014 get isRegular() 1015 { 1016 return this.origin === "regular"; 1017 } 1018 } 1019 1020 /** 1021 * @constructor 1022 * @param {?WebInspector.CSSStyleDeclaration} ownerStyle 1023 * @param {number} index 1024 * @param {string} name 1025 * @param {string} value 1026 * @param {?string} priority 1027 * @param {string} status 1028 * @param {boolean} parsedOk 1029 * @param {boolean} implicit 1030 * @param {?string=} text 1031 * @param {CSSAgent.SourceRange=} range 1032 */ 1033 WebInspector.CSSProperty = function(ownerStyle, index, name, value, priority, status, parsedOk, implicit, text, range) 1034 { 1035 this.ownerStyle = ownerStyle; 1036 this.index = index; 1037 this.name = name; 1038 this.value = value; 1039 this.priority = priority; 1040 this.status = status; 1041 this.parsedOk = parsedOk; 1042 this.implicit = implicit; 1043 this.text = text; 1044 this.range = range; 1045 } 1046 1047 /** 1048 * @param {?WebInspector.CSSStyleDeclaration} ownerStyle 1049 * @param {number} index 1050 * @param {CSSAgent.CSSProperty} payload 1051 * @return {WebInspector.CSSProperty} 1052 */ 1053 WebInspector.CSSProperty.parsePayload = function(ownerStyle, index, payload) 1054 { 1055 // The following default field values are used in the payload: 1056 // priority: "" 1057 // parsedOk: true 1058 // implicit: false 1059 // status: "style" 1060 var result = new WebInspector.CSSProperty( 1061 ownerStyle, index, payload.name, payload.value, payload.priority || "", payload.status || "style", ("parsedOk" in payload) ? !!payload.parsedOk : true, !!payload.implicit, payload.text, payload.range); 1062 return result; 1063 } 1064 1065 WebInspector.CSSProperty.prototype = { 1066 get propertyText() 1067 { 1068 if (this.text !== undefined) 1069 return this.text; 1070 1071 if (this.name === "") 1072 return ""; 1073 return this.name + ": " + this.value + (this.priority ? " !" + this.priority : "") + ";"; 1074 }, 1075 1076 get isLive() 1077 { 1078 return this.active || this.styleBased; 1079 }, 1080 1081 get active() 1082 { 1083 return this.status === "active"; 1084 }, 1085 1086 get styleBased() 1087 { 1088 return this.status === "style"; 1089 }, 1090 1091 get inactive() 1092 { 1093 return this.status === "inactive"; 1094 }, 1095 1096 get disabled() 1097 { 1098 return this.status === "disabled"; 1099 }, 1100 1101 /** 1102 * Replaces "propertyName: propertyValue [!important];" in the stylesheet by an arbitrary propertyText. 1103 * 1104 * @param {string} propertyText 1105 * @param {boolean} majorChange 1106 * @param {boolean} overwrite 1107 * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback 1108 */ 1109 setText: function(propertyText, majorChange, overwrite, userCallback) 1110 { 1111 /** 1112 * @param {?WebInspector.CSSStyleDeclaration} style 1113 */ 1114 function enabledCallback(style) 1115 { 1116 if (userCallback) 1117 userCallback(style); 1118 } 1119 1120 /** 1121 * @param {?string} error 1122 * @param {?CSSAgent.CSSStyle} stylePayload 1123 */ 1124 function callback(error, stylePayload) 1125 { 1126 WebInspector.cssModel._pendingCommandsMajorState.pop(); 1127 if (!error) { 1128 if (majorChange) 1129 WebInspector.domAgent.markUndoableState(); 1130 this.text = propertyText; 1131 var style = WebInspector.CSSStyleDeclaration.parsePayload(stylePayload); 1132 var newProperty = style.allProperties[this.index]; 1133 1134 if (newProperty && this.disabled && !propertyText.match(/^\s*$/)) { 1135 newProperty.setDisabled(false, enabledCallback); 1136 return; 1137 } 1138 1139 if (userCallback) 1140 userCallback(style); 1141 } else { 1142 if (userCallback) 1143 userCallback(null); 1144 } 1145 } 1146 1147 if (!this.ownerStyle) 1148 throw "No ownerStyle for property"; 1149 1150 if (!this.ownerStyle.id) 1151 throw "No owner style id"; 1152 1153 // An index past all the properties adds a new property to the style. 1154 WebInspector.cssModel._pendingCommandsMajorState.push(majorChange); 1155 CSSAgent.setPropertyText(this.ownerStyle.id, this.index, propertyText, overwrite, callback.bind(this)); 1156 }, 1157 1158 /** 1159 * @param {string} newValue 1160 * @param {boolean} majorChange 1161 * @param {boolean} overwrite 1162 * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback 1163 */ 1164 setValue: function(newValue, majorChange, overwrite, userCallback) 1165 { 1166 var text = this.name + ": " + newValue + (this.priority ? " !" + this.priority : "") + ";" 1167 this.setText(text, majorChange, overwrite, userCallback); 1168 }, 1169 1170 /** 1171 * @param {boolean} disabled 1172 * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback 1173 */ 1174 setDisabled: function(disabled, userCallback) 1175 { 1176 if (!this.ownerStyle && userCallback) 1177 userCallback(null); 1178 if (disabled === this.disabled && userCallback) 1179 userCallback(this.ownerStyle); 1180 1181 /** 1182 * @param {?string} error 1183 * @param {CSSAgent.CSSStyle} stylePayload 1184 */ 1185 function callback(error, stylePayload) 1186 { 1187 WebInspector.cssModel._pendingCommandsMajorState.pop(); 1188 if (error) { 1189 if (userCallback) 1190 userCallback(null); 1191 return; 1192 } 1193 WebInspector.domAgent.markUndoableState(); 1194 if (userCallback) { 1195 var style = WebInspector.CSSStyleDeclaration.parsePayload(stylePayload); 1196 userCallback(style); 1197 } 1198 } 1199 1200 if (!this.ownerStyle.id) 1201 throw "No owner style id"; 1202 1203 WebInspector.cssModel._pendingCommandsMajorState.push(false); 1204 CSSAgent.toggleProperty(this.ownerStyle.id, this.index, disabled, callback.bind(this)); 1205 }, 1206 1207 /** 1208 * @param {boolean} forName 1209 * @return {WebInspector.UILocation} 1210 */ 1211 uiLocation: function(forName) 1212 { 1213 if (!this.range || !this.ownerStyle || !this.ownerStyle.parentRule) 1214 return null; 1215 1216 var url = this.ownerStyle.parentRule.resourceURL(); 1217 if (!url) 1218 return null; 1219 1220 var range = this.range; 1221 var line = forName ? range.startLine : range.endLine; 1222 // End of range is exclusive, so subtract 1 from the end offset. 1223 var column = forName ? range.startColumn : range.endColumn - (this.text && this.text.endsWith(";") ? 2 : 1); 1224 var rawLocation = new WebInspector.CSSLocation(url, line, column); 1225 return WebInspector.cssModel.rawLocationToUILocation(rawLocation); 1226 } 1227 } 1228 1229 /** 1230 * @constructor 1231 * @param {CSSAgent.CSSMedia} payload 1232 */ 1233 WebInspector.CSSMedia = function(payload) 1234 { 1235 this.text = payload.text; 1236 this.source = payload.source; 1237 this.sourceURL = payload.sourceURL || ""; 1238 this.range = payload.range; 1239 this.parentStyleSheetId = payload.parentStyleSheetId; 1240 } 1241 1242 WebInspector.CSSMedia.Source = { 1243 LINKED_SHEET: "linkedSheet", 1244 INLINE_SHEET: "inlineSheet", 1245 MEDIA_RULE: "mediaRule", 1246 IMPORT_RULE: "importRule" 1247 }; 1248 1249 /** 1250 * @param {CSSAgent.CSSMedia} payload 1251 * @return {WebInspector.CSSMedia} 1252 */ 1253 WebInspector.CSSMedia.parsePayload = function(payload) 1254 { 1255 return new WebInspector.CSSMedia(payload); 1256 } 1257 1258 /** 1259 * @param {Array.<CSSAgent.CSSMedia>} payload 1260 * @return {Array.<WebInspector.CSSMedia>} 1261 */ 1262 WebInspector.CSSMedia.parseMediaArrayPayload = function(payload) 1263 { 1264 var result = []; 1265 for (var i = 0; i < payload.length; ++i) 1266 result.push(WebInspector.CSSMedia.parsePayload(payload[i])); 1267 return result; 1268 } 1269 1270 WebInspector.CSSMedia.prototype = { 1271 /** 1272 * @return {number|undefined} 1273 */ 1274 lineNumberInSource: function() 1275 { 1276 if (!this.range) 1277 return undefined; 1278 var header = this.header(); 1279 if (!header) 1280 return undefined; 1281 return header.lineNumberInSource(this.range.startLine); 1282 }, 1283 1284 /** 1285 * @return {number|undefined} 1286 */ 1287 columnNumberInSource: function() 1288 { 1289 if (!this.range) 1290 return undefined; 1291 var header = this.header(); 1292 if (!header) 1293 return undefined; 1294 return header.columnNumberInSource(this.range.startLine, this.range.startColumn); 1295 }, 1296 1297 /** 1298 * @return {?WebInspector.CSSStyleSheetHeader} 1299 */ 1300 header: function() 1301 { 1302 return this.parentStyleSheetId ? WebInspector.cssModel.styleSheetHeaderForId(this.parentStyleSheetId) : null; 1303 } 1304 } 1305 1306 /** 1307 * @constructor 1308 * @implements {WebInspector.ContentProvider} 1309 * @param {CSSAgent.CSSStyleSheetHeader} payload 1310 */ 1311 WebInspector.CSSStyleSheetHeader = function(payload) 1312 { 1313 this.id = payload.styleSheetId; 1314 this.frameId = payload.frameId; 1315 this.sourceURL = payload.sourceURL; 1316 this.hasSourceURL = !!payload.hasSourceURL; 1317 this.sourceMapURL = payload.sourceMapURL; 1318 this.origin = payload.origin; 1319 this.title = payload.title; 1320 this.disabled = payload.disabled; 1321 this.isInline = payload.isInline; 1322 this.startLine = payload.startLine; 1323 this.startColumn = payload.startColumn; 1324 /** @type {!Set.<!WebInspector.CSSStyleModel.LiveLocation>} */ 1325 this._locations = new Set(); 1326 /** @type {!Array.<!WebInspector.SourceMapping>} */ 1327 this._sourceMappings = []; 1328 } 1329 1330 WebInspector.CSSStyleSheetHeader.prototype = { 1331 /** 1332 * @return {string} 1333 */ 1334 resourceURL: function() 1335 { 1336 return this.origin === "inspector" ? this._viaInspectorResourceURL() : this.sourceURL; 1337 }, 1338 1339 /** 1340 * @param {WebInspector.CSSLocation} rawLocation 1341 * @param {function(WebInspector.UILocation):(boolean|undefined)} updateDelegate 1342 * @return {?WebInspector.LiveLocation} 1343 */ 1344 createLiveLocation: function(rawLocation, updateDelegate) 1345 { 1346 var location = new WebInspector.CSSStyleModel.LiveLocation(rawLocation, updateDelegate, this); 1347 this._locations.add(location); 1348 location.update(); 1349 return location; 1350 }, 1351 1352 updateLocations: function() 1353 { 1354 var items = this._locations.items(); 1355 for (var i = 0; i < items.length; ++i) 1356 items[i].update(); 1357 }, 1358 1359 /** 1360 * @param {!WebInspector.CSSStyleModel.LiveLocation} location 1361 */ 1362 _removeLocation: function(location) 1363 { 1364 this._locations.remove(location); 1365 }, 1366 1367 /** 1368 * @param {number} lineNumber 1369 * @param {number=} columnNumber 1370 * @return {?WebInspector.UILocation} 1371 */ 1372 rawLocationToUILocation: function(lineNumber, columnNumber) 1373 { 1374 var uiLocation; 1375 var rawLocation = new WebInspector.CSSLocation(this.resourceURL(), lineNumber, columnNumber || 0); 1376 for (var i = this._sourceMappings.length - 1; !uiLocation && i >= 0; --i) 1377 uiLocation = this._sourceMappings[i].rawLocationToUILocation(rawLocation); 1378 return uiLocation || null; 1379 }, 1380 1381 /** 1382 * @param {!WebInspector.SourceMapping} sourceMapping 1383 */ 1384 pushSourceMapping: function(sourceMapping) 1385 { 1386 this._sourceMappings.push(sourceMapping); 1387 this.updateLocations(); 1388 }, 1389 1390 /** 1391 * @return {string} 1392 */ 1393 _key: function() 1394 { 1395 return this.frameId + ":" + this.resourceURL(); 1396 }, 1397 1398 /** 1399 * @return {string} 1400 */ 1401 _viaInspectorResourceURL: function() 1402 { 1403 var frame = WebInspector.resourceTreeModel.frameForId(this.frameId); 1404 console.assert(frame); 1405 var parsedURL = new WebInspector.ParsedURL(frame.url); 1406 var fakeURL = "inspector://" + parsedURL.host + parsedURL.folderPathComponents; 1407 if (!fakeURL.endsWith("/")) 1408 fakeURL += "/"; 1409 fakeURL += "inspector-stylesheet"; 1410 return fakeURL; 1411 }, 1412 1413 /** 1414 * @param {number} lineNumberInStyleSheet 1415 * @return {number} 1416 */ 1417 lineNumberInSource: function(lineNumberInStyleSheet) 1418 { 1419 return this.startLine + lineNumberInStyleSheet; 1420 }, 1421 1422 /** 1423 * @param {number} lineNumberInStyleSheet 1424 * @param {number} columnNumberInStyleSheet 1425 * @return {number|undefined} 1426 */ 1427 columnNumberInSource: function(lineNumberInStyleSheet, columnNumberInStyleSheet) 1428 { 1429 return (lineNumberInStyleSheet ? 0 : this.startColumn) + columnNumberInStyleSheet; 1430 }, 1431 1432 /** 1433 * @override 1434 */ 1435 contentURL: function() 1436 { 1437 return this.resourceURL(); 1438 }, 1439 1440 /** 1441 * @override 1442 */ 1443 contentType: function() 1444 { 1445 return WebInspector.resourceTypes.Stylesheet; 1446 }, 1447 1448 /** 1449 * @override 1450 */ 1451 requestContent: function(callback) 1452 { 1453 CSSAgent.getStyleSheetText(this.id, textCallback.bind(this)); 1454 1455 function textCallback(error, text) 1456 { 1457 if (error) { 1458 WebInspector.log("Failed to get text for stylesheet " + this.id + ": " + error); 1459 text = ""; 1460 // Fall through. 1461 } 1462 callback(text, false, "text/css"); 1463 } 1464 }, 1465 1466 /** 1467 * @override 1468 */ 1469 searchInContent: function(query, caseSensitive, isRegex, callback) 1470 { 1471 function performSearch(content) 1472 { 1473 callback(WebInspector.ContentProvider.performSearchInContent(content, query, caseSensitive, isRegex)); 1474 } 1475 1476 // searchInContent should call back later. 1477 this.requestContent(performSearch); 1478 } 1479 } 1480 1481 /** 1482 * @constructor 1483 * @param {CSSAgent.CSSStyleSheetBody} payload 1484 */ 1485 WebInspector.CSSStyleSheet = function(payload) 1486 { 1487 this.id = payload.styleSheetId; 1488 this.rules = []; 1489 this.styles = {}; 1490 for (var i = 0; i < payload.rules.length; ++i) { 1491 var rule = WebInspector.CSSRule.parsePayload(payload.rules[i]); 1492 this.rules.push(rule); 1493 if (rule.style) 1494 this.styles[rule.style.id] = rule.style; 1495 } 1496 if ("text" in payload) 1497 this._text = payload.text; 1498 } 1499 1500 /** 1501 * @param {CSSAgent.StyleSheetId} styleSheetId 1502 * @param {function(?WebInspector.CSSStyleSheet)} userCallback 1503 */ 1504 WebInspector.CSSStyleSheet.createForId = function(styleSheetId, userCallback) 1505 { 1506 /** 1507 * @param {?string} error 1508 * @param {CSSAgent.CSSStyleSheetBody} styleSheetPayload 1509 */ 1510 function callback(error, styleSheetPayload) 1511 { 1512 if (error) 1513 userCallback(null); 1514 else 1515 userCallback(new WebInspector.CSSStyleSheet(styleSheetPayload)); 1516 } 1517 CSSAgent.getStyleSheet(styleSheetId, callback.bind(this)); 1518 } 1519 1520 WebInspector.CSSStyleSheet.prototype = { 1521 /** 1522 * @return {string|undefined} 1523 */ 1524 getText: function() 1525 { 1526 return this._text; 1527 }, 1528 1529 /** 1530 * @param {string} newText 1531 * @param {boolean} majorChange 1532 * @param {function(?string)=} userCallback 1533 */ 1534 setText: function(newText, majorChange, userCallback) 1535 { 1536 /** 1537 * @param {?string} error 1538 */ 1539 function callback(error) 1540 { 1541 if (!error) 1542 WebInspector.domAgent.markUndoableState(); 1543 1544 WebInspector.cssModel._pendingCommandsMajorState.pop(); 1545 if (userCallback) 1546 userCallback(error); 1547 } 1548 1549 WebInspector.cssModel._pendingCommandsMajorState.push(majorChange); 1550 CSSAgent.setStyleSheetText(this.id, newText, callback.bind(this)); 1551 } 1552 } 1553 1554 /** 1555 * @constructor 1556 * @implements {CSSAgent.Dispatcher} 1557 * @param {WebInspector.CSSStyleModel} cssModel 1558 */ 1559 WebInspector.CSSDispatcher = function(cssModel) 1560 { 1561 this._cssModel = cssModel; 1562 } 1563 1564 WebInspector.CSSDispatcher.prototype = { 1565 mediaQueryResultChanged: function() 1566 { 1567 this._cssModel.mediaQueryResultChanged(); 1568 }, 1569 1570 /** 1571 * @param {CSSAgent.StyleSheetId} styleSheetId 1572 */ 1573 styleSheetChanged: function(styleSheetId) 1574 { 1575 this._cssModel._fireStyleSheetChanged(styleSheetId); 1576 }, 1577 1578 /** 1579 * @param {CSSAgent.CSSStyleSheetHeader} header 1580 */ 1581 styleSheetAdded: function(header) 1582 { 1583 this._cssModel._styleSheetAdded(header); 1584 }, 1585 1586 /** 1587 * @param {CSSAgent.StyleSheetId} id 1588 */ 1589 styleSheetRemoved: function(id) 1590 { 1591 this._cssModel._styleSheetRemoved(id); 1592 }, 1593 1594 /** 1595 * @param {CSSAgent.NamedFlow} namedFlowPayload 1596 */ 1597 namedFlowCreated: function(namedFlowPayload) 1598 { 1599 this._cssModel._namedFlowCreated(namedFlowPayload); 1600 }, 1601 1602 /** 1603 * @param {DOMAgent.NodeId} documentNodeId 1604 * @param {string} flowName 1605 */ 1606 namedFlowRemoved: function(documentNodeId, flowName) 1607 { 1608 this._cssModel._namedFlowRemoved(documentNodeId, flowName); 1609 }, 1610 1611 /** 1612 * @param {CSSAgent.NamedFlow} namedFlowPayload 1613 */ 1614 regionLayoutUpdated: function(namedFlowPayload) 1615 { 1616 this._cssModel._regionLayoutUpdated(namedFlowPayload); 1617 }, 1618 1619 /** 1620 * @param {CSSAgent.NamedFlow} namedFlowPayload 1621 */ 1622 regionOversetChanged: function(namedFlowPayload) 1623 { 1624 this._cssModel._regionOversetChanged(namedFlowPayload); 1625 } 1626 } 1627 1628 /** 1629 * @constructor 1630 * @param {CSSAgent.NamedFlow} payload 1631 */ 1632 WebInspector.NamedFlow = function(payload) 1633 { 1634 this.documentNodeId = payload.documentNodeId; 1635 this.name = payload.name; 1636 this.overset = payload.overset; 1637 this.content = payload.content; 1638 this.regions = payload.regions; 1639 } 1640 1641 /** 1642 * @param {CSSAgent.NamedFlow} payload 1643 * @return {WebInspector.NamedFlow} 1644 */ 1645 WebInspector.NamedFlow.parsePayload = function(payload) 1646 { 1647 return new WebInspector.NamedFlow(payload); 1648 } 1649 1650 /** 1651 * @constructor 1652 * @param {Array.<CSSAgent.NamedFlow>} payload 1653 */ 1654 WebInspector.NamedFlowCollection = function(payload) 1655 { 1656 /** @type {Object.<string, WebInspector.NamedFlow>} */ 1657 this.namedFlowMap = {}; 1658 1659 for (var i = 0; i < payload.length; ++i) { 1660 var namedFlow = WebInspector.NamedFlow.parsePayload(payload[i]); 1661 this.namedFlowMap[namedFlow.name] = namedFlow; 1662 } 1663 } 1664 1665 WebInspector.NamedFlowCollection.prototype = { 1666 /** 1667 * @param {WebInspector.NamedFlow} namedFlow 1668 */ 1669 _appendNamedFlow: function(namedFlow) 1670 { 1671 this.namedFlowMap[namedFlow.name] = namedFlow; 1672 }, 1673 1674 /** 1675 * @param {string} flowName 1676 */ 1677 _removeNamedFlow: function(flowName) 1678 { 1679 delete this.namedFlowMap[flowName]; 1680 }, 1681 1682 /** 1683 * @param {string} flowName 1684 * @return {WebInspector.NamedFlow} 1685 */ 1686 flowByName: function(flowName) 1687 { 1688 var namedFlow = this.namedFlowMap[flowName]; 1689 1690 if (!namedFlow) 1691 return null; 1692 return namedFlow; 1693 } 1694 } 1695 /** 1696 * @type {WebInspector.CSSStyleModel} 1697 */ 1698 WebInspector.cssModel = null; 1699