From 8c04cbad2397f5511be93e172ef16319770c2db7 Mon Sep 17 00:00:00 2001 From: kim dohun Date: Sat, 9 Aug 2025 23:14:00 +0900 Subject: [PATCH 1/2] new_audit(non-composited-animations): add UIString for custom properties --- core/audits/non-composited-animations.js | 48 ++++++- .../audits/non-composited-animations-test.js | 125 ++++++++++++++++++ 2 files changed, 168 insertions(+), 5 deletions(-) diff --git a/core/audits/non-composited-animations.js b/core/audits/non-composited-animations.js index 8c6e7fb48547..e0a807b9f3ef 100644 --- a/core/audits/non-composited-animations.js +++ b/core/audits/non-composited-animations.js @@ -32,6 +32,14 @@ const UIStrings = { =1 {Unsupported CSS Property: {properties}} other {Unsupported CSS Properties: {properties}} }`, + /** + * @description [ICU Syntax] Descriptive reason for why a user-provided animation failed to be optimized by the browser due to custom CSS properties (CSS variables) not being supported on the compositor. Shown in a table with a list of other potential failure reasons. + * @example {--swing-y, --rotation} properties + */ + unsupportedCustomCSSProperty: `{propertyCount, plural, + =1 {Custom CSS properties cannot be animated on the compositor: {properties}} + other {Custom CSS properties cannot be animated on the compositor: {properties}} + }`, /** Descriptive reason for why a user-provided animation failed to be optimized by the browser due to a `transform` property being dependent on the size of the element itself. Shown in a table with a list of other potential failure reasons. */ transformDependsBoxSize: 'Transform-related property depends on box size', /** Descriptive reason for why a user-provided animation failed to be optimized by the browser due to a `filter` property possibly moving pixels. Shown in a table with a list of other potential failure reasons. */ @@ -90,14 +98,44 @@ function getActionableFailureReasons(failureCode, unsupportedProperties) { return ACTIONABLE_FAILURE_REASONS .filter(reason => failureCode & reason.flag) .map(reason => { + // Handle both regular CSS properties and custom CSS properties if (reason.text === UIStrings.unsupportedCSSProperty) { - return str_(reason.text, { - propertyCount: unsupportedProperties.length, - properties: unsupportedProperties.join(', '), - }); + const customProperties = new Set(); + const nonCustomProperties = new Set(); + + // Separate custom properties (starting with '--') from regular properties + for (const property of unsupportedProperties) { + if (property.startsWith('--')) { + customProperties.add(property); + } else { + nonCustomProperties.add(property); + } + } + + const reasons = []; + + // Add regular CSS properties message if any exist + if (nonCustomProperties.size > 0) { + reasons.push(str_(UIStrings.unsupportedCSSProperty, { + propertyCount: nonCustomProperties.size, + properties: Array.from(nonCustomProperties).join(', '), + })); + } + + // Add custom CSS properties message if any exist + if (customProperties.size > 0) { + reasons.push(str_(UIStrings.unsupportedCustomCSSProperty, { + propertyCount: customProperties.size, + properties: Array.from(customProperties).join(', '), + })); + } + + return reasons; } + return str_(reason.text); - }); + }) + .flat(); // Flatten array since we might return multiple messages for unsupported properties } class NonCompositedAnimations extends Audit { diff --git a/core/test/audits/non-composited-animations-test.js b/core/test/audits/non-composited-animations-test.js index 11f99eb02479..154bee29ce1c 100644 --- a/core/test/audits/non-composited-animations-test.js +++ b/core/test/audits/non-composited-animations-test.js @@ -233,4 +233,129 @@ describe('Non-composited animations audit', () => { expect(auditResult.details.items[0].subItems.items[5].animation) .toBeUndefined(); }); + + // Testing custom CSS property separation + it('separates custom CSS properties from regular properties', async () => { + const artifacts = { + TraceElements: [ + { + traceEventType: 'animation', + nodeId: 4, + node: { + devtoolsNodePath: '1,HTML,1,BODY,1,DIV', + selector: 'body > div#custom-animated', + nodeLabel: 'div', + snippet: '
', + }, + animations: [ + { + name: 'customAnimation', + failureReasonsMask: 8192, + unsupportedProperties: ['--swing-y', '--rotation', 'color', 'height'], + }, + ], + }, + ], + HostUserAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4216.0 Safari/537.36', + }; + + const auditResult = await NonCompositedAnimationsAudit.audit(artifacts); + expect(auditResult.score).toEqual(0); + expect(auditResult.details.headings).toHaveLength(2); + expect(auditResult.displayValue).toBeDisplayString('1 animated element found'); + expect(auditResult.details.items).toHaveLength(1); + + const subItems = auditResult.details.items[0].subItems.items; + + // There should be two separate messages: one for standard CSS properties, and one for custom properties. + expect(subItems).toHaveLength(2); + + const failureReasons = subItems.map(item => item.failureReason); + + expect(failureReasons[0]) + .toBeDisplayString('Unsupported CSS Properties: color, height'); + expect(failureReasons[1]) + .toBeDisplayString( + 'Custom CSS properties cannot be animated on the compositor: --swing-y, --rotation' + ); + + expect(subItems[0].animation).toEqual('customAnimation'); + expect(subItems[1].animation).toEqual('customAnimation'); + }); + + // Custom properties only + it('handles animations with only custom CSS properties', async () => { + const artifacts = { + TraceElements: [ + { + traceEventType: 'animation', + nodeId: 5, + node: { + devtoolsNodePath: '1,HTML,1,BODY,1,DIV', + selector: 'body > div#only-custom', + nodeLabel: 'div', + snippet: '
', + }, + animations: [ + { + failureReasonsMask: 8192, + unsupportedProperties: ['--yheight'], + }, + ], + }, + ], + HostUserAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4216.0 Safari/537.36', + }; + + const auditResult = await NonCompositedAnimationsAudit.audit(artifacts); + expect(auditResult.score).toEqual(0); + expect(auditResult.details.items).toHaveLength(1); + + const subItems = auditResult.details.items[0].subItems.items; + expect(subItems).toHaveLength(1); + expect(subItems[0].failureReason) + .toBeDisplayString( + 'Custom CSS properties cannot be animated on the compositor: --yheight' + ); + expect(subItems[0].animation).toBeUndefined(); + }); + + // In the case of general properties only + it('handles animations with only regular CSS properties', async () => { + const artifacts = { + TraceElements: [ + { + traceEventType: 'animation', + nodeId: 6, + node: { + devtoolsNodePath: '1,HTML,1,BODY,1,DIV', + selector: 'body > div#only-regular', + nodeLabel: 'div', + snippet: '
', + }, + animations: [ + { + name: 'regularAnimation', + failureReasonsMask: 8192, + unsupportedProperties: ['margin', 'padding'], + }, + ], + }, + ], + HostUserAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4216.0 Safari/537.36', + }; + + const auditResult = await NonCompositedAnimationsAudit.audit(artifacts); + expect(auditResult.score).toEqual(0); + expect(auditResult.details.items).toHaveLength(1); + + const subItems = auditResult.details.items[0].subItems.items; + expect(subItems).toHaveLength(1); + expect(subItems[0].failureReason) + .toBeDisplayString('Unsupported CSS Properties: margin, padding'); + expect(subItems[0].animation).toEqual('regularAnimation'); + }); }); From f26cfda4bcc6df2e103d47c75a4a6e428edc7f4d Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 28 Aug 2025 12:24:45 -0700 Subject: [PATCH 2/2] update strings --- shared/localization/locales/en-US.json | 3 +++ shared/localization/locales/en-XL.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/shared/localization/locales/en-US.json b/shared/localization/locales/en-US.json index 0abe2fa96034..bdcfd83f69ef 100644 --- a/shared/localization/locales/en-US.json +++ b/shared/localization/locales/en-US.json @@ -1229,6 +1229,9 @@ "core/audits/non-composited-animations.js | unsupportedCSSProperty": { "message": "{propertyCount, plural,\n =1 {Unsupported CSS Property: {properties}}\n other {Unsupported CSS Properties: {properties}}\n }" }, + "core/audits/non-composited-animations.js | unsupportedCustomCSSProperty": { + "message": "{propertyCount, plural,\n =1 {Custom CSS properties cannot be animated on the compositor: {properties}}\n other {Custom CSS properties cannot be animated on the compositor: {properties}}\n }" + }, "core/audits/non-composited-animations.js | unsupportedTimingParameters": { "message": "Effect has unsupported timing parameters" }, diff --git a/shared/localization/locales/en-XL.json b/shared/localization/locales/en-XL.json index 0e4d595438bf..66344d16a7b0 100644 --- a/shared/localization/locales/en-XL.json +++ b/shared/localization/locales/en-XL.json @@ -1229,6 +1229,9 @@ "core/audits/non-composited-animations.js | unsupportedCSSProperty": { "message": "{propertyCount, plural,\n =1 {Ûńŝúp̂ṕôŕt̂éd̂ ĆŜŚ P̂ŕôṕêŕt̂ý: {properties}}\n other {Ûńŝúp̂ṕôŕt̂éd̂ ĆŜŚ P̂ŕôṕêŕt̂íêś: {properties}}\n }" }, + "core/audits/non-composited-animations.js | unsupportedCustomCSSProperty": { + "message": "{propertyCount, plural,\n =1 {Ĉúŝt́ôḿ ĈŚŜ ṕr̂óp̂ér̂t́îéŝ ćâńn̂ót̂ b́ê án̂ím̂át̂éd̂ ón̂ t́ĥé ĉóm̂ṕôśît́ôŕ: {properties}}\n other {Ĉúŝt́ôḿ ĈŚŜ ṕr̂óp̂ér̂t́îéŝ ćâńn̂ót̂ b́ê án̂ím̂át̂éd̂ ón̂ t́ĥé ĉóm̂ṕôśît́ôŕ: {properties}}\n }" + }, "core/audits/non-composited-animations.js | unsupportedTimingParameters": { "message": "Êf́f̂éĉt́ ĥáŝ ún̂śûṕp̂ór̂t́êd́ t̂ím̂ín̂ǵ p̂ár̂ám̂ét̂ér̂ś" },