blob: 477195873b16935e99f28e896e06699f3ed2f2cb [file] [log] [blame]
/*
* Copyright (C) 2017 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.
*/
WI.SpreadsheetRulesStyleDetailsPanel = class SpreadsheetRulesStyleDetailsPanel extends WI.StyleDetailsPanel
{
constructor(delegate)
{
const className = "rules";
const identifier = "rules";
const label = WI.UIString("Styles \u2014 Rules");
super(delegate, className, identifier, label);
// Make the styles sidebar always left-to-right since CSS is strictly an LTR language.
this.element.dir = "ltr";
this._headerMap = new Map;
this._sections = [];
this._newRuleSelector = null;
this._ruleMediaAndInherticanceList = [];
this._propertyToSelectAndHighlight = null;
this._filterText = null;
this._shouldRefreshSubviews = false;
this._suppressLayoutAfterSelectorChange = false;
this._emptyFilterResultsElement = WI.createMessageTextView(WI.UIString("No Results Found"));
}
// Public
get supportsNewRule()
{
return this.nodeStyles && !this.nodeStyles.node.isInUserAgentShadowTree() && InspectorBackend.hasCommand("CSS.addRule");
}
get supportsToggleCSSClassList()
{
return true;
}
get supportsToggleCSSForcedPseudoClass()
{
return true;
}
get initialToggleCSSForcedPseudoClassState()
{
return true;
}
refresh(significantChange)
{
// We only need to do a rebuild on significant changes. Other changes are handled
// by the sections and text editors themselves.
if (significantChange) {
this._shouldRefreshSubviews = true;
this.needsLayout();
}
super.refresh(significantChange);
}
scrollToSectionAndHighlightProperty(property)
{
if (!this.isAttached || this.layoutPending) {
this._propertyToSelectAndHighlight = property;
return;
}
for (let section of this._sections) {
if (section.highlightProperty(property))
return;
}
}
nodeStylesRefreshed(event)
{
super.nodeStylesRefreshed(event);
if (this._propertyToSelectAndHighlight) {
this.scrollToSectionAndHighlightProperty(this._propertyToSelectAndHighlight);
this._propertyToSelectAndHighlight = null;
}
}
newRuleButtonClicked()
{
this._addNewRule();
}
newRuleButtonContextMenu(event)
{
let styleSheets = WI.cssManager.styleSheets.filter(styleSheet => styleSheet.hasInfo() && !styleSheet.isInlineStyleTag() && !styleSheet.isInlineStyleAttributeStyleSheet());
if (!styleSheets.length)
return;
let contextMenu = WI.ContextMenu.createFromEvent(event);
const handler = null;
const disabled = true;
contextMenu.appendItem(WI.UIString("Available Style Sheets"), handler, disabled);
let [inspectorStyleSheets, regularStyleSheets] = styleSheets.partition(styleSheet => styleSheet.isInspectorStyleSheet());
console.assert(inspectorStyleSheets.length <= 1, "There should never be more than one inspector style sheet");
contextMenu.appendItem(WI.UIString("Inspector Style Sheet"), () => {
this._addNewRule(inspectorStyleSheets.length ? inspectorStyleSheets[0].id : null);
});
for (let styleSheet of regularStyleSheets) {
contextMenu.appendItem(styleSheet.displayName, () => {
this._addNewRule(styleSheet.id);
});
}
}
applyFilter(filterText)
{
this._filterText = filterText;
if (!this.didInitialLayout)
return;
if (this._filterText)
this.element.classList.add("filter-non-matching");
for (let header of this._headerMap.values())
header.classList.add(WI.GeneralStyleDetailsSidebarPanel.NoFilterMatchInSectionClassName);
for (let section of this._sections)
section.applyFilter(this._filterText);
}
focusFirstSection()
{
this.spreadsheetCSSStyleDeclarationSectionStartEditingAdjacentRule(null, 1);
}
focusLastSection()
{
this.spreadsheetCSSStyleDeclarationSectionStartEditingAdjacentRule(null, -1);
}
// SpreadsheetCSSStyleDeclarationSection delegate
spreadsheetCSSStyleDeclarationSectionSelectProperty(property)
{
this.scrollToSectionAndHighlightProperty(property);
}
spreadsheetCSSStyleDeclarationSectionStartEditingAdjacentRule(currentSection, delta)
{
console.assert(delta !== 0);
let index = this._sections.indexOf(currentSection);
if (index < 0) {
if (delta < 0)
index = this._sections.length;
else if (delta > 0)
index = -1;
}
index += delta;
while (this._sections[index] !== currentSection) {
if (index < 0)
index = this._sections.length - 1;
else if (index >= this._sections.length) {
if (this._delegate && this._delegate.styleDetailsPanelFocusFilterBar) {
this._delegate.styleDetailsPanelFocusFilterBar(this);
break;
}
index = 0;
}
let section = this._sections[index];
if (section.editable) {
if (delta < 0)
section._propertiesEditor.startEditingLastProperty();
else
section.startEditingRuleSelector();
break;
}
index += delta;
}
}
spreadsheetCSSStyleDeclarationSectionAddNewRule(section, selector, text)
{
this._newRuleSelector = selector;
this.nodeStyles.addRule(this._newRuleSelector, text);
}
spreadsheetCSSStyleDeclarationSectionSetAllPropertyVisibilityMode(section, propertyVisibilityMode)
{
for (let section of this._sections)
section.propertyVisibilityMode = propertyVisibilityMode;
}
// Protected
layout()
{
if (!this._shouldRefreshSubviews)
return;
this._shouldRefreshSubviews = false;
if (this._suppressLayoutAfterSelectorChange) {
this._suppressLayoutAfterSelectorChange = false;
return;
}
this.removeAllSubviews();
let previousStyle = null;
let currentHeader = null;
this._headerMap.clear();
this._sections = [];
let addHeader = (text, nodeOrPseudoId) => {
currentHeader = this.element.appendChild(document.createElement("h2"));
currentHeader.classList.add("section-header");
currentHeader.append(text);
if (nodeOrPseudoId) {
if (nodeOrPseudoId instanceof WI.DOMNode) {
currentHeader.append(" ", WI.linkifyNodeReference(nodeOrPseudoId, {
maxLength: 100,
excludeRevealElement: true,
}));
} else
currentHeader.append(" ", WI.CSSManager.displayNameForPseudoId(nodeOrPseudoId));
}
};
let addSection = (section) => {
if (section.style.inherited && (!previousStyle || previousStyle.node !== section.style.node))
addHeader(WI.UIString("Inherited From", "A section of CSS rules matching an ancestor DOM node"), section.style.node);
this.addSubview(section);
this._sections.push(section);
section.needsLayout();
if (currentHeader)
this._headerMap.set(section.style, currentHeader);
previousStyle = section.style;
};
let createSection = (style) => {
let section = style[SpreadsheetRulesStyleDetailsPanel.StyleSectionSymbol];
if (!section) {
section = new WI.SpreadsheetCSSStyleDeclarationSection(this, style);
section.addEventListener(WI.SpreadsheetCSSStyleDeclarationSection.Event.FilterApplied, this._handleSectionFilterApplied, this);
section.addEventListener(WI.SpreadsheetCSSStyleDeclarationSection.Event.SelectorWillChange, this._handleSectionSelectorWillChange, this);
style[SpreadsheetRulesStyleDetailsPanel.StyleSectionSymbol] = section;
}
if (this._newRuleSelector === style.selectorText && style.enabledProperties.length === 0)
section.startEditingRuleSelector();
addSection(section);
};
let addedPseudoStyles = false;
let addPseudoStyles = () => {
if (addedPseudoStyles)
return;
// Add all pseudo styles before any inherited rules.
let beforePseudoId = null;
let afterPseudoId = null;
if (InspectorBackend.Enum.CSS.PseudoId) {
beforePseudoId = WI.CSSManager.PseudoSelectorNames.Before;
afterPseudoId = WI.CSSManager.PseudoSelectorNames.After;
} else {
// Compatibility (iOS 12.2): CSS.PseudoId did not exist.
beforePseudoId = 4;
afterPseudoId = 5;
}
for (let [pseudoId, pseudoElementInfo] of this.nodeStyles.pseudoElements) {
let pseudoElement = null;
if (pseudoId === beforePseudoId)
pseudoElement = this.nodeStyles.node.beforePseudoElement();
else if (pseudoId === afterPseudoId)
pseudoElement = this.nodeStyles.node.afterPseudoElement();
addHeader(WI.UIString("Pseudo-Element"), pseudoElement || pseudoId);
for (let style of WI.DOMNodeStyles.uniqueOrderedStyles(pseudoElementInfo.orderedStyles))
createSection(style);
}
addedPseudoStyles = true;
};
for (let style of this.nodeStyles.uniqueOrderedStyles) {
if (style.inherited)
addPseudoStyles();
createSection(style);
}
addPseudoStyles();
this._newRuleSelector = null;
this.element.append(this._emptyFilterResultsElement);
if (this._filterText)
this.applyFilter(this._filterText);
}
filterDidChange(filterBar)
{
this.applyFilter(filterBar.filters.text);
}
// Private
_handleSectionFilterApplied(event)
{
if (!event.data.matches)
return;
this.element.classList.remove("filter-non-matching");
let header = this._headerMap.get(event.target.style);
if (header)
header.classList.remove(WI.GeneralStyleDetailsSidebarPanel.NoFilterMatchInSectionClassName);
}
_addNewRule(stylesheetId)
{
const justSelector = true;
this._newRuleSelector = this.nodeStyles.node.appropriateSelectorFor(justSelector);
const text = "";
this.nodeStyles.addRule(this._newRuleSelector, text, stylesheetId);
}
_handleSectionSelectorWillChange(event)
{
this._suppressLayoutAfterSelectorChange = true;
}
};
WI.SpreadsheetRulesStyleDetailsPanel.StyleSectionSymbol = Symbol("style-section");