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