diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Controllers/CSSStyleManager.js | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Controllers/CSSStyleManager.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Controllers/CSSStyleManager.js | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CSSStyleManager.js b/Source/WebInspectorUI/UserInterface/Controllers/CSSStyleManager.js new file mode 100644 index 000000000..8e101e31f --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Controllers/CSSStyleManager.js @@ -0,0 +1,551 @@ +/* + * 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 = class CSSStyleManager extends WebInspector.Object +{ + constructor() + { + super(); + + 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 = new Map; + this._styleSheetFrameURLMap = new Map; + this._nodeStylesMap = {}; + + // COMPATIBILITY (iOS 9): Legacy backends did not send stylesheet + // added/removed events and must be fetched manually. + this._fetchedInitialStyleSheets = window.CSSAgent && window.CSSAgent.hasEvent("styleSheetAdded"); + } + + // Static + + static protocolStyleSheetOriginToEnum(origin) + { + switch (origin) { + case CSSAgent.StyleSheetOrigin.Regular: + return WebInspector.CSSStyleSheet.Type.Author; + case CSSAgent.StyleSheetOrigin.User: + return WebInspector.CSSStyleSheet.Type.User; + case CSSAgent.StyleSheetOrigin.UserAgent: + return WebInspector.CSSStyleSheet.Type.UserAgent; + case CSSAgent.StyleSheetOrigin.Inspector: + return WebInspector.CSSStyleSheet.Type.Inspector; + default: + console.assert(false, "Unknown CSS.StyleSheetOrigin", origin); + return CSSAgent.StyleSheetOrigin.Regular; + } + } + + static protocolMediaSourceToEnum(source) + { + switch (source) { + case CSSAgent.CSSMediaSource.MediaRule: + return WebInspector.CSSMedia.Type.MediaRule; + case CSSAgent.CSSMediaSource.ImportRule: + return WebInspector.CSSMedia.Type.ImportRule; + case CSSAgent.CSSMediaSource.LinkedSheet: + return WebInspector.CSSMedia.Type.LinkedStyleSheet; + case CSSAgent.CSSMediaSource.InlineSheet: + return WebInspector.CSSMedia.Type.InlineStyleSheet; + default: + console.assert(false, "Unknown CSS.CSSMediaSource", source); + return WebInspector.CSSMedia.Type.MediaRule; + } + } + + // Public + + get preferredColorFormat() + { + return this._colorFormatSetting.value; + } + + get styleSheets() + { + return [...this._styleSheetIdentifierMap.values()]; + } + + canForcePseudoClasses() + { + return window.CSSAgent && !!CSSAgent.forcePseudoState; + } + + propertyNameHasOtherVendorPrefix(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(value) + { + var match = value.match(/(?:-moz-|-ms-|-o-|-epub-)[-\w]+/); + if (!match) + return false; + + return true; + } + + canonicalNameForPropertyName(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]; + } + + fetchStyleSheetsIfNeeded() + { + if (this._fetchedInitialStyleSheets) + return; + + this._fetchInfoForAllStyleSheets(function() {}); + } + + styleSheetForIdentifier(id) + { + let styleSheet = this._styleSheetIdentifierMap.get(id); + if (styleSheet) + return styleSheet; + + styleSheet = new WebInspector.CSSStyleSheet(id); + this._styleSheetIdentifierMap.set(id, styleSheet); + return styleSheet; + } + + stylesForNode(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; + } + + preferredInspectorStyleSheetForFrame(frame, callback) + { + var inspectorStyleSheets = this._inspectorStyleSheetsForFrame(frame); + for (let styleSheet of inspectorStyleSheets) { + if (styleSheet[WebInspector.CSSStyleManager.PreferredInspectorStyleSheetSymbol]) { + callback(styleSheet); + return; + } + } + + if (CSSAgent.createStyleSheet) { + CSSAgent.createStyleSheet(frame.id, function(error, styleSheetId) { + let styleSheet = WebInspector.cssStyleManager.styleSheetForIdentifier(styleSheetId); + styleSheet[WebInspector.CSSStyleManager.PreferredInspectorStyleSheetSymbol] = true; + callback(styleSheet); + }); + return; + } + + // COMPATIBILITY (iOS 9): CSS.createStyleSheet did not exist. + // Legacy backends can only create the Inspector StyleSheet through CSS.addRule. + // Exploit that to create the Inspector StyleSheet for the document.body node in + // this frame, then get the StyleSheet for the new rule. + + let expression = appendWebInspectorSourceURL("document"); + let contextId = frame.pageExecutionContext.id; + RuntimeAgent.evaluate.invoke({expression, objectGroup: "", includeCommandLineAPI: false, doNotPauseOnExceptionsAndMuteConsole: true, contextId, returnByValue: false, generatePreview: false}, documentAvailable); + + function documentAvailable(error, documentRemoteObjectPayload) + { + if (error) { + callback(null); + return; + } + + let remoteObject = WebInspector.RemoteObject.fromPayload(documentRemoteObjectPayload); + remoteObject.pushNodeToFrontend(documentNodeAvailable.bind(null, remoteObject)); + } + + function documentNodeAvailable(remoteObject, documentNodeId) + { + remoteObject.release(); + + if (!documentNodeId) { + callback(null); + return; + } + + DOMAgent.querySelector(documentNodeId, "body", bodyNodeAvailable); + } + + function bodyNodeAvailable(error, bodyNodeId) + { + if (error) { + console.error(error); + callback(null); + return; + } + + let selector = ""; // Intentionally empty. + CSSAgent.addRule(bodyNodeId, selector, cssRuleAvailable); + } + + function cssRuleAvailable(error, payload) + { + if (error || !payload.ruleId) { + callback(null); + return; + } + + let styleSheetId = payload.ruleId.styleSheetId; + let styleSheet = WebInspector.cssStyleManager.styleSheetForIdentifier(styleSheetId); + if (!styleSheet) { + callback(null); + return; + } + + styleSheet[WebInspector.CSSStyleManager.PreferredInspectorStyleSheetSymbol] = true; + + console.assert(styleSheet.isInspectorStyleSheet()); + console.assert(styleSheet.parentFrame === frame); + + callback(styleSheet); + } + } + + mediaTypeChanged() + { + // Act the same as if media queries changed. + this.mediaQueryResultChanged(); + } + + // Protected + + mediaQueryResultChanged() + { + // Called from WebInspector.CSSObserver. + + for (var key in this._nodeStylesMap) + this._nodeStylesMap[key].mediaQueryResultDidChange(); + } + + styleSheetChanged(styleSheetIdentifier) + { + // Called from WebInspector.CSSObserver. + var styleSheet = this.styleSheetForIdentifier(styleSheetIdentifier); + console.assert(styleSheet); + + // Do not observe inline styles + if (styleSheet.isInlineStyleAttributeStyleSheet()) + return; + + styleSheet.noteContentDidChange(); + this._updateResourceContent(styleSheet); + } + + styleSheetAdded(styleSheetInfo) + { + console.assert(!this._styleSheetIdentifierMap.has(styleSheetInfo.styleSheetId), "Attempted to add a CSSStyleSheet but identifier was already in use"); + let styleSheet = this.styleSheetForIdentifier(styleSheetInfo.styleSheetId); + let parentFrame = WebInspector.frameResourceManager.frameForIdentifier(styleSheetInfo.frameId); + let origin = WebInspector.CSSStyleManager.protocolStyleSheetOriginToEnum(styleSheetInfo.origin); + styleSheet.updateInfo(styleSheetInfo.sourceURL, parentFrame, origin, styleSheetInfo.isInline, styleSheetInfo.startLine, styleSheetInfo.startColumn); + + this.dispatchEventToListeners(WebInspector.CSSStyleManager.Event.StyleSheetAdded, {styleSheet}); + } + + styleSheetRemoved(styleSheetIdentifier) + { + let styleSheet = this._styleSheetIdentifierMap.get(styleSheetIdentifier); + console.assert(styleSheet, "Attempted to remove a CSSStyleSheet that was not tracked"); + if (!styleSheet) + return; + + this._styleSheetIdentifierMap.delete(styleSheetIdentifier); + + this.dispatchEventToListeners(WebInspector.CSSStyleManager.Event.StyleSheetRemoved, {styleSheet}); + } + + // Private + + _inspectorStyleSheetsForFrame(frame) + { + let styleSheets = []; + + for (let styleSheet of this.styleSheets) { + if (styleSheet.isInspectorStyleSheet() && styleSheet.parentFrame === frame) + styleSheets.push(styleSheet); + } + + return styleSheets; + } + + _nodePseudoClassesDidChange(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(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(event) + { + console.assert(event.target instanceof WebInspector.Frame); + + if (!event.target.isMainFrame()) + return; + + // Clear our maps when the main frame navigates. + + this._fetchedInitialStyleSheets = window.CSSAgent && window.CSSAgent.hasEvent("styleSheetAdded"); + this._styleSheetIdentifierMap.clear(); + this._styleSheetFrameURLMap.clear(); + this._nodeStylesMap = {}; + } + + _resourceAdded(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(event) + { + console.assert(event.target instanceof WebInspector.Resource); + + var resource = event.target; + if (resource.type !== WebInspector.Resource.Type.Stylesheet) + return; + + this._clearStyleSheetsForResource(resource); + } + + _clearStyleSheetsForResource(resource) + { + // Clear known stylesheets for this URL and frame. This will cause the stylesheets to + // be updated next time _fetchInfoForAllStyleSheets is called. + this._styleSheetIdentifierMap.delete(this._frameURLMapKey(resource.parentFrame, resource.url)); + } + + _frameURLMapKey(frame, url) + { + return frame.id + ":" + url; + } + + _lookupStyleSheetForResource(resource, callback) + { + this._lookupStyleSheet(resource.parentFrame, resource.url, callback); + } + + _lookupStyleSheet(frame, url, callback) + { + console.assert(frame instanceof WebInspector.Frame); + + let key = this._frameURLMapKey(frame, url); + + function styleSheetsFetched() + { + callback(this._styleSheetFrameURLMap.get(key) || null); + } + + let styleSheet = this._styleSheetFrameURLMap.get(key) || null; + if (styleSheet) + callback(styleSheet); + else + this._fetchInfoForAllStyleSheets(styleSheetsFetched.bind(this)); + } + + _fetchInfoForAllStyleSheets(callback) + { + console.assert(typeof callback === "function"); + + function processStyleSheets(error, styleSheets) + { + this._styleSheetFrameURLMap.clear(); + + if (error) { + callback(); + return; + } + + for (let styleSheetInfo of styleSheets) { + let parentFrame = WebInspector.frameResourceManager.frameForIdentifier(styleSheetInfo.frameId); + let origin = WebInspector.CSSStyleManager.protocolStyleSheetOriginToEnum(styleSheetInfo.origin); + + // COMPATIBILITY (iOS 9): The info did not have 'isInline', 'startLine', and 'startColumn', so make false and 0 in these cases. + let isInline = styleSheetInfo.isInline || false; + let startLine = styleSheetInfo.startLine || 0; + let startColumn = styleSheetInfo.startColumn || 0; + + let styleSheet = this.styleSheetForIdentifier(styleSheetInfo.styleSheetId); + styleSheet.updateInfo(styleSheetInfo.sourceURL, parentFrame, origin, isInline, startLine, startColumn); + + let key = this._frameURLMapKey(parentFrame, styleSheetInfo.sourceURL); + this._styleSheetFrameURLMap.set(key, styleSheet); + } + + callback(); + } + + CSSAgent.getAllStyleSheets(processStyleSheets.bind(this)); + } + + _resourceContentDidChange(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) + { + resource.__pendingChangeTimeout = undefined; + + 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(styleSheet) + { + console.assert(styleSheet); + + function fetchedStyleSheetContent(parameters) + { + var styleSheet = parameters.sourceCode; + var content = parameters.content; + + styleSheet.__pendingChangeTimeout = undefined; + + console.assert(styleSheet.url); + if (!styleSheet.url) + return; + + var resource = styleSheet.parentFrame.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) { + resource.__ignoreNextUpdateResourceContent = false; + return; + } + + this._ignoreResourceContentDidChangeEventForResource = resource; + WebInspector.branchManager.currentBranch.revisionForRepresentedObject(resource).content = content; + this._ignoreResourceContentDidChangeEventForResource = null; + } + + 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); + } +}; + +WebInspector.CSSStyleManager.Event = { + StyleSheetAdded: "css-style-manager-style-sheet-added", + StyleSheetRemoved: "css-style-manager-style-sheet-removed", +}; + +WebInspector.CSSStyleManager.PseudoElementNames = ["before", "after"]; +WebInspector.CSSStyleManager.ForceablePseudoClasses = ["active", "focus", "hover", "visited"]; +WebInspector.CSSStyleManager.PreferredInspectorStyleSheetSymbol = Symbol("css-style-manager-preferred-inspector-stylesheet"); |