1/* 2 * Copyright (C) 2013 Apple 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 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26WebInspector.CSSStyleManager = function() 27{ 28 WebInspector.Object.call(this); 29 30 if (window.CSSAgent) 31 CSSAgent.enable(); 32 33 WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); 34 WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ResourceWasAdded, this._resourceAdded, this); 35 WebInspector.Resource.addEventListener(WebInspector.SourceCode.Event.ContentDidChange, this._resourceContentDidChange, this); 36 WebInspector.Resource.addEventListener(WebInspector.Resource.Event.TypeDidChange, this._resourceTypeDidChange, this); 37 38 WebInspector.DOMNode.addEventListener(WebInspector.DOMNode.Event.AttributeModified, this._nodeAttributesDidChange, this); 39 WebInspector.DOMNode.addEventListener(WebInspector.DOMNode.Event.AttributeRemoved, this._nodeAttributesDidChange, this); 40 WebInspector.DOMNode.addEventListener(WebInspector.DOMNode.Event.EnabledPseudoClassesChanged, this._nodePseudoClassesDidChange, this); 41 42 this._colorFormatSetting = new WebInspector.Setting("default-color-format", WebInspector.Color.Format.Original); 43 44 this._styleSheetIdentifierMap = {}; 45 this._styleSheetFrameURLMap = {}; 46 this._nodeStylesMap = {}; 47} 48 49WebInspector.CSSStyleManager.ForceablePseudoClasses = ["active", "focus", "hover", "visited"]; 50 51WebInspector.CSSStyleManager.prototype = { 52 constructor: WebInspector.CSSStyleManager, 53 54 // Public 55 56 get preferredColorFormat() 57 { 58 return this._colorFormatSetting.value; 59 }, 60 61 canForcePseudoClasses: function() 62 { 63 return window.CSSAgent && !!CSSAgent.forcePseudoState; 64 }, 65 66 propertyNameHasOtherVendorPrefix: function(name) 67 { 68 if (!name || name.length < 4 || name.charAt(0) !== "-") 69 return false; 70 71 var match = name.match(/^(?:-moz-|-ms-|-o-|-epub-)/); 72 if (!match) 73 return false; 74 75 return true; 76 }, 77 78 propertyValueHasOtherVendorKeyword: function(value) 79 { 80 var match = value.match(/(?:-moz-|-ms-|-o-|-epub-)[-\w]+/); 81 if (!match) 82 return false; 83 84 return true; 85 }, 86 87 canonicalNameForPropertyName: function(name) 88 { 89 if (!name || name.length < 8 || name.charAt(0) !== "-") 90 return name; 91 92 var match = name.match(/^(?:-webkit-|-khtml-|-apple-)(.+)/); 93 if (!match) 94 return name; 95 96 return match[1]; 97 }, 98 99 styleSheetForIdentifier: function(id) 100 { 101 if (id in this._styleSheetIdentifierMap) 102 return this._styleSheetIdentifierMap[id]; 103 104 var styleSheet = new WebInspector.CSSStyleSheet(id); 105 this._styleSheetIdentifierMap[id] = styleSheet; 106 return styleSheet; 107 }, 108 109 stylesForNode: function(node) 110 { 111 if (node.id in this._nodeStylesMap) 112 return this._nodeStylesMap[node.id]; 113 114 var styles = new WebInspector.DOMNodeStyles(node); 115 this._nodeStylesMap[node.id] = styles; 116 return styles; 117 }, 118 119 // Protected 120 121 mediaQueryResultChanged: function() 122 { 123 // Called from WebInspector.CSSObserver. 124 125 for (var key in this._nodeStylesMap) 126 this._nodeStylesMap[key].mediaQueryResultDidChange(); 127 }, 128 129 styleSheetChanged: function(styleSheetIdentifier) 130 { 131 // Called from WebInspector.CSSObserver. 132 133 var styleSheet = this.styleSheetForIdentifier(styleSheetIdentifier); 134 console.assert(styleSheet); 135 136 styleSheet.noteContentDidChange(); 137 138 this._updateResourceContent(styleSheet); 139 }, 140 141 // Private 142 143 _nodePseudoClassesDidChange: function(event) 144 { 145 var node = event.target; 146 147 for (var key in this._nodeStylesMap) { 148 var nodeStyles = this._nodeStylesMap[key]; 149 if (nodeStyles.node !== node && !nodeStyles.node.isDescendant(node)) 150 continue; 151 nodeStyles.pseudoClassesDidChange(node); 152 } 153 }, 154 155 _nodeAttributesDidChange: function(event) 156 { 157 var node = event.target; 158 159 for (var key in this._nodeStylesMap) { 160 var nodeStyles = this._nodeStylesMap[key]; 161 if (nodeStyles.node !== node && !nodeStyles.node.isDescendant(node)) 162 continue; 163 nodeStyles.attributeDidChange(node, event.data.name); 164 } 165 }, 166 167 _mainResourceDidChange: function(event) 168 { 169 console.assert(event.target instanceof WebInspector.Frame); 170 171 if (!event.target.isMainFrame()) 172 return; 173 174 // Clear our maps when the main frame navigates. 175 176 this._styleSheetIdentifierMap = {}; 177 this._styleSheetFrameURLMap = {}; 178 this._nodeStylesMap = {}; 179 }, 180 181 _resourceAdded: function(event) 182 { 183 console.assert(event.target instanceof WebInspector.Frame); 184 185 var resource = event.data.resource; 186 console.assert(resource); 187 188 if (resource.type !== WebInspector.Resource.Type.Stylesheet) 189 return; 190 191 this._clearStyleSheetsForResource(resource); 192 }, 193 194 _resourceTypeDidChange: function(event) 195 { 196 console.assert(event.target instanceof WebInspector.Resource); 197 198 var resource = event.target; 199 if (resource.type !== WebInspector.Resource.Type.Stylesheet) 200 return; 201 202 this._clearStyleSheetsForResource(resource); 203 }, 204 205 _clearStyleSheetsForResource: function(resource) 206 { 207 // Clear known stylesheets for this URL and frame. This will cause the stylesheets to 208 // be updated next time _fetchInfoForAllStyleSheets is called. 209 // COMPATIBILITY (iOS 6): The frame's id was not available for the key, so delete just the url too. 210 delete this._styleSheetFrameURLMap[this._frameURLMapKey(resource.parentFrame, resource.url)]; 211 delete this._styleSheetFrameURLMap[resource.url]; 212 }, 213 214 _frameURLMapKey: function(frame, url) 215 { 216 return (frame ? frame.id + ":" : "") + url; 217 }, 218 219 _lookupStyleSheetForResource: function(resource, callback) 220 { 221 this._lookupStyleSheet(resource.parentFrame, resource.url, callback); 222 }, 223 224 _lookupStyleSheet: function(frame, url, callback) 225 { 226 console.assert(frame instanceof WebInspector.Frame); 227 228 function syleSheetsFetched() 229 { 230 callback(this._styleSheetFrameURLMap[key] || this._styleSheetFrameURLMap[url] || null); 231 } 232 233 var key = this._frameURLMapKey(frame, url); 234 235 // COMPATIBILITY (iOS 6): The frame's id was not available for the key, so check for just the url too. 236 if (key in this._styleSheetFrameURLMap || url in this._styleSheetFrameURLMap) 237 callback(this._styleSheetFrameURLMap[key] || this._styleSheetFrameURLMap[url] || null); 238 else 239 this._fetchInfoForAllStyleSheets(syleSheetsFetched.bind(this)); 240 }, 241 242 _fetchInfoForAllStyleSheets: function(callback) 243 { 244 console.assert(typeof callback === "function"); 245 246 function processStyleSheets(error, styleSheets) 247 { 248 this._styleSheetFrameURLMap = {}; 249 250 if (error) { 251 callback(); 252 return; 253 } 254 255 for (var i = 0; i < styleSheets.length; ++i) { 256 var styleSheetInfo = styleSheets[i]; 257 258 // COMPATIBILITY (iOS 6): The info did not have 'frameId', so make parentFrame null in that case. 259 var parentFrame = "frameId" in styleSheetInfo ? WebInspector.frameResourceManager.frameForIdentifier(styleSheetInfo.frameId) : null; 260 261 var styleSheet = this.styleSheetForIdentifier(styleSheetInfo.styleSheetId); 262 styleSheet.updateInfo(styleSheetInfo.sourceURL, parentFrame); 263 264 var key = this._frameURLMapKey(parentFrame, styleSheetInfo.sourceURL); 265 this._styleSheetFrameURLMap[key] = styleSheet; 266 } 267 268 callback(); 269 } 270 271 CSSAgent.getAllStyleSheets(processStyleSheets.bind(this)); 272 }, 273 274 _resourceContentDidChange: function(event) 275 { 276 var resource = event.target; 277 if (resource === this._ignoreResourceContentDidChangeEventForResource) 278 return; 279 280 // Ignore if it isn't a CSS stylesheet. 281 if (resource.type !== WebInspector.Resource.Type.Stylesheet || resource.syntheticMIMEType !== "text/css") 282 return; 283 284 function applyStyleSheetChanges() 285 { 286 function styleSheetFound(styleSheet) 287 { 288 delete resource.__pendingChangeTimeout; 289 290 console.assert(styleSheet); 291 if (!styleSheet) 292 return; 293 294 // To prevent updating a TextEditor's content while the user is typing in it we want to 295 // ignore the next _updateResourceContent call. 296 resource.__ignoreNextUpdateResourceContent = true; 297 298 WebInspector.branchManager.currentBranch.revisionForRepresentedObject(styleSheet).content = resource.content; 299 } 300 301 this._lookupStyleSheetForResource(resource, styleSheetFound.bind(this)); 302 } 303 304 if (resource.__pendingChangeTimeout) 305 clearTimeout(resource.__pendingChangeTimeout); 306 resource.__pendingChangeTimeout = setTimeout(applyStyleSheetChanges.bind(this), 500); 307 }, 308 309 _updateResourceContent: function(styleSheet) 310 { 311 console.assert(styleSheet); 312 313 function fetchedStyleSheetContent(styleSheet, content) 314 { 315 delete styleSheet.__pendingChangeTimeout; 316 317 console.assert(styleSheet.url); 318 if (!styleSheet.url) 319 return; 320 321 var resource = null; 322 323 // COMPATIBILITY (iOS 6): The stylesheet did not always have a frame, so fallback to looking 324 // for the resource in all frames. 325 if (styleSheet.parentFrame) 326 resource = styleSheet.parentFrame.resourceForURL(styleSheet.url); 327 else 328 resource = WebInspector.frameResourceManager.resourceForURL(styleSheet.url); 329 330 if (!resource) 331 return; 332 333 // Only try to update stylesheet resources. Other resources, like documents, can contain 334 // multiple stylesheets and we don't have the source ranges to update those. 335 if (resource.type !== WebInspector.Resource.Type.Stylesheet) 336 return; 337 338 if (resource.__ignoreNextUpdateResourceContent) { 339 delete resource.__ignoreNextUpdateResourceContent; 340 return; 341 } 342 343 this._ignoreResourceContentDidChangeEventForResource = resource; 344 WebInspector.branchManager.currentBranch.revisionForRepresentedObject(resource).content = content; 345 delete this._ignoreResourceContentDidChangeEventForResource; 346 } 347 348 function styleSheetReady() 349 { 350 styleSheet.requestContent(fetchedStyleSheetContent.bind(this)); 351 } 352 353 function applyStyleSheetChanges() 354 { 355 if (styleSheet.url) 356 styleSheetReady.call(this); 357 else 358 this._fetchInfoForAllStyleSheets(styleSheetReady.bind(this)); 359 } 360 361 if (styleSheet.__pendingChangeTimeout) 362 clearTimeout(styleSheet.__pendingChangeTimeout); 363 styleSheet.__pendingChangeTimeout = setTimeout(applyStyleSheetChanges.bind(this), 500); 364 } 365} 366 367WebInspector.CSSStyleManager.prototype.__proto__ = WebInspector.Object.prototype; 368