| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import * as i18n from '../../core/i18n/i18n.js'; |
| import * as SDK from '../../core/sdk/sdk.js'; |
| |
| import { |
| buildPropertyDefinitionText, |
| buildPropertyName, |
| buildPropertyValue, |
| isBlockContainer, |
| isFlexContainer, |
| isGridContainer, |
| isGridLanesContainer, |
| isInlineElement, |
| isMulticolContainer, |
| isPossiblyReplacedElement, |
| } from './CSSRuleValidatorHelper.js'; |
| |
| const UIStrings = { |
| /** |
| * @description The message shown in the Style pane when the user hovers over a property that has no effect due to some other property. |
| * @example {flex-wrap: nowrap} REASON_PROPERTY_DECLARATION_CODE |
| * @example {align-content} AFFECTED_PROPERTY_DECLARATION_CODE |
| */ |
| ruleViolatedBySameElementRuleReason: |
| 'The {REASON_PROPERTY_DECLARATION_CODE} property prevents {AFFECTED_PROPERTY_DECLARATION_CODE} from having an effect.', |
| /** |
| * @description The message shown in the Style pane when the user hovers over a property declaration that has no effect due to some other property. |
| * @example {flex-wrap} PROPERTY_NAME |
| * @example {nowrap} PROPERTY_VALUE |
| */ |
| ruleViolatedBySameElementRuleFix: 'Try setting {PROPERTY_NAME} to something other than {PROPERTY_VALUE}.', |
| /** |
| * @description The message shown in the Style pane when the user hovers over a property declaration that has no effect due to not being a flex or grid container. |
| * @example {display: grid} DISPLAY_GRID_RULE |
| * @example {display: flex} DISPLAY_FLEX_RULE |
| */ |
| ruleViolatedBySameElementRuleChangeFlexOrGrid: |
| 'Try adding {DISPLAY_GRID_RULE} or {DISPLAY_FLEX_RULE} to make this element into a container.', |
| /** |
| * @description The message shown in the Style pane when the user hovers over a property declaration that has no effect due to the current property value. |
| * @example {display: block} EXISTING_PROPERTY_DECLARATION |
| * @example {display: flex} TARGET_PROPERTY_DECLARATION |
| */ |
| ruleViolatedBySameElementRuleChangeSuggestion: |
| 'Try setting the {EXISTING_PROPERTY_DECLARATION} property to {TARGET_PROPERTY_DECLARATION}.', |
| /** |
| * @description The message shown in the Style pane when the user hovers over a property declaration that has no effect due to properties of the parent element. |
| * @example {display: block} REASON_PROPERTY_DECLARATION_CODE |
| * @example {flex} AFFECTED_PROPERTY_DECLARATION_CODE |
| */ |
| ruleViolatedByParentElementRuleReason: |
| 'The {REASON_PROPERTY_DECLARATION_CODE} property on the parent element prevents {AFFECTED_PROPERTY_DECLARATION_CODE} from having an effect.', |
| /** |
| * @description The message shown in the Style pane when the user hovers over a property declaration that has no effect due to the properties of the parent element. |
| * @example {display: block} EXISTING_PARENT_ELEMENT_RULE |
| * @example {display: flex} TARGET_PARENT_ELEMENT_RULE |
| */ |
| ruleViolatedByParentElementRuleFix: |
| 'Try setting the {EXISTING_PARENT_ELEMENT_RULE} property on the parent to {TARGET_PARENT_ELEMENT_RULE}.', |
| |
| /** |
| * @description The warning text shown in Elements panel when font-variation-settings don't match allowed values |
| * @example {wdth} PH1 |
| * @example {100} PH2 |
| * @example {10} PH3 |
| * @example {20} PH4 |
| * @example {Arial} PH5 |
| */ |
| fontVariationSettingsWarning: |
| 'Value for setting “{PH1}” {PH2} is outside the supported range [{PH3}, {PH4}] for font-family “{PH5}”.', |
| /** |
| * @description The message shown in the Style pane when the user hovers over a property declaration that has no effect on flex or grid child items. |
| * @example {flex} CONTAINER_DISPLAY_NAME |
| * @example {align-contents} PROPERTY_NAME |
| */ |
| flexGridContainerPropertyRuleReason: |
| 'This element is a {CONTAINER_DISPLAY_NAME} item, i.e. a child of a {CONTAINER_DISPLAY_NAME} container, but {PROPERTY_NAME} only applies to containers.', |
| /** |
| * @description The message shown in the Style pane when the user hovers over a property declaration that has no effect on flex or grid child items. |
| * @example {align-contents} PROPERTY_NAME |
| * @example {align-self} ALTERNATIVE_PROPERTY_NAME |
| */ |
| flexGridContainerPropertyRuleFix: |
| 'Try setting the {PROPERTY_NAME} on the container element or use {ALTERNATIVE_PROPERTY_NAME} instead.', |
| /** |
| * @description The messages shown in the Style pane when the user hovers over a position-anchor declaration that has no affect on a non-anchor-positioned element. |
| * @example {relative} POSITION |
| */ |
| invalidAnchorPositioning: |
| 'An anchor was defined but the element was not anchor-positioned but positioned "{POSITION}".', |
| /** |
| * @description The messages shown in the Style pane when the user hovers over a position-anchor declaration that has no affect on a non-anchor-positioned element. |
| */ |
| invalidAnchorPositioningFix: 'Set position to either "fixed" or "absolute".', |
| /** |
| * @description The messages shown in the Style pane when the user hovers over a position-anchor declaration that has no affect on hidden element. |
| */ |
| unusedAnchorPositioning: 'An anchor was defined but the element is hidden.', |
| } as const; |
| const str_ = i18n.i18n.registerUIStrings('panels/elements/CSSRuleValidator.ts', UIStrings); |
| const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); |
| |
| export const enum HintType { |
| INACTIVE_PROPERTY = 'ruleValidation', |
| DEPRECATED_PROPERTY = 'deprecatedProperty', |
| } |
| |
| export class Hint { |
| readonly #hintMessage: string; |
| readonly #possibleFixMessage: string|null; |
| readonly #learnMoreLink: string|undefined; |
| |
| constructor(hintMessage: string, possibleFixMessage: string|null, learnMoreLink?: string) { |
| this.#hintMessage = hintMessage; |
| this.#possibleFixMessage = possibleFixMessage; |
| this.#learnMoreLink = learnMoreLink; |
| } |
| |
| getMessage(): string { |
| return this.#hintMessage; |
| } |
| |
| getPossibleFixMessage(): string|null { |
| return this.#possibleFixMessage; |
| } |
| |
| getLearnMoreLink(): string|undefined { |
| return this.#learnMoreLink; |
| } |
| } |
| |
| export abstract class CSSRuleValidator { |
| readonly #affectedProperties: string[]; |
| |
| constructor(affectedProperties: string[]) { |
| this.#affectedProperties = affectedProperties; |
| } |
| |
| getApplicableProperties(): string[] { |
| return this.#affectedProperties; |
| } |
| |
| abstract getHint( |
| propertyName: string, computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>, |
| nodeName?: string, fontFaces?: SDK.CSSFontFace.CSSFontFace[]): Hint|undefined; |
| } |
| |
| export class AlignContentValidator extends CSSRuleValidator { |
| constructor() { |
| super(['align-content', 'place-content']); |
| } |
| |
| getHint(_propertyName: string, computedStyles?: Map<string, string>): Hint|undefined { |
| if (!computedStyles) { |
| return; |
| } |
| const isFlex = isFlexContainer(computedStyles); |
| if (!isFlex && !isBlockContainer(computedStyles) && !isGridContainer(computedStyles) && |
| !isGridLanesContainer(computedStyles)) { |
| const reasonPropertyDeclaration = buildPropertyDefinitionText('display', computedStyles?.get('display')); |
| const affectedPropertyDeclarationCode = buildPropertyName('align-content'); |
| |
| return new Hint( |
| i18nString(UIStrings.ruleViolatedBySameElementRuleReason, { |
| REASON_PROPERTY_DECLARATION_CODE: reasonPropertyDeclaration, |
| AFFECTED_PROPERTY_DECLARATION_CODE: affectedPropertyDeclarationCode, |
| }), |
| i18nString(UIStrings.ruleViolatedBySameElementRuleFix, { |
| PROPERTY_NAME: buildPropertyName('display'), |
| PROPERTY_VALUE: buildPropertyValue(computedStyles?.get('display') as string), |
| }), |
| ); |
| } |
| |
| if (!isFlex) { |
| return; |
| } |
| if (computedStyles.get('flex-wrap') !== 'nowrap') { |
| return; |
| } |
| |
| const reasonPropertyDeclaration = buildPropertyDefinitionText('flex-wrap', 'nowrap'); |
| const affectedPropertyDeclarationCode = buildPropertyName('align-content'); |
| |
| return new Hint( |
| i18nString(UIStrings.ruleViolatedBySameElementRuleReason, { |
| REASON_PROPERTY_DECLARATION_CODE: reasonPropertyDeclaration, |
| AFFECTED_PROPERTY_DECLARATION_CODE: affectedPropertyDeclarationCode, |
| }), |
| i18nString(UIStrings.ruleViolatedBySameElementRuleFix, { |
| PROPERTY_NAME: buildPropertyName('flex-wrap'), |
| PROPERTY_VALUE: buildPropertyValue('nowrap'), |
| }), |
| ); |
| } |
| } |
| |
| export class FlexItemValidator extends CSSRuleValidator { |
| constructor() { |
| super(['flex', 'flex-basis', 'flex-grow', 'flex-shrink']); |
| } |
| |
| getHint(propertyName: string, _computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>): Hint |
| |undefined { |
| if (!parentComputedStyles) { |
| return; |
| } |
| if (isFlexContainer(parentComputedStyles)) { |
| return; |
| } |
| const reasonPropertyDeclaration = buildPropertyDefinitionText('display', parentComputedStyles?.get('display')); |
| const affectedPropertyDeclarationCode = buildPropertyName(propertyName); |
| const targetParentPropertyDeclaration = buildPropertyDefinitionText('display', 'flex'); |
| return new Hint( |
| i18nString(UIStrings.ruleViolatedByParentElementRuleReason, { |
| REASON_PROPERTY_DECLARATION_CODE: reasonPropertyDeclaration, |
| AFFECTED_PROPERTY_DECLARATION_CODE: affectedPropertyDeclarationCode, |
| }), |
| i18nString(UIStrings.ruleViolatedByParentElementRuleFix, { |
| EXISTING_PARENT_ELEMENT_RULE: reasonPropertyDeclaration, |
| TARGET_PARENT_ELEMENT_RULE: targetParentPropertyDeclaration, |
| }), |
| ); |
| } |
| } |
| |
| export class FlexContainerValidator extends CSSRuleValidator { |
| constructor() { |
| super(['flex-direction', 'flex-flow', 'flex-wrap']); |
| } |
| |
| getHint(propertyName: string, computedStyles?: Map<string, string>): Hint|undefined { |
| if (!computedStyles) { |
| return; |
| } |
| if (isFlexContainer(computedStyles)) { |
| return; |
| } |
| const reasonPropertyDeclaration = buildPropertyDefinitionText('display', computedStyles?.get('display')); |
| const targetRuleCode = buildPropertyDefinitionText('display', 'flex'); |
| const affectedPropertyDeclarationCode = buildPropertyName(propertyName); |
| |
| return new Hint( |
| i18nString(UIStrings.ruleViolatedBySameElementRuleReason, { |
| REASON_PROPERTY_DECLARATION_CODE: reasonPropertyDeclaration, |
| AFFECTED_PROPERTY_DECLARATION_CODE: affectedPropertyDeclarationCode, |
| }), |
| i18nString(UIStrings.ruleViolatedBySameElementRuleChangeSuggestion, { |
| EXISTING_PROPERTY_DECLARATION: reasonPropertyDeclaration, |
| TARGET_PROPERTY_DECLARATION: targetRuleCode, |
| }), |
| ); |
| } |
| } |
| |
| export class GridContainerValidator extends CSSRuleValidator { |
| constructor() { |
| super([ |
| 'grid', |
| 'grid-auto-columns', |
| 'grid-auto-flow', |
| 'grid-auto-rows', |
| 'grid-template', |
| 'grid-template-areas', |
| 'grid-template-columns', |
| 'grid-template-rows', |
| ]); |
| } |
| |
| getHint(propertyName: string, computedStyles?: Map<string, string>): Hint|undefined { |
| if (isGridContainer(computedStyles) || isGridLanesContainer(computedStyles)) { |
| return; |
| } |
| const reasonPropertyDeclaration = buildPropertyDefinitionText('display', computedStyles?.get('display')); |
| const targetRuleCode = buildPropertyDefinitionText('display', 'grid'); |
| const affectedPropertyDeclarationCode = buildPropertyName(propertyName); |
| |
| return new Hint( |
| i18nString(UIStrings.ruleViolatedBySameElementRuleReason, { |
| REASON_PROPERTY_DECLARATION_CODE: reasonPropertyDeclaration, |
| AFFECTED_PROPERTY_DECLARATION_CODE: affectedPropertyDeclarationCode, |
| }), |
| i18nString(UIStrings.ruleViolatedBySameElementRuleChangeSuggestion, { |
| EXISTING_PROPERTY_DECLARATION: reasonPropertyDeclaration, |
| TARGET_PROPERTY_DECLARATION: targetRuleCode, |
| }), |
| ); |
| } |
| } |
| |
| export class GridItemValidator extends CSSRuleValidator { |
| constructor() { |
| super([ |
| 'grid-area', |
| 'grid-column', |
| 'grid-row', |
| 'grid-row-end', |
| 'grid-row-start', |
| ]); |
| } |
| |
| getHint(propertyName: string, _computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>): Hint |
| |undefined { |
| if (!parentComputedStyles) { |
| return; |
| } |
| if (isGridContainer(parentComputedStyles) || isGridLanesContainer(parentComputedStyles)) { |
| return; |
| } |
| const reasonPropertyDeclaration = buildPropertyDefinitionText('display', parentComputedStyles?.get('display')); |
| const targetParentPropertyDeclaration = buildPropertyDefinitionText('display', 'grid'); |
| const affectedPropertyDeclarationCode = buildPropertyName(propertyName); |
| |
| return new Hint( |
| i18nString(UIStrings.ruleViolatedByParentElementRuleReason, { |
| REASON_PROPERTY_DECLARATION_CODE: reasonPropertyDeclaration, |
| AFFECTED_PROPERTY_DECLARATION_CODE: affectedPropertyDeclarationCode, |
| }), |
| i18nString(UIStrings.ruleViolatedByParentElementRuleFix, { |
| EXISTING_PARENT_ELEMENT_RULE: reasonPropertyDeclaration, |
| TARGET_PARENT_ELEMENT_RULE: targetParentPropertyDeclaration, |
| }), |
| ); |
| } |
| } |
| |
| export class FlexOrGridItemValidator extends CSSRuleValidator { |
| constructor() { |
| super([ |
| 'order', |
| ]); |
| } |
| |
| getHint(propertyName: string, _computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>): Hint |
| |undefined { |
| if (!parentComputedStyles) { |
| return; |
| } |
| if (isFlexContainer(parentComputedStyles) || isGridContainer(parentComputedStyles)) { |
| return; |
| } |
| const reasonPropertyDeclaration = buildPropertyDefinitionText('display', parentComputedStyles?.get('display')); |
| const targetParentPropertyDeclaration = |
| `${buildPropertyDefinitionText('display', 'flex')} or ${buildPropertyDefinitionText('display', 'grid')}`; |
| const affectedPropertyDeclarationCode = buildPropertyName(propertyName); |
| |
| return new Hint( |
| i18nString(UIStrings.ruleViolatedByParentElementRuleReason, { |
| REASON_PROPERTY_DECLARATION_CODE: reasonPropertyDeclaration, |
| AFFECTED_PROPERTY_DECLARATION_CODE: affectedPropertyDeclarationCode, |
| }), |
| i18nString(UIStrings.ruleViolatedByParentElementRuleFix, { |
| EXISTING_PARENT_ELEMENT_RULE: reasonPropertyDeclaration, |
| TARGET_PARENT_ELEMENT_RULE: targetParentPropertyDeclaration, |
| }), |
| ); |
| } |
| } |
| |
| export class FlexGridValidator extends CSSRuleValidator { |
| constructor() { |
| // justify-content is specified to affect multicol, but we don't implement that yet. |
| super(['justify-content']); |
| } |
| |
| getHint(propertyName: string, computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>): Hint |
| |undefined { |
| if (!computedStyles) { |
| return; |
| } |
| |
| if (isFlexContainer(computedStyles) || isGridContainer(computedStyles) || isGridLanesContainer(computedStyles)) { |
| return; |
| } |
| |
| if (parentComputedStyles && |
| (isFlexContainer(parentComputedStyles) || isGridContainer(parentComputedStyles) || |
| isGridLanesContainer(parentComputedStyles))) { |
| const reasonContainerDisplayName = buildPropertyValue(parentComputedStyles.get('display') as string); |
| const reasonPropertyName = buildPropertyName(propertyName); |
| const reasonAlternativePropertyName = buildPropertyName('justify-self'); |
| return new Hint( |
| i18nString(UIStrings.flexGridContainerPropertyRuleReason, { |
| CONTAINER_DISPLAY_NAME: reasonContainerDisplayName, |
| PROPERTY_NAME: reasonPropertyName, |
| }), |
| i18nString(UIStrings.flexGridContainerPropertyRuleFix, { |
| PROPERTY_NAME: reasonPropertyName, |
| ALTERNATIVE_PROPERTY_NAME: reasonAlternativePropertyName, |
| }), |
| ); |
| } |
| |
| const reasonPropertyDeclaration = buildPropertyDefinitionText('display', computedStyles.get('display')); |
| const affectedPropertyDeclarationCode = buildPropertyName(propertyName); |
| |
| return new Hint( |
| i18nString(UIStrings.ruleViolatedBySameElementRuleReason, { |
| REASON_PROPERTY_DECLARATION_CODE: reasonPropertyDeclaration, |
| AFFECTED_PROPERTY_DECLARATION_CODE: affectedPropertyDeclarationCode, |
| }), |
| i18nString(UIStrings.ruleViolatedBySameElementRuleChangeFlexOrGrid, { |
| DISPLAY_GRID_RULE: buildPropertyDefinitionText('display', 'grid'), |
| DISPLAY_FLEX_RULE: buildPropertyDefinitionText('display', 'flex'), |
| }), |
| ); |
| } |
| } |
| |
| export class MulticolFlexGridValidator extends CSSRuleValidator { |
| constructor() { |
| super([ |
| 'gap', |
| 'column-gap', |
| 'row-gap', |
| 'grid-gap', |
| 'grid-column-gap', |
| 'grid-row-gap', |
| ]); |
| } |
| |
| getHint(propertyName: string, computedStyles?: Map<string, string>): Hint|undefined { |
| if (!computedStyles) { |
| return; |
| } |
| |
| if (isMulticolContainer(computedStyles) || isFlexContainer(computedStyles) || isGridContainer(computedStyles) || |
| isGridLanesContainer(computedStyles)) { |
| return; |
| } |
| |
| const reasonPropertyDeclaration = buildPropertyDefinitionText('display', computedStyles?.get('display')); |
| const affectedPropertyDeclarationCode = buildPropertyName(propertyName); |
| |
| return new Hint( |
| i18nString(UIStrings.ruleViolatedBySameElementRuleReason, { |
| REASON_PROPERTY_DECLARATION_CODE: reasonPropertyDeclaration, |
| AFFECTED_PROPERTY_DECLARATION_CODE: affectedPropertyDeclarationCode, |
| }), |
| i18nString(UIStrings.ruleViolatedBySameElementRuleFix, { |
| PROPERTY_NAME: buildPropertyName('display'), |
| PROPERTY_VALUE: buildPropertyValue(computedStyles?.get('display') as string), |
| }), |
| ); |
| } |
| } |
| |
| export class PaddingValidator extends CSSRuleValidator { |
| constructor() { |
| super([ |
| 'padding', |
| 'padding-top', |
| 'padding-right', |
| 'padding-bottom', |
| 'padding-left', |
| ]); |
| } |
| |
| getHint(propertyName: string, computedStyles?: Map<string, string>): Hint|undefined { |
| const display = computedStyles?.get('display'); |
| if (!display) { |
| return; |
| } |
| const tableAttributes = [ |
| 'table-row-group', |
| 'table-header-group', |
| 'table-footer-group', |
| 'table-row', |
| 'table-column-group', |
| 'table-column', |
| ]; |
| if (!tableAttributes.includes(display)) { |
| return; |
| } |
| |
| const reasonPropertyDeclaration = buildPropertyDefinitionText('display', computedStyles?.get('display')); |
| const affectedPropertyDeclarationCode = buildPropertyName(propertyName); |
| |
| return new Hint( |
| i18nString(UIStrings.ruleViolatedBySameElementRuleReason, { |
| REASON_PROPERTY_DECLARATION_CODE: reasonPropertyDeclaration, |
| AFFECTED_PROPERTY_DECLARATION_CODE: affectedPropertyDeclarationCode, |
| }), |
| i18nString(UIStrings.ruleViolatedBySameElementRuleFix, { |
| PROPERTY_NAME: buildPropertyName('display'), |
| PROPERTY_VALUE: buildPropertyValue(computedStyles?.get('display') as string), |
| }), |
| ); |
| } |
| } |
| |
| export class PositionValidator extends CSSRuleValidator { |
| constructor() { |
| super([ |
| 'top', |
| 'right', |
| 'bottom', |
| 'left', |
| ]); |
| } |
| |
| getHint(propertyName: string, computedStyles?: Map<string, string>): Hint|undefined { |
| const position = computedStyles?.get('position'); |
| if (!position) { |
| return; |
| } |
| if (position !== 'static') { |
| return; |
| } |
| |
| const reasonPropertyDeclaration = buildPropertyDefinitionText('position', computedStyles?.get('position')); |
| const affectedPropertyDeclarationCode = buildPropertyName(propertyName); |
| |
| return new Hint( |
| i18nString(UIStrings.ruleViolatedBySameElementRuleReason, { |
| REASON_PROPERTY_DECLARATION_CODE: reasonPropertyDeclaration, |
| AFFECTED_PROPERTY_DECLARATION_CODE: affectedPropertyDeclarationCode, |
| }), |
| i18nString(UIStrings.ruleViolatedBySameElementRuleFix, { |
| PROPERTY_NAME: buildPropertyName('position'), |
| PROPERTY_VALUE: buildPropertyValue(computedStyles?.get('position') as string), |
| }), |
| ); |
| } |
| } |
| |
| export class ZIndexValidator extends CSSRuleValidator { |
| constructor() { |
| super([ |
| 'z-index', |
| ]); |
| } |
| |
| getHint(propertyName: string, computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>): Hint |
| |undefined { |
| const position = computedStyles?.get('position'); |
| if (!position) { |
| return; |
| } |
| if (['absolute', 'relative', 'fixed', 'sticky'].includes(position) || isFlexContainer(parentComputedStyles) || |
| isGridContainer(parentComputedStyles)) { |
| return; |
| } |
| |
| const reasonPropertyDeclaration = buildPropertyDefinitionText('position', computedStyles?.get('position')); |
| const affectedPropertyDeclarationCode = buildPropertyName(propertyName); |
| |
| return new Hint( |
| i18nString(UIStrings.ruleViolatedBySameElementRuleReason, { |
| REASON_PROPERTY_DECLARATION_CODE: reasonPropertyDeclaration, |
| AFFECTED_PROPERTY_DECLARATION_CODE: affectedPropertyDeclarationCode, |
| }), |
| i18nString(UIStrings.ruleViolatedBySameElementRuleFix, { |
| PROPERTY_NAME: buildPropertyName('position'), |
| PROPERTY_VALUE: buildPropertyValue(computedStyles?.get('position') as string), |
| }), |
| ); |
| } |
| } |
| |
| export class PositionAnchorValidator extends CSSRuleValidator { |
| constructor() { |
| super(['position-anchor']); |
| } |
| |
| override getHint(propertyName: string, computedStyles?: Map<string, string>): Hint|undefined { |
| const position = computedStyles?.get('position') ?? 'static'; |
| const display = computedStyles?.get('display'); |
| |
| if (position !== 'absolute' && position !== 'fixed') { |
| return new Hint( |
| i18nString(UIStrings.invalidAnchorPositioning, {POSITION: position}), |
| i18nString(UIStrings.invalidAnchorPositioningFix)); |
| } |
| |
| if (display === 'none') { |
| return new Hint(i18nString(UIStrings.unusedAnchorPositioning, {POSITION: position}), null); |
| } |
| |
| return undefined; |
| } |
| } |
| |
| /** |
| * Validates if CSS width/height are having an effect on an element. |
| * See "Applies to" in https://www.w3.org/TR/css-sizing-3/#propdef-width. |
| * See "Applies to" in https://www.w3.org/TR/css-sizing-3/#propdef-height. |
| */ |
| export class SizingValidator extends CSSRuleValidator { |
| constructor() { |
| super([ |
| 'width', |
| 'height', |
| ]); |
| } |
| |
| getHint( |
| propertyName: string, computedStyles?: Map<string, string>, _parentComputedStyles?: Map<string, string>, |
| nodeName?: string): Hint|undefined { |
| if (!computedStyles || !nodeName) { |
| return; |
| } |
| if (!isInlineElement(computedStyles)) { |
| return; |
| } |
| // See https://html.spec.whatwg.org/multipage/rendering.html#replaced-elements. |
| if (isPossiblyReplacedElement(nodeName)) { |
| return; |
| } |
| |
| const reasonPropertyDeclaration = buildPropertyDefinitionText('display', computedStyles?.get('display')); |
| const affectedPropertyDeclarationCode = buildPropertyName(propertyName); |
| |
| return new Hint( |
| i18nString(UIStrings.ruleViolatedBySameElementRuleReason, { |
| REASON_PROPERTY_DECLARATION_CODE: reasonPropertyDeclaration, |
| AFFECTED_PROPERTY_DECLARATION_CODE: affectedPropertyDeclarationCode, |
| }), |
| i18nString(UIStrings.ruleViolatedBySameElementRuleFix, { |
| PROPERTY_NAME: buildPropertyName('display'), |
| PROPERTY_VALUE: buildPropertyValue(computedStyles?.get('display') as string), |
| }), |
| ); |
| } |
| } |
| |
| /** |
| * Checks that font variation settings are applicable to the actual font. |
| */ |
| export class FontVariationSettingsValidator extends CSSRuleValidator { |
| constructor() { |
| super([ |
| 'font-variation-settings', |
| ]); |
| } |
| |
| getHint( |
| _propertyName: string, computedStyles?: Map<string, string>, _parentComputedStyles?: Map<string, string>, |
| _nodeName?: string, fontFaces?: SDK.CSSFontFace.CSSFontFace[]): Hint|undefined { |
| if (!computedStyles) { |
| return; |
| } |
| const value = computedStyles.get('font-variation-settings'); |
| if (!value) { |
| return; |
| } |
| const fontFamily = computedStyles.get('font-family'); |
| if (!fontFamily) { |
| return; |
| } |
| const fontFamilies = new Set<string>(SDK.CSSPropertyParser.parseFontFamily(fontFamily)); |
| const matchingFontFaces = (fontFaces || []).filter(f => fontFamilies.has(f.getFontFamily())); |
| const variationSettings = SDK.CSSPropertyParser.parseFontVariationSettings(value); |
| const warnings = []; |
| for (const elementSetting of variationSettings) { |
| for (const font of matchingFontFaces) { |
| const fontSetting = font.getVariationAxisByTag(elementSetting.tag); |
| if (!fontSetting) { |
| continue; |
| } |
| if (elementSetting.value < fontSetting.minValue || elementSetting.value > fontSetting.maxValue) { |
| warnings.push(i18nString(UIStrings.fontVariationSettingsWarning, { |
| PH1: elementSetting.tag, |
| PH2: elementSetting.value, |
| PH3: fontSetting.minValue, |
| PH4: fontSetting.maxValue, |
| PH5: font.getFontFamily(), |
| })); |
| } |
| } |
| } |
| |
| if (!warnings.length) { |
| return; |
| } |
| |
| return new Hint( |
| warnings.join(' '), |
| '', |
| ); |
| } |
| } |
| |
| const CSS_RULE_VALIDATORS = [ |
| AlignContentValidator, |
| FlexContainerValidator, |
| FlexGridValidator, |
| FlexItemValidator, |
| FlexOrGridItemValidator, |
| FontVariationSettingsValidator, |
| GridContainerValidator, |
| GridItemValidator, |
| MulticolFlexGridValidator, |
| PaddingValidator, |
| PositionValidator, |
| PositionAnchorValidator, |
| SizingValidator, |
| ZIndexValidator, |
| ]; |
| |
| const setupCSSRulesValidators = (): Map<string, CSSRuleValidator[]> => { |
| const validatorsMap = new Map<string, CSSRuleValidator[]>(); |
| for (const validatorClass of CSS_RULE_VALIDATORS) { |
| const validator = new validatorClass(); |
| const affectedProperties = validator.getApplicableProperties(); |
| |
| for (const affectedProperty of affectedProperties) { |
| let propertyValidators = validatorsMap.get(affectedProperty); |
| if (propertyValidators === undefined) { |
| propertyValidators = []; |
| } |
| propertyValidators.push(validator); |
| |
| validatorsMap.set(affectedProperty, propertyValidators); |
| } |
| } |
| return validatorsMap; |
| }; |
| |
| export const cssRuleValidatorsMap: Map<string, CSSRuleValidator[]> = setupCSSRulesValidators(); |