| /* |
| * Copyright (C) 2013 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| WebInspector.CSSStyleManager = function() |
| { |
| // FIXME: Convert this to a WebInspector.Object subclass, and call super(). |
| // WebInspector.Object.call(this); |
| |
| if (window.CSSAgent) |
| CSSAgent.enable(); |
| |
| WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); |
| WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ResourceWasAdded, this._resourceAdded, this); |
| WebInspector.Resource.addEventListener(WebInspector.SourceCode.Event.ContentDidChange, this._resourceContentDidChange, this); |
| WebInspector.Resource.addEventListener(WebInspector.Resource.Event.TypeDidChange, this._resourceTypeDidChange, this); |
| |
| WebInspector.DOMNode.addEventListener(WebInspector.DOMNode.Event.AttributeModified, this._nodeAttributesDidChange, this); |
| WebInspector.DOMNode.addEventListener(WebInspector.DOMNode.Event.AttributeRemoved, this._nodeAttributesDidChange, this); |
| WebInspector.DOMNode.addEventListener(WebInspector.DOMNode.Event.EnabledPseudoClassesChanged, this._nodePseudoClassesDidChange, this); |
| |
| this._colorFormatSetting = new WebInspector.Setting("default-color-format", WebInspector.Color.Format.Original); |
| |
| this._styleSheetIdentifierMap = {}; |
| this._styleSheetFrameURLMap = {}; |
| this._nodeStylesMap = {}; |
| }; |
| |
| WebInspector.CSSStyleManager.ForceablePseudoClasses = ["active", "focus", "hover", "visited"]; |
| |
| WebInspector.CSSStyleManager.prototype = { |
| constructor: WebInspector.CSSStyleManager, |
| __proto__: WebInspector.Object.prototype, |
| |
| // Public |
| |
| get preferredColorFormat() |
| { |
| return this._colorFormatSetting.value; |
| }, |
| |
| canForcePseudoClasses: function() |
| { |
| return window.CSSAgent && !!CSSAgent.forcePseudoState; |
| }, |
| |
| propertyNameHasOtherVendorPrefix: function(name) |
| { |
| if (!name || name.length < 4 || name.charAt(0) !== "-") |
| return false; |
| |
| var match = name.match(/^(?:-moz-|-ms-|-o-|-epub-)/); |
| if (!match) |
| return false; |
| |
| return true; |
| }, |
| |
| propertyValueHasOtherVendorKeyword: function(value) |
| { |
| var match = value.match(/(?:-moz-|-ms-|-o-|-epub-)[-\w]+/); |
| if (!match) |
| return false; |
| |
| return true; |
| }, |
| |
| canonicalNameForPropertyName: function(name) |
| { |
| if (!name || name.length < 8 || name.charAt(0) !== "-") |
| return name; |
| |
| var match = name.match(/^(?:-webkit-|-khtml-|-apple-)(.+)/); |
| if (!match) |
| return name; |
| |
| return match[1]; |
| }, |
| |
| styleSheetForIdentifier: function(id) |
| { |
| if (id in this._styleSheetIdentifierMap) |
| return this._styleSheetIdentifierMap[id]; |
| |
| var styleSheet = new WebInspector.CSSStyleSheet(id); |
| this._styleSheetIdentifierMap[id] = styleSheet; |
| return styleSheet; |
| }, |
| |
| stylesForNode: function(node) |
| { |
| if (node.id in this._nodeStylesMap) |
| return this._nodeStylesMap[node.id]; |
| |
| var styles = new WebInspector.DOMNodeStyles(node); |
| this._nodeStylesMap[node.id] = styles; |
| return styles; |
| }, |
| |
| // Protected |
| |
| mediaQueryResultChanged: function() |
| { |
| // Called from WebInspector.CSSObserver. |
| |
| for (var key in this._nodeStylesMap) |
| this._nodeStylesMap[key].mediaQueryResultDidChange(); |
| }, |
| |
| styleSheetChanged: function(styleSheetIdentifier) |
| { |
| // Called from WebInspector.CSSObserver. |
| |
| var styleSheet = this.styleSheetForIdentifier(styleSheetIdentifier); |
| console.assert(styleSheet); |
| |
| styleSheet.noteContentDidChange(); |
| |
| this._updateResourceContent(styleSheet); |
| }, |
| |
| // Private |
| |
| _nodePseudoClassesDidChange: function(event) |
| { |
| var node = event.target; |
| |
| for (var key in this._nodeStylesMap) { |
| var nodeStyles = this._nodeStylesMap[key]; |
| if (nodeStyles.node !== node && !nodeStyles.node.isDescendant(node)) |
| continue; |
| nodeStyles.pseudoClassesDidChange(node); |
| } |
| }, |
| |
| _nodeAttributesDidChange: function(event) |
| { |
| var node = event.target; |
| |
| for (var key in this._nodeStylesMap) { |
| var nodeStyles = this._nodeStylesMap[key]; |
| if (nodeStyles.node !== node && !nodeStyles.node.isDescendant(node)) |
| continue; |
| nodeStyles.attributeDidChange(node, event.data.name); |
| } |
| }, |
| |
| _mainResourceDidChange: function(event) |
| { |
| console.assert(event.target instanceof WebInspector.Frame); |
| |
| if (!event.target.isMainFrame()) |
| return; |
| |
| // Clear our maps when the main frame navigates. |
| |
| this._styleSheetIdentifierMap = {}; |
| this._styleSheetFrameURLMap = {}; |
| this._nodeStylesMap = {}; |
| }, |
| |
| _resourceAdded: function(event) |
| { |
| console.assert(event.target instanceof WebInspector.Frame); |
| |
| var resource = event.data.resource; |
| console.assert(resource); |
| |
| if (resource.type !== WebInspector.Resource.Type.Stylesheet) |
| return; |
| |
| this._clearStyleSheetsForResource(resource); |
| }, |
| |
| _resourceTypeDidChange: function(event) |
| { |
| console.assert(event.target instanceof WebInspector.Resource); |
| |
| var resource = event.target; |
| if (resource.type !== WebInspector.Resource.Type.Stylesheet) |
| return; |
| |
| this._clearStyleSheetsForResource(resource); |
| }, |
| |
| _clearStyleSheetsForResource: function(resource) |
| { |
| // Clear known stylesheets for this URL and frame. This will cause the stylesheets to |
| // be updated next time _fetchInfoForAllStyleSheets is called. |
| // COMPATIBILITY (iOS 6): The frame's id was not available for the key, so delete just the url too. |
| delete this._styleSheetFrameURLMap[this._frameURLMapKey(resource.parentFrame, resource.url)]; |
| delete this._styleSheetFrameURLMap[resource.url]; |
| }, |
| |
| _frameURLMapKey: function(frame, url) |
| { |
| return (frame ? frame.id + ":" : "") + url; |
| }, |
| |
| _lookupStyleSheetForResource: function(resource, callback) |
| { |
| this._lookupStyleSheet(resource.parentFrame, resource.url, callback); |
| }, |
| |
| _lookupStyleSheet: function(frame, url, callback) |
| { |
| console.assert(frame instanceof WebInspector.Frame); |
| |
| function syleSheetsFetched() |
| { |
| callback(this._styleSheetFrameURLMap[key] || this._styleSheetFrameURLMap[url] || null); |
| } |
| |
| var key = this._frameURLMapKey(frame, url); |
| |
| // COMPATIBILITY (iOS 6): The frame's id was not available for the key, so check for just the url too. |
| if (key in this._styleSheetFrameURLMap || url in this._styleSheetFrameURLMap) |
| callback(this._styleSheetFrameURLMap[key] || this._styleSheetFrameURLMap[url] || null); |
| else |
| this._fetchInfoForAllStyleSheets(syleSheetsFetched.bind(this)); |
| }, |
| |
| _fetchInfoForAllStyleSheets: function(callback) |
| { |
| console.assert(typeof callback === "function"); |
| |
| function processStyleSheets(error, styleSheets) |
| { |
| this._styleSheetFrameURLMap = {}; |
| |
| if (error) { |
| callback(); |
| return; |
| } |
| |
| for (var styleSheetInfo of styleSheets) { |
| // COMPATIBILITY (iOS 6): The info did not have 'frameId', so make parentFrame null in that case. |
| var parentFrame = "frameId" in styleSheetInfo ? WebInspector.frameResourceManager.frameForIdentifier(styleSheetInfo.frameId) : null; |
| |
| var styleSheet = this.styleSheetForIdentifier(styleSheetInfo.styleSheetId); |
| styleSheet.updateInfo(styleSheetInfo.sourceURL, parentFrame); |
| |
| var key = this._frameURLMapKey(parentFrame, styleSheetInfo.sourceURL); |
| this._styleSheetFrameURLMap[key] = styleSheet; |
| } |
| |
| callback(); |
| } |
| |
| CSSAgent.getAllStyleSheets(processStyleSheets.bind(this)); |
| }, |
| |
| _resourceContentDidChange: function(event) |
| { |
| var resource = event.target; |
| if (resource === this._ignoreResourceContentDidChangeEventForResource) |
| return; |
| |
| // Ignore if it isn't a CSS stylesheet. |
| if (resource.type !== WebInspector.Resource.Type.Stylesheet || resource.syntheticMIMEType !== "text/css") |
| return; |
| |
| function applyStyleSheetChanges() |
| { |
| function styleSheetFound(styleSheet) |
| { |
| delete resource.__pendingChangeTimeout; |
| |
| console.assert(styleSheet); |
| if (!styleSheet) |
| return; |
| |
| // To prevent updating a TextEditor's content while the user is typing in it we want to |
| // ignore the next _updateResourceContent call. |
| resource.__ignoreNextUpdateResourceContent = true; |
| |
| WebInspector.branchManager.currentBranch.revisionForRepresentedObject(styleSheet).content = resource.content; |
| } |
| |
| this._lookupStyleSheetForResource(resource, styleSheetFound.bind(this)); |
| } |
| |
| if (resource.__pendingChangeTimeout) |
| clearTimeout(resource.__pendingChangeTimeout); |
| resource.__pendingChangeTimeout = setTimeout(applyStyleSheetChanges.bind(this), 500); |
| }, |
| |
| _updateResourceContent: function(styleSheet) |
| { |
| console.assert(styleSheet); |
| |
| function fetchedStyleSheetContent(parameters) |
| { |
| var styleSheet = parameters.sourceCode; |
| var content = parameters.content; |
| |
| delete styleSheet.__pendingChangeTimeout; |
| |
| console.assert(styleSheet.url); |
| if (!styleSheet.url) |
| return; |
| |
| var resource = null; |
| |
| // COMPATIBILITY (iOS 6): The stylesheet did not always have a frame, so fallback to looking |
| // for the resource in all frames. |
| if (styleSheet.parentFrame) |
| resource = styleSheet.parentFrame.resourceForURL(styleSheet.url); |
| else |
| resource = WebInspector.frameResourceManager.resourceForURL(styleSheet.url); |
| |
| if (!resource) |
| return; |
| |
| // Only try to update stylesheet resources. Other resources, like documents, can contain |
| // multiple stylesheets and we don't have the source ranges to update those. |
| if (resource.type !== WebInspector.Resource.Type.Stylesheet) |
| return; |
| |
| if (resource.__ignoreNextUpdateResourceContent) { |
| delete resource.__ignoreNextUpdateResourceContent; |
| return; |
| } |
| |
| this._ignoreResourceContentDidChangeEventForResource = resource; |
| WebInspector.branchManager.currentBranch.revisionForRepresentedObject(resource).content = content; |
| delete this._ignoreResourceContentDidChangeEventForResource; |
| } |
| |
| function styleSheetReady() |
| { |
| styleSheet.requestContent().then(fetchedStyleSheetContent.bind(this)); |
| } |
| |
| function applyStyleSheetChanges() |
| { |
| if (styleSheet.url) |
| styleSheetReady.call(this); |
| else |
| this._fetchInfoForAllStyleSheets(styleSheetReady.bind(this)); |
| } |
| |
| if (styleSheet.__pendingChangeTimeout) |
| clearTimeout(styleSheet.__pendingChangeTimeout); |
| styleSheet.__pendingChangeTimeout = setTimeout(applyStyleSheetChanges.bind(this), 500); |
| } |
| }; |