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