monaca-lib
Version:
Monaca cloud API bindings for JavaScript
1,371 lines (1,202 loc) • 141 kB
JavaScript
/*
* Copyright (C) 2007 Apple Inc. All rights reserved.
* Copyright (C) 2009 Joseph Pecoraro
*
* 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.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE 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 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.
*/
/**
* @constructor
* @extends {WebInspector.SidebarPane}
* @param {!WebInspector.ComputedStyleSidebarPane} computedStylePane
* @param {function(!WebInspector.DOMNode, string, boolean)=} setPseudoClassCallback
*/
WebInspector.StylesSidebarPane = function(computedStylePane, setPseudoClassCallback)
{
WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles"));
if (!Runtime.experiments.isEnabled("animationInspection")) {
this._animationsControlButton = createElement("button");
this._animationsControlButton.className = "pane-title-button animations-controls";
this._animationsControlButton.title = WebInspector.UIString("Animations Controls");
this._animationsControlButton.addEventListener("click", this._toggleAnimationsControlPane.bind(this), false);
this.titleElement.appendChild(this._animationsControlButton);
}
this._elementStateButton = createElement("button");
this._elementStateButton.className = "pane-title-button element-state";
this._elementStateButton.title = WebInspector.UIString("Toggle Element State");
this._elementStateButton.addEventListener("click", this._toggleElementStatePane.bind(this), false);
this.titleElement.appendChild(this._elementStateButton);
var addButton = createElement("button");
addButton.className = "pane-title-button add";
addButton.id = "add-style-button-test-id";
addButton.title = WebInspector.UIString("New Style Rule");
addButton.addEventListener("click", this._createNewRuleInViaInspectorStyleSheet.bind(this), false);
this.titleElement.appendChild(addButton);
addButton.createChild("div", "long-click-glyph fill");
this._addButtonLongClickController = new WebInspector.LongClickController(addButton);
this._addButtonLongClickController.addEventListener(WebInspector.LongClickController.Events.LongClick, this._onAddButtonLongClick.bind(this));
this._addButtonLongClickController.enable();
this._computedStylePane = computedStylePane;
computedStylePane.setHostingPane(this);
this._setPseudoClassCallback = setPseudoClassCallback;
this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
WebInspector.settings.colorFormat.addChangeListener(this._colorFormatSettingChanged.bind(this));
WebInspector.settings.showUserAgentStyles.addChangeListener(this._showUserAgentStylesSettingChanged.bind(this));
this._createElementStatePane();
this.bodyElement.appendChild(this._elementStatePane);
this._createAnimationsControlPane();
this.bodyElement.appendChild(this._animationsControlPane);
this._sectionsContainer = createElement("div");
this.bodyElement.appendChild(this._sectionsContainer);
this._stylesPopoverHelper = new WebInspector.StylesPopoverHelper();
this._spectrum = new WebInspector.Spectrum();
this._bezierEditor = new WebInspector.BezierEditor();
this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultCSSFormatter());
this.element.classList.add("styles-pane");
this.element.classList.toggle("show-user-styles", WebInspector.settings.showUserAgentStyles.get());
this.element.addEventListener("mousemove", this._mouseMovedOverElement.bind(this), false);
this._keyDownBound = this._keyDown.bind(this);
this._keyUpBound = this._keyUp.bind(this);
}
// Keep in sync with LayoutStyleConstants.h PseudoId enum. Array below contains pseudo id names for corresponding enum indexes.
// First item is empty due to its artificial NOPSEUDO nature in the enum.
// FIXME: find a way of generating this mapping or getting it from combination of LayoutStyleConstants and CSSSelector.cpp at
// runtime.
WebInspector.StylesSidebarPane.PseudoIdNames = [
"", "first-line", "first-letter", "before", "after", "backdrop", "selection", "", "-webkit-scrollbar",
"-webkit-scrollbar-thumb", "-webkit-scrollbar-button", "-webkit-scrollbar-track", "-webkit-scrollbar-track-piece",
"-webkit-scrollbar-corner", "-webkit-resizer"
];
WebInspector.StylesSidebarPane._colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b(?!-))/g;
WebInspector.StylesSidebarPane._bezierRegex = /(cubic-bezier\([^)]+\))/g;
/**
* @enum {string}
*/
WebInspector.StylesSidebarPane.Events = {
SelectorEditingStarted: "SelectorEditingStarted",
SelectorEditingEnded: "SelectorEditingEnded"
};
/**
* @param {!WebInspector.CSSProperty} property
* @return {!Element}
*/
WebInspector.StylesSidebarPane.createExclamationMark = function(property)
{
var exclamationElement = createElement("div");
exclamationElement.className = "exclamation-mark" + (WebInspector.StylesSidebarPane._ignoreErrorsForProperty(property) ? "" : " warning-icon-small");
exclamationElement.title = WebInspector.CSSMetadata.cssPropertiesMetainfo.keySet()[property.name.toLowerCase()] ? WebInspector.UIString("Invalid property value.") : WebInspector.UIString("Unknown property name.");
return exclamationElement;
}
/**
* @param {!WebInspector.CSSProperty} property
* @return {boolean}
*/
WebInspector.StylesSidebarPane._ignoreErrorsForProperty = function(property) {
/**
* @param {string} string
*/
function hasUnknownVendorPrefix(string)
{
return !string.startsWith("-webkit-") && /^[-_][\w\d]+-\w/.test(string);
}
var name = property.name.toLowerCase();
// IE hack.
if (name.charAt(0) === "_")
return true;
// IE has a different format for this.
if (name === "filter")
return true;
// Common IE-specific property prefix.
if (name.startsWith("scrollbar-"))
return true;
if (hasUnknownVendorPrefix(name))
return true;
var value = property.value.toLowerCase();
// IE hack.
if (value.endsWith("\9"))
return true;
if (hasUnknownVendorPrefix(value))
return true;
return false;
}
WebInspector.StylesSidebarPane.prototype = {
/**
* @return {?WebInspector.DOMNode}
*/
node: function()
{
return this._node;
},
/**
* @param {!WebInspector.Event} event
*/
_onAddButtonLongClick: function(event)
{
this._addButtonLongClickController.reset();
var cssModel = this._target.cssModel;
var headers = cssModel.styleSheetHeaders().filter(styleSheetResourceHeader);
/** @type {!Array.<{text: string, handler: function()}>} */
var contextMenuDescriptors = [];
for (var i = 0; i < headers.length; ++i) {
var header = headers[i];
var handler = this._createNewRuleInStyleSheet.bind(this, header);
contextMenuDescriptors.push({
text: WebInspector.displayNameForURL(header.resourceURL()),
handler: handler
});
}
contextMenuDescriptors.sort(compareDescriptors);
var contextMenu = new WebInspector.ContextMenu(/** @type {!Event} */(event.data));
for (var i = 0; i < contextMenuDescriptors.length; ++i) {
var descriptor = contextMenuDescriptors[i];
contextMenu.appendItem(descriptor.text, descriptor.handler);
}
if (!contextMenu.isEmpty())
contextMenu.appendSeparator();
contextMenu.appendItem("inspector-stylesheet", this._createNewRuleInViaInspectorStyleSheet.bind(this));
contextMenu.show();
/**
* @param {!{text: string, handler: function()}} descriptor1
* @param {!{text: string, handler: function()}} descriptor2
* @return {number}
*/
function compareDescriptors(descriptor1, descriptor2)
{
return String.naturalOrderComparator(descriptor1.text, descriptor2.text);
}
/**
* @param {!WebInspector.CSSStyleSheetHeader} header
* @return {boolean}
*/
function styleSheetResourceHeader(header)
{
return !header.isViaInspector() && !header.isInline && !!header.resourceURL();
}
},
/**
* @param {!WebInspector.DOMNode} node
*/
updateEditingSelectorForNode: function(node)
{
var selectorText = WebInspector.DOMPresentationUtils.simpleSelector(node);
if (!selectorText)
return;
this._editingSelectorSection.setSelectorText(selectorText);
},
/**
* @return {boolean}
*/
isEditingSelector: function()
{
return !!this._editingSelectorSection;
},
/**
* @param {!WebInspector.StylePropertiesSection} section
*/
_startEditingSelector: function(section)
{
this._editingSelectorSection = section;
this.dispatchEventToListeners(WebInspector.StylesSidebarPane.Events.SelectorEditingStarted);
},
_finishEditingSelector: function()
{
delete this._editingSelectorSection;
this.dispatchEventToListeners(WebInspector.StylesSidebarPane.Events.SelectorEditingEnded);
},
/**
* @param {!WebInspector.CSSRule} editedRule
* @param {!WebInspector.TextRange} oldRange
* @param {!WebInspector.TextRange} newRange
*/
_styleSheetRuleEdited: function(editedRule, oldRange, newRange)
{
if (!editedRule.styleSheetId)
return;
for (var pseudoId in this.sections) {
var styleRuleSections = this.sections[pseudoId];
for (var i = 0; i < styleRuleSections.length; ++i) {
var section = styleRuleSections[i];
if (section.computedStyle)
continue;
section._styleSheetRuleEdited(editedRule, oldRange, newRange);
}
}
},
/**
* @param {!WebInspector.CSSMedia} oldMedia
* @param {!WebInspector.CSSMedia} newMedia
*/
_styleSheetMediaEdited: function(oldMedia, newMedia)
{
if (!oldMedia.parentStyleSheetId)
return;
for (var pseudoId in this.sections) {
var styleRuleSections = this.sections[pseudoId];
for (var i = 0; i < styleRuleSections.length; ++i) {
var section = styleRuleSections[i];
if (section.computedStyle)
continue;
section._styleSheetMediaEdited(oldMedia, newMedia);
}
}
},
/**
* @param {!Event} event
*/
_contextMenuEventFired: function(event)
{
// We start editing upon click -> default navigation to resources panel is not available
// Hence we add a soft context menu for hrefs.
var contextMenu = new WebInspector.ContextMenu(event);
contextMenu.appendApplicableItems(/** @type {!Node} */ (event.target));
contextMenu.show();
},
/**
* @param {!Element} matchedStylesElement
*/
setFilterBoxContainer: function(matchedStylesElement)
{
matchedStylesElement.appendChild(this._createCSSFilterControl());
},
/**
* @return {!Element}
*/
_createCSSFilterControl: function()
{
var filterInput = WebInspector.StylesSidebarPane._createPropertyFilterElement(WebInspector.UIString("Find in Styles"), searchHandler.bind(this));
/**
* @param {?RegExp} regex
* @this {WebInspector.StylesSidebarPane}
*/
function searchHandler(regex)
{
this._filterRegex = regex;
this._updateFilter();
}
return filterInput;
},
get _forcedPseudoClasses()
{
return this._node ? (this._node.getUserProperty(WebInspector.CSSStyleModel.PseudoStatePropertyName) || undefined) : undefined;
},
_updateForcedPseudoStateInputs: function()
{
if (!this._node)
return;
var hasPseudoType = !!this._node.pseudoType();
this._elementStateButton.classList.toggle("hidden", hasPseudoType);
this._elementStatePane.classList.toggle("expanded", !hasPseudoType && this._elementStateButton.classList.contains("toggled"));
var nodePseudoState = this._forcedPseudoClasses;
if (!nodePseudoState)
nodePseudoState = [];
var inputs = this._elementStatePane.inputs;
for (var i = 0; i < inputs.length; ++i)
inputs[i].checked = nodePseudoState.indexOf(inputs[i].state) >= 0;
},
/**
* @param {?WebInspector.DOMNode} node
*/
setNode: function(node)
{
this._stylesPopoverHelper.hide();
this._discardElementUnderMouse();
if (node && node.nodeType() === Node.TEXT_NODE && node.parentNode)
node = node.parentNode;
if (node && node.nodeType() !== Node.ELEMENT_NODE)
node = null;
this._node = node;
if (node)
this._updateTarget(node.target());
this._computedStylePane.setNode(node);
this._resetCache();
this._scheduleUpdate();
},
_scheduleUpdate: function()
{
if (!this.isShowing() && !this._computedStylePane.isShowing()) {
this._updateWhenVisible = true;
return;
}
this._rebuildUpdate();
},
/**
* @param {!WebInspector.Target} target
*/
_updateTarget: function(target)
{
if (this._target === target)
return;
if (this._target) {
this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetOrMediaQueryResultChanged, this);
this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetOrMediaQueryResultChanged, this);
this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this);
this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this);
this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeChanged, this);
this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeChanged, this);
this._target.resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this._frameResized, this);
}
this._target = target;
this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetOrMediaQueryResultChanged, this);
this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetOrMediaQueryResultChanged, this);
this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this);
this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this);
this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeChanged, this);
this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeChanged, this);
this._target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this._frameResized, this);
},
/**
* @param {!WebInspector.StylePropertiesSection=} editedSection
*/
_refreshUpdate: function(editedSection)
{
var node = this._validateNode();
if (!node)
return;
this._innerRefreshUpdate(node, editedSection);
if (this._filterRegex)
this._updateFilter();
},
_rebuildUpdate: function()
{
this._updateForcedPseudoStateInputs();
if (this._rebuildUpdateInProgress) {
this._lastNodeForInnerRebuild = this.node();
return;
}
var node = this._validateNode();
if (!node)
return;
this._rebuildUpdateInProgress = true;
this._fetchMatchedCascade()
.then(onStylesLoaded.bind(this));
/**
* @param {?{matched: !WebInspector.SectionCascade, pseudo: !Map.<number, !WebInspector.SectionCascade>}} cascades
* @this {WebInspector.StylesSidebarPane}
*/
function onStylesLoaded(cascades)
{
var lastRequestedRebuildNode = this._lastNodeForInnerRebuild || node;
delete this._rebuildUpdateInProgress;
delete this._lastNodeForInnerRebuild;
if (node !== lastRequestedRebuildNode) {
this._rebuildUpdate();
return;
}
this._innerRebuildUpdate(cascades);
}
},
_resetCache: function()
{
delete this._matchedCascadePromise;
this._resetComputedCache();
},
_resetComputedCache: function()
{
delete this._computedCascadePromise;
delete this._animationPropertiesPromise;
},
/**
* @return {!Promise.<?{matched: !WebInspector.SectionCascade, pseudo: !Map.<number, !WebInspector.SectionCascade>}>}
*/
_fetchMatchedCascade: function()
{
var node = this.node();
if (!node)
return Promise.resolve(/** @type {?{matched: !WebInspector.SectionCascade, pseudo: !Map.<number, !WebInspector.SectionCascade>}} */(null));
if (!this._matchedCascadePromise)
this._matchedCascadePromise = new Promise(this._getMatchedStylesForNode.bind(this, node)).then(buildMatchedCascades.bind(this, node));
return this._matchedCascadePromise;
/**
* @param {!WebInspector.DOMNode} node
* @param {!WebInspector.StylesSidebarPane.MatchedRulesPayload} payload
* @return {?{matched: !WebInspector.SectionCascade, pseudo: !Map.<number, !WebInspector.SectionCascade>}}
* @this {WebInspector.StylesSidebarPane}
*/
function buildMatchedCascades(node, payload)
{
if (node !== this.node() || !payload.fulfilled())
return null;
return {
matched: this._buildMatchedRulesSectionCascade(node, payload),
pseudo: this._buildPseudoCascades(node, payload)
};
}
},
/**
* @return {!Promise.<?WebInspector.SectionCascade>}
*/
_fetchComputedCascade: function()
{
var node = this.node();
if (!node)
return Promise.resolve(/** @type {?WebInspector.SectionCascade} */(null));
if (!this._computedCascadePromise)
this._computedCascadePromise = new Promise(getComputedStyle.bind(null, node)).then(buildComputedCascade.bind(this, node));
return this._computedCascadePromise;
/**
* @param {!WebInspector.DOMNode} node
* @param {function(?WebInspector.CSSStyleDeclaration)} resolve
*/
function getComputedStyle(node, resolve)
{
node.target().cssModel.getComputedStyleAsync(node.id, resolve);
}
/**
* @param {!WebInspector.DOMNode} node
* @param {?WebInspector.CSSStyleDeclaration} styles
* @return {?WebInspector.SectionCascade}
* @this {WebInspector.StylesSidebarPane}
*/
function buildComputedCascade(node, styles)
{
if (node !== this.node())
return null;
if (!styles)
return null;
var computedCascade = new WebInspector.SectionCascade();
computedCascade.appendModelFromStyle(styles, "");
return computedCascade;
}
},
/**
* @return {!Promise.<!Map.<string, string>>}
*/
_fetchAnimationProperties: function()
{
var node = this.node();
if (!node)
return Promise.resolve(new Map());
if (!this._animationPropertiesPromise)
this._animationPropertiesPromise = new Promise(this._getAnimationPropertiesForNode.bind(this, node)).then(onAnimationProperties.bind(this));
return this._animationPropertiesPromise;
/**
* @param {!Map.<string, string>} properties
* @return {!Map.<string, string>}
* @this {WebInspector.StylesSidebarPane}
*/
function onAnimationProperties(properties)
{
return this.node() !== node ? new Map() : properties;
}
},
/**
* @param {!WebInspector.DOMNode} node
* @param {function(!WebInspector.StylesSidebarPane.MatchedRulesPayload)} callback
*/
_getMatchedStylesForNode: function(node, callback)
{
var target = node.target();
target.cssModel.getInlineStylesAsync(node.id, inlineCallback);
target.cssModel.getMatchedStylesAsync(node.id, false, false, matchedCallback);
var payload = new WebInspector.StylesSidebarPane.MatchedRulesPayload();
/**
* @param {?WebInspector.CSSStyleDeclaration} inlineStyle
* @param {?WebInspector.CSSStyleDeclaration} attributesStyle
*/
function inlineCallback(inlineStyle, attributesStyle)
{
payload.inlineStyle = /** @type {?WebInspector.CSSStyleDeclaration} */(inlineStyle);
payload.attributesStyle = /** @type {?WebInspector.CSSStyleDeclaration} */(attributesStyle);
}
/**
* @param {?*} matchedResult
*/
function matchedCallback(matchedResult)
{
if (matchedResult) {
payload.matchedCSSRules = /** @type {?Array.<!WebInspector.CSSRule>} */(matchedResult.matchedCSSRules);
payload.pseudoElements = /** @type {?Array.<{pseudoId: number, rules: !Array.<!WebInspector.CSSRule>}>} */(matchedResult.pseudoElements);
payload.inherited = /** @type {?Array.<{matchedCSSRules: !Array.<!WebInspector.CSSRule>}>} */(matchedResult.inherited);
}
callback(payload);
}
},
/**
* @param {!WebInspector.DOMNode} node
* @param {function(!Map<string, string>)} callback
*/
_getAnimationPropertiesForNode: function(node, callback)
{
if (Runtime.experiments.isEnabled("animationInspection"))
node.target().animationModel.getAnimationPlayers(node.id, false, animationPlayersCallback);
else
callback(new Map());
/**
* @param {?Array.<!WebInspector.AnimationModel.AnimationPlayer>} animationPlayers
*/
function animationPlayersCallback(animationPlayers)
{
var animationProperties = new Map();
if (!animationPlayers)
return;
for (var i = 0; i < animationPlayers.length; i++) {
var player = animationPlayers[i];
if (!player.source().keyframesRule())
continue;
var animationCascade = new WebInspector.SectionCascade();
var keyframes = player.source().keyframesRule().keyframes();
for (var j = 0; j < keyframes.length; j++)
animationCascade.appendModelFromStyle(keyframes[j].style(), "");
for (var property of animationCascade.allUsedProperties())
animationProperties.set(property, player.name());
}
callback(animationProperties);
}
},
_validateNode: function()
{
if (!this._node) {
this._sectionsContainer.removeChildren();
this.sections = {};
return null;
}
return this._node;
},
/**
* @param {boolean} editing
*/
setEditingStyle: function(editing)
{
this._isEditingStyle = editing;
},
_styleSheetOrMediaQueryResultChanged: function()
{
if (this._userOperation || this._isEditingStyle) {
this._resetComputedCache();
return;
}
this._resetCache();
this._scheduleUpdate();
},
_frameResized: function()
{
/**
* @this {WebInspector.StylesSidebarPane}
*/
function refreshContents()
{
this._styleSheetOrMediaQueryResultChanged();
delete this._activeTimer;
}
if (this._activeTimer)
clearTimeout(this._activeTimer);
this._activeTimer = setTimeout(refreshContents.bind(this), 100);
},
/**
* @param {!WebInspector.Event} event
*/
_attributeChanged: function(event)
{
// Any attribute removal or modification can affect the styles of "related" nodes.
// Do not touch the styles if they are being edited.
if (this._isEditingStyle || this._userOperation) {
this._resetComputedCache();
return;
}
if (!this._canAffectCurrentStyles(event.data.node))
return;
this._resetCache();
this._scheduleUpdate();
},
/**
* @param {?WebInspector.DOMNode} node
*/
_canAffectCurrentStyles: function(node)
{
return this._node && (this._node === node || node.parentNode === this._node.parentNode || node.isAncestor(this._node));
},
/**
* @param {!WebInspector.DOMNode} node
* @param {!WebInspector.StylePropertiesSection=} editedSection
*/
_innerRefreshUpdate: function(node, editedSection)
{
for (var pseudoId in this.sections) {
var sections = this.sections[pseudoId].filter(nonBlankSections);
for (var section of sections)
section.update(section === editedSection);
}
this._computedStylePane.update();
this._nodeStylesUpdatedForTest(node, false);
/**
* @param {!WebInspector.StylePropertiesSection} section
* @return {boolean}
*/
function nonBlankSections(section)
{
return !section.isBlank;
}
},
/**
* @param {?{matched: !WebInspector.SectionCascade, pseudo: !Map.<number, !WebInspector.SectionCascade>}} cascades
*/
_innerRebuildUpdate: function(cascades)
{
this._linkifier.reset();
this._sectionsContainer.removeChildren();
this.sections = {};
var node = this.node();
if (!cascades || !node)
return;
if (!!node.pseudoType())
this._appendTopPadding();
this.sections[0] = this._rebuildSectionsForStyleRules(cascades.matched);
this._computedStylePane.update();
var pseudoIds = cascades.pseudo.keysArray().sort();
for (var pseudoId of pseudoIds) {
this._appendSectionPseudoIdSeparator(pseudoId);
this.sections[pseudoId] = this._rebuildSectionsForStyleRules(cascades.pseudo.get(pseudoId));
}
if (this._filterRegex)
this._updateFilter();
this._nodeStylesUpdatedForTest(node, true);
this._updateAnimationsPlaybackRate();
},
/**
* @param {!WebInspector.DOMNode} node
* @param {!WebInspector.StylesSidebarPane.MatchedRulesPayload} styles
* @return {!Map<number, !WebInspector.SectionCascade>}
*/
_buildPseudoCascades: function(node, styles)
{
var pseudoCascades = new Map();
for (var i = 0; i < styles.pseudoElements.length; ++i) {
var pseudoElementCSSRules = styles.pseudoElements[i];
var pseudoId = pseudoElementCSSRules.pseudoId;
// Add rules in reverse order to match the cascade order.
var pseudoElementCascade = new WebInspector.SectionCascade();
for (var j = pseudoElementCSSRules.rules.length - 1; j >= 0; --j) {
var rule = pseudoElementCSSRules.rules[j];
pseudoElementCascade.appendModelFromRule(rule);
}
pseudoCascades.set(pseudoId, pseudoElementCascade);
}
return pseudoCascades;
},
/**
* @param {!WebInspector.DOMNode} node
* @param {boolean} rebuild
*/
_nodeStylesUpdatedForTest: function(node, rebuild)
{
// For sniffing in tests.
},
/**
* @param {!WebInspector.DOMNode} node
* @param {!WebInspector.StylesSidebarPane.MatchedRulesPayload} styles
* @return {!WebInspector.SectionCascade}
*/
_buildMatchedRulesSectionCascade: function(node, styles)
{
var cascade = new WebInspector.SectionCascade();
function addAttributesStyle()
{
if (!styles.attributesStyle)
return;
var selectorText = node.nodeNameInCorrectCase() + "[" + WebInspector.UIString("Attributes Style") + "]";
cascade.appendModelFromStyle(styles.attributesStyle, selectorText);
}
// Inline style has the greatest specificity.
if (styles.inlineStyle && node.nodeType() === Node.ELEMENT_NODE) {
var model = cascade.appendModelFromStyle(styles.inlineStyle, "element.style");
model.setIsAttribute(true);
}
// Add rules in reverse order to match the cascade order.
var addedAttributesStyle;
for (var i = styles.matchedCSSRules.length - 1; i >= 0; --i) {
var rule = styles.matchedCSSRules[i];
if ((rule.isInjected || rule.isUserAgent) && !addedAttributesStyle) {
// Show element's Style Attributes after all author rules.
addedAttributesStyle = true;
addAttributesStyle();
}
cascade.appendModelFromRule(rule);
}
if (!addedAttributesStyle)
addAttributesStyle();
// Walk the node structure and identify styles with inherited properties.
var parentNode = node.parentNode;
for (var parentOrdinal = 0; parentOrdinal < styles.inherited.length; ++parentOrdinal) {
var parentStyles = styles.inherited[parentOrdinal];
if (parentStyles.inlineStyle) {
if (this._containsInherited(parentStyles.inlineStyle)) {
var model = cascade.appendModelFromStyle(parentStyles.inlineStyle, WebInspector.UIString("Style Attribute"), parentNode);
model.setIsAttribute(true);
}
}
for (var i = parentStyles.matchedCSSRules.length - 1; i >= 0; --i) {
var rulePayload = parentStyles.matchedCSSRules[i];
if (!this._containsInherited(rulePayload.style))
continue;
cascade.appendModelFromRule(rulePayload, parentNode);
}
parentNode = parentNode.parentNode;
}
return cascade;
},
_appendTopPadding: function()
{
var separatorElement = createElement("div");
separatorElement.className = "styles-sidebar-placeholder";
this._sectionsContainer.appendChild(separatorElement);
},
/**
* @param {number} pseudoId
*/
_appendSectionPseudoIdSeparator: function(pseudoId)
{
var separatorElement = createElement("div");
separatorElement.className = "sidebar-separator";
var pseudoName = WebInspector.StylesSidebarPane.PseudoIdNames[pseudoId];
if (pseudoName)
separatorElement.textContent = WebInspector.UIString("Pseudo ::%s element", pseudoName);
else
separatorElement.textContent = WebInspector.UIString("Pseudo element");
this._sectionsContainer.appendChild(separatorElement);
},
/**
* @param {!WebInspector.DOMNode} node
*/
_appendSectionInheritedNodeSeparator: function(node)
{
var separatorElement = createElement("div");
separatorElement.className = "sidebar-separator";
var link = WebInspector.DOMPresentationUtils.linkifyNodeReference(node);
separatorElement.createTextChild(WebInspector.UIString("Inherited from") + " ");
separatorElement.appendChild(link);
this._sectionsContainer.appendChild(separatorElement);
},
/**
* @param {!WebInspector.SectionCascade} cascade
* @return {!Array.<!WebInspector.StylePropertiesSection>}
*/
_rebuildSectionsForStyleRules: function(cascade)
{
var sections = [];
var lastParentNode = null;
for (var sectionModel of cascade.sectionModels()) {
var parentNode = sectionModel.parentNode();
if (parentNode && parentNode !== lastParentNode) {
lastParentNode = parentNode;
this._appendSectionInheritedNodeSeparator(lastParentNode);
}
var section = new WebInspector.StylePropertiesSection(this, sectionModel);
section._markSelectorMatches();
section.onpopulate();
this._sectionsContainer.appendChild(section.element);
sections.push(section);
}
return sections;
},
/**
* @param {!WebInspector.CSSStyleDeclaration} style
* @return {boolean}
*/
_containsInherited: function(style)
{
var properties = style.allProperties;
for (var i = 0; i < properties.length; ++i) {
var property = properties[i];
// Does this style contain non-overridden inherited property?
if (property.isLive && WebInspector.CSSMetadata.isPropertyInherited(property.name))
return true;
}
return false;
},
/**
* @param {!WebInspector.Event} event
*/
_colorFormatSettingChanged: function(event)
{
for (var pseudoId in this.sections) {
var sections = this.sections[pseudoId];
for (var i = 0; i < sections.length; ++i)
sections[i].update(true);
}
},
/**
* @param {?Event} event
*/
_createNewRuleInViaInspectorStyleSheet: function(event)
{
var cssModel = this._target.cssModel;
cssModel.requestViaInspectorStylesheet(this._node, this._createNewRuleInStyleSheet.bind(this));
},
/**
* @param {?WebInspector.CSSStyleSheetHeader} styleSheetHeader
*/
_createNewRuleInStyleSheet: function(styleSheetHeader)
{
if (!styleSheetHeader)
return;
styleSheetHeader.requestContent(onStyleSheetContent.bind(this, styleSheetHeader.id));
/**
* @param {string} styleSheetId
* @param {string} text
* @this {WebInspector.StylesSidebarPane}
*/
function onStyleSheetContent(styleSheetId, text)
{
var lines = text.split("\n");
var range = WebInspector.TextRange.createFromLocation(lines.length - 1, lines[lines.length - 1].length);
this._addBlankSection(this.sections[0][0], styleSheetId, range);
}
},
/**
* @param {!WebInspector.StylePropertiesSection} insertAfterSection
* @param {string} styleSheetId
* @param {!WebInspector.TextRange} ruleLocation
*/
_addBlankSection: function(insertAfterSection, styleSheetId, ruleLocation)
{
this.expand();
var blankSection = new WebInspector.BlankStylePropertiesSection(this, this._node ? WebInspector.DOMPresentationUtils.simpleSelector(this._node) : "", styleSheetId, ruleLocation, insertAfterSection.styleRule);
this._sectionsContainer.insertBefore(blankSection.element, insertAfterSection.element.nextSibling);
var index = this.sections[0].indexOf(insertAfterSection);
this.sections[0].splice(index + 1, 0, blankSection);
blankSection.startEditingSelector();
},
/**
* @param {!WebInspector.StylePropertiesSection} section
*/
removeSection: function(section)
{
for (var pseudoId in this.sections) {
var sections = this.sections[pseudoId];
var index = sections.indexOf(section);
if (index === -1)
continue;
sections.splice(index, 1);
section.element.remove();
}
},
/**
* @param {!Event} event
*/
_toggleElementStatePane: function(event)
{
event.consume();
var buttonToggled = !this._elementStateButton.classList.contains("toggled");
if (buttonToggled)
this.expand();
this._elementStateButton.classList.toggle("toggled", buttonToggled);
this._elementStatePane.classList.toggle("expanded", buttonToggled);
this._animationsControlButton.classList.remove("toggled");
this._animationsControlPane.classList.remove("expanded");
},
_createElementStatePane: function()
{
this._elementStatePane = createElement("div");
this._elementStatePane.className = "styles-element-state-pane source-code";
var table = createElement("table");
var inputs = [];
this._elementStatePane.inputs = inputs;
/**
* @param {!Event} event
* @this {WebInspector.StylesSidebarPane}
*/
function clickListener(event)
{
var node = this._validateNode();
if (!node)
return;
this._setPseudoClassCallback(node, event.target.state, event.target.checked);
}
/**
* @param {string} state
* @return {!Element}
* @this {WebInspector.StylesSidebarPane}
*/
function createCheckbox(state)
{
var td = createElement("td");
var label = createCheckboxLabel(":" + state);
var input = label.checkboxElement;
input.state = state;
input.addEventListener("click", clickListener.bind(this), false);
inputs.push(input);
td.appendChild(label);
return td;
}
var tr = table.createChild("tr");
tr.appendChild(createCheckbox.call(this, "active"));
tr.appendChild(createCheckbox.call(this, "hover"));
tr = table.createChild("tr");
tr.appendChild(createCheckbox.call(this, "focus"));
tr.appendChild(createCheckbox.call(this, "visited"));
this._elementStatePane.appendChild(table);
},
/**
* @param {!Event} event
*/
_toggleAnimationsControlPane: function(event)
{
event.consume();
var buttonToggled = !this._animationsControlButton.classList.contains("toggled");
if (buttonToggled)
this.expand();
this._animationsControlButton.classList.toggle("toggled", buttonToggled);
this._animationsControlPane.classList.toggle("expanded", buttonToggled);
this._elementStateButton.classList.remove("toggled");
this._elementStatePane.classList.remove("expanded");
},
_updateAnimationsPlaybackRate: function()
{
/**
* @param {?Protocol.Error} error
* @param {number} playbackRate
* @this {WebInspector.StylesSidebarPane}
*/
function setPlaybackRate(error, playbackRate)
{
this._animationsPlaybackSlider.value = WebInspector.AnimationsSidebarPane.GlobalPlaybackRates.indexOf(playbackRate);
this._animationsPlaybackLabel.textContent = playbackRate + "x";
}
if (this._target)
this._target.pageAgent().getAnimationsPlaybackRate(setPlaybackRate.bind(this));
},
_createAnimationsControlPane: function()
{
/**
* @param {!Event} event
* @this {WebInspector.StylesSidebarPane}
*/
function playbackSliderInputHandler(event)
{
this._animationsPlaybackRate = WebInspector.AnimationsSidebarPane.GlobalPlaybackRates[event.target.value];
this._target.pageAgent().setAnimationsPlaybackRate(this._animationsPaused ? 0 : this._animationsPlaybackRate);
this._animationsPlaybackLabel.textContent = this._animationsPlaybackRate + "x";
WebInspector.userMetrics.AnimationsPlaybackRateChanged.record();
}
/**
* @this {WebInspector.StylesSidebarPane}
*/
function pauseButtonHandler()
{
this._animationsPaused = !this._animationsPaused;
this._target.pageAgent().setAnimationsPlaybackRate(this._animationsPaused ? 0 : this._animationsPlaybackRate);
WebInspector.userMetrics.AnimationsPlaybackRateChanged.record();
this._animationsPauseButton.element.classList.toggle("pause-status-bar-item");
this._animationsPauseButton.element.classList.toggle("play-status-bar-item");
}
this._animationsPaused = false;
this._animationsPlaybackRate = 1;
this._updateAnimationsPlaybackRate();
this._animationsControlPane = createElementWithClass("div", "styles-animations-controls-pane");
var labelElement = createElement("div");
labelElement.createTextChild("Animations");
this._animationsControlPane.appendChild(labelElement);
var container = this._animationsControlPane.createChild("div", "animations-controls");
var statusBar = new WebInspector.StatusBar();
this._animationsPauseButton = new WebInspector.StatusBarButton("", "pause-status-bar-item");
statusBar.appendStatusBarItem(this._animationsPauseButton);
this._animationsPauseButton.addEventListener("click", pauseButtonHandler.bind(this));
container.appendChild(statusBar.element);
this._animationsPlaybackSlider = container.createChild("input");
this._animationsPlaybackSlider.type = "range";
this._animationsPlaybackSlider.min = 0;
this._animationsPlaybackSlider.max = WebInspector.AnimationsSidebarPane.GlobalPlaybackRates.length - 1;
this._animationsPlaybackSlider.value = this._animationsPlaybackSlider.max;
this._animationsPlaybackSlider.addEventListener("input", playbackSliderInputHandler.bind(this));
this._animationsPlaybackLabel = container.createChild("div", "playback-label");
this._animationsPlaybackLabel.createTextChild("1x");
},
/**
* @return {?RegExp}
*/
filterRegex: function()
{
return this._filterRegex;
},
_updateFilter: function()
{
for (var pseudoId in this.sections) {
var sections = this.sections[pseudoId];
for (var i = 0; i < sections.length; ++i) {
var section = sections[i];
section._updateFilter();
}
}
},
/**
* @param {!WebInspector.Event} event
*/
_showUserAgentStylesSettingChanged: function(event)
{
var showStyles = /** @type {boolean} */ (event.data);
this.element.classList.toggle("show-user-styles", showStyles);
},
/**
* @override
*/
wasShown: function()
{
WebInspector.SidebarPane.prototype.wasShown.call(this);
this.element.ownerDocument.body.addEventListener("keydown", this._keyDownBound, false);
this.element.ownerDocument.body.addEventListener("keyup", this._keyUpBound, false);
if (this._updateWhenVisible) {
this._rebuildUpdate();
delete this._updateWhenVisible;
}
},
/**
* @override
*/
willHide: function()
{
this.element.ownerDocument.body.removeEventListener("keydown", this._keyDownBound, false);
this.element.ownerDocument.body.removeEventListener("keyup", this._keyUpBound, false);
this._stylesPopoverHelper.hide();
this._discardElementUnderMouse();
WebInspector.SidebarPane.prototype.willHide.call(this);
},
_discardElementUnderMouse: function()
{
if (this._elementUnderMouse)
this._elementUnderMouse.classList.remove("styles-panel-hovered");
delete this._elementUnderMouse;
},
/**
* @param {!Event} event
*/
_mouseMovedOverElement: function(event)
{
if (this._elementUnderMouse && event.target !== this._elementUnderMouse)
this._discardElementUnderMouse();
this._elementUnderMouse = event.target;
if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(/** @type {!MouseEvent} */(event)))
this._elementUnderMouse.classList.add("styles-panel-hovered");
},
/**
* @param {!Event} event
*/
_keyDown: function(event)
{
if ((!WebInspector.isMac() && event.keyCode === WebInspector.KeyboardShortcut.Keys.Ctrl.code) ||
(WebInspector.isMac() && event.keyCode === WebInspector.KeyboardShortcut.Keys.Meta.code)) {
if (this._elementUnderMouse)
this._elementUnderMouse.classList.add("styles-panel-hovered");
}
},
/**
* @param {!Event} event
*/
_keyUp: function(event)
{
if ((!WebInspector.isMac() && event.keyCode === WebInspector.KeyboardShortcut.Keys.Ctrl.code) ||
(WebInspector.isMac() && event.keyCode === WebInspector.KeyboardShortcut.Keys.Meta.code)) {
this._discardElementUnderMouse();
}
},
__proto__: WebInspector.SidebarPane.prototype
}
/**
* @param {string} placeholder
* @return {!Element}
* @param {function(?RegExp)} filterCallback
*/
WebInspector.StylesSidebarPane._createPropertyFilterElement = function(placeholder, filterCallback)
{
var input = createElement("input");
input.type = "text";
input.placeholder = placeholder;
function searchHandler()
{
var regex = input.value ? new RegExp(input.value.escapeForRegExp(), "i") : null;
filterCallback(regex);
input.parentNode.classList.toggle("styles-filter-engaged", !!input.value);
}
input.addEventListener("input", searchHandler, false);
/**
* @param {!Event} event
*/
function keydownHandler(event)
{
var Esc = "U+001B";
if (event.keyIdentifier !== Esc || !input.value)
return;
event.consume(true);
input.value = "";
searchHandler();
}
input.addEventListener("keydown", keydownHandler, false);
return input;
}
/**
* @constructor
* @extends {WebInspector.ElementsSidebarPane}
*/
WebInspector.ComputedStyleSidebarPane = function()
{
WebInspector.ElementsSidebarPane.call(this, WebInspector.UIString("Computed Style"));
WebInspector.settings.showInheritedComputedStyleProperties.addChangeListener(this._showInheritedComputedStyleChanged.bind(this));
this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultCSSFormatter());
}
WebInspector.ComputedStyleSidebarPane.prototype = {
_showInheritedComputedStyleChanged: function()
{
this._computedStyleSection.update();
this._computedStyleSection._rebuildComputedTrace();
},
/**
* @override
* @param {?WebInspector.DOMNode} node
*/
setNode: function(node)
{
if (node)
this._target = node.target();
WebInspector.ElementsSidebarPane.prototype.setNode.call(this, node);
},
/**
* @override
* @param {!WebInspector.Throttler.FinishCallback} finishedCallback
*/
doUpdate: function(finishedCallback)
{
var promises = [
this._stylesSidebarPane._fetchComputedCascade(),
this._stylesSidebarPane._fetchMatchedCascade(),
this._stylesSidebarPane._fetchAnimationProperties()
];
Promise.all(promises)
.spread(this._innerRebuildUpdate.bind(this))
.then(finishedCallback);
},
/**
* @param {?WebInspector.SectionCascade} computedCascade
@param {?{matched: !WebInspector.SectionCascade, pseudo: !Map.<number, !WebInspector.SectionCascade>}} cascades
* @param {!Map.<string, string>} animationProperties
*/
_innerRebuildUpdate: function(computedCascade, cascades, animationProperties)
{
this._linkifier.reset();
this.bodyElement.removeChildren();
if (!computedCascade || !cascades)
return;
var computedStyleRule = computedCascade.sectionModels()[0];
this._computedStyleSection = new WebInspector.ComputedStylePropertiesSection(this, computedStyleRule, cascades.matched, animationProperties);
this._computedStyleSection.expand();
this._computedStyleSection._rebuildComputedTrace();
this.bodyElement.appendChild(this._computedStyle