diff options
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/CSSStyleManager.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/CSSStyleManager.js | 366 |
1 files changed, 366 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/CSSStyleManager.js b/Source/WebInspectorUI/UserInterface/CSSStyleManager.js new file mode 100644 index 000000000..ea625081a --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/CSSStyleManager.js @@ -0,0 +1,366 @@ +/* + * 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() +{ + WebInspector.Object.call(this); + + 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, + + // Public + + get preferredColorFormat() + { + return this._colorFormatSetting.value; + }, + + canForcePseudoClasses: function() + { + return !!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 i = 0; i < styleSheets.length; ++i) { + var styleSheetInfo = styleSheets[i]; + + // 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(styleSheet, 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(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); + } +} + +WebInspector.CSSStyleManager.prototype.__proto__ = WebInspector.Object.prototype; |