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