[go: nahoru, domu]

Add link from `position-fallback` property to `@position-fallback` rule

Renamed `AnimationNameSwatch` to be `LinkSwatch` and reused it for linking from `position-fallback` property declaration.

See the screenshot: https://bugs.chromium.org/p/chromium/issues/attachment?aid=593023&signed_aid=KIDmOt1aBxuH53HF29QrTg==&inline=1

Bug: 1412227
Change-Id: If1a1e32cff6d07426f35085e0d1c38f5c7c98822
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/4402908
Commit-Queue: Ergün Erdoğmuş <ergunsh@chromium.org>
Reviewed-by: Changhao Han <changhaohan@chromium.org>
Auto-Submit: Ergün Erdoğmuş <ergunsh@chromium.org>
diff --git a/front_end/panels/elements/StylePropertyTreeElement.ts b/front_end/panels/elements/StylePropertyTreeElement.ts
index f38a4a6..eea5862 100644
--- a/front_end/panels/elements/StylePropertyTreeElement.ts
+++ b/front_end/panels/elements/StylePropertyTreeElement.ts
@@ -281,13 +281,13 @@
     const contentChild = document.createElement('span');
     for (let i = 0; i < animationNames.length; i++) {
       const animationName = animationNames[i];
-      const swatch = new InlineEditor.LinkSwatch.AnimationNameSwatch();
+      const swatch = new InlineEditor.LinkSwatch.LinkSwatch();
       UI.UIUtils.createTextChild(swatch, animationName);
       const isDefined = Boolean(this.matchedStylesInternal.keyframes().find(kf => kf.name().text === animationName));
       swatch.data = {
         text: animationName,
         isDefined,
-        onLinkActivate: this.handleAnimationNameDefinitionActivate.bind(this),
+        onLinkActivate: () => this.parentPaneInternal.jumpToSectionBlock(`@keyframes ${animationName}`),
       };
       contentChild.appendChild(swatch);
 
@@ -335,6 +335,22 @@
     return contentChild;
   }
 
+  private processPositionFallback(propertyText: string): Node {
+    const contentChild = document.createElement('span');
+    const swatch = new InlineEditor.LinkSwatch.LinkSwatch();
+    UI.UIUtils.createTextChild(swatch, propertyText);
+    const isDefined =
+        Boolean(this.matchedStylesInternal.positionFallbackRules().find(pf => pf.name().text === propertyText));
+    swatch.data = {
+      text: propertyText,
+      isDefined,
+      onLinkActivate: () => this.parentPaneInternal.jumpToSectionBlock(`@position-fallback ${propertyText}`),
+    };
+    contentChild.appendChild(swatch);
+
+    return contentChild;
+  }
+
   private processColor(text: string, valueChild?: Node|null): Node {
     return this.renderColorSwatch(text, valueChild);
   }
@@ -475,10 +491,6 @@
     return this.processColor(computedValue, varSwatch);
   }
 
-  private handleAnimationNameDefinitionActivate(animationName: string): void {
-    this.parentPaneInternal.jumpToSectionBlock(`@keyframes ${animationName}`);
-  }
-
   private handleVarDefinitionActivate(variableName: string): void {
     Host.userMetrics.actionTaken(Host.UserMetrics.Action.CustomPropertyLinkClicked);
     this.parentPaneInternal.jumpToProperty(variableName);
@@ -862,6 +874,7 @@
       propertyRenderer.setGridHandler(this.processGrid.bind(this));
       propertyRenderer.setAngleHandler(this.processAngle.bind(this));
       propertyRenderer.setLengthHandler(this.processLength.bind(this));
+      propertyRenderer.setPositionFallbackHandler(this.processPositionFallback.bind(this));
     }
 
     this.listItemElement.removeChildren();
diff --git a/front_end/panels/elements/StylesSidebarPane.ts b/front_end/panels/elements/StylesSidebarPane.ts
index c83a4f6..600fc0e 100644
--- a/front_end/panels/elements/StylesSidebarPane.ts
+++ b/front_end/panels/elements/StylesSidebarPane.ts
@@ -2135,6 +2135,7 @@
   private lengthHandler: ((arg0: string) => Node)|null;
   private animationNameHandler: ((data: string) => Node)|null;
   private animationHandler: ((data: string) => Node)|null;
+  private positionFallbackHandler: ((data: string) => Node)|null;
 
   constructor(rule: SDK.CSSRule.CSSRule|null, node: SDK.DOMModel.DOMNode|null, name: string, value: string) {
     this.rule = rule;
@@ -2152,6 +2153,7 @@
     this.angleHandler = null;
     this.lengthHandler = null;
     this.animationHandler = null;
+    this.positionFallbackHandler = null;
   }
 
   setColorHandler(handler: (arg0: string) => Node): void {
@@ -2198,6 +2200,10 @@
     this.lengthHandler = handler;
   }
 
+  setPositionFallbackHandler(handler: (arg0: string) => Node): void {
+    this.positionFallbackHandler = handler;
+  }
+
   renderName(): Element {
     const nameElement = document.createElement('span');
     UI.ARIAUtils.setAccessibleName(nameElement, i18nString(UIStrings.cssPropertyName, {PH1: this.propertyName}));
@@ -2292,6 +2298,12 @@
       regexes.push(/^.*$/g);
       processors.push(this.animationNameHandler);
     }
+
+    if (this.positionFallbackHandler && this.propertyName === 'position-fallback') {
+      regexes.push(/^.*$/g);
+      processors.push(this.positionFallbackHandler);
+    }
+
     const results = TextUtils.TextUtils.Utils.splitStringByRegexes(this.propertyValue, regexes);
     for (let i = 0; i < results.length; i++) {
       const result = results[i];
diff --git a/front_end/ui/legacy/components/inline_editor/LinkSwatch.ts b/front_end/ui/legacy/components/inline_editor/LinkSwatch.ts
index cf54b14..857c794 100644
--- a/front_end/ui/legacy/components/inline_editor/LinkSwatch.ts
+++ b/front_end/ui/legacy/components/inline_editor/LinkSwatch.ts
@@ -20,16 +20,15 @@
 const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
 const {render, html, Directives} = LitHtml;
 
-interface LinkSwatchRenderData {
+interface BaseLinkSwatchRenderData {
   text: string;
   title: string;
   isDefined: boolean;
   onLinkActivate: (linkText: string) => void;
 }
 
-/* eslint-disable-next-line rulesdir/check_component_naming */
-class LinkSwatch extends HTMLElement {
-  static readonly litTagName = LitHtml.literal`devtools-link-swatch`;
+class BaseLinkSwatch extends HTMLElement {
+  static readonly litTagName = LitHtml.literal`devtools-base-link-swatch`;
   protected readonly shadow = this.attachShadow({mode: 'open'});
   protected onLinkActivate: (linkText: string, event: MouseEvent|KeyboardEvent) => void = () => undefined;
 
@@ -37,7 +36,7 @@
     this.shadow.adoptedStyleSheets = [linkSwatchStyles];
   }
 
-  set data(data: LinkSwatchRenderData) {
+  set data(data: BaseLinkSwatchRenderData) {
     this. string, event: MouseEvent|KeyboardEvent): void => {
       if (event instanceof MouseEvent && event.button !== 0) {
         return;
@@ -53,7 +52,7 @@
     this.render(data);
   }
 
-  private render(data: LinkSwatchRenderData): void {
+  private render(data: BaseLinkSwatchRenderData): void {
     const {isDefined, text, title} = data;
     const classes = Directives.classMap({
       'link-swatch-link': true,
@@ -153,50 +152,50 @@
     const fallbackIncludeComma = functionParts.fallbackIncludeComma ? functionParts.fallbackIncludeComma : '';
 
     render(
-        html`<span title=${data.computedValue || ''}>${functionParts.pre}<${LinkSwatch.litTagName} .data=${
+        html`<span title=${data.computedValue || ''}>${functionParts.pre}<${BaseLinkSwatch.litTagName} .data=${
             {title, text: functionParts.variableName, isDefined, onLinkActivate} as
-            LinkSwatchRenderData}></${LinkSwatch.litTagName}>${fallbackIncludeComma}${functionParts.post}</span>`,
+            LinkSwatchRenderData}></${BaseLinkSwatch.litTagName}>${fallbackIncludeComma}${functionParts.post}</span>`,
         this.shadow, {host: this});
   }
 }
 
-interface AnimationNameSwatchRenderData {
+interface LinkSwatchRenderData {
   isDefined: boolean;
   text: string;
   onLinkActivate: (linkText: string) => void;
 }
 
-export class AnimationNameSwatch extends HTMLElement {
-  static readonly litTagName = LitHtml.literal`devtools-animation-name-swatch`;
+export class LinkSwatch extends HTMLElement {
+  static readonly litTagName = LitHtml.literal`devtools-link-swatch`;
   protected readonly shadow = this.attachShadow({mode: 'open'});
 
-  set data(data: AnimationNameSwatchRenderData) {
+  set data(data: LinkSwatchRenderData) {
     this.render(data);
   }
 
-  protected render(data: AnimationNameSwatchRenderData): void {
+  protected render(data: LinkSwatchRenderData): void {
     const {text, isDefined, onLinkActivate} = data;
     const title = isDefined ? text : i18nString(UIStrings.sIsNotDefined, {PH1: text});
     render(
-        html`<span title=${data.text}><${LinkSwatch.litTagName} .data=${{
+        html`<span title=${data.text}><${BaseLinkSwatch.litTagName} .data=${{
           text,
           isDefined,
           title,
           onLinkActivate,
-        } as LinkSwatchRenderData}></${LinkSwatch.litTagName}></span>`,
+        } as LinkSwatchRenderData}></${BaseLinkSwatch.litTagName}></span>`,
         this.shadow, {host: this});
   }
 }
 
-ComponentHelpers.CustomElements.defineComponent('devtools-animation-name-swatch', AnimationNameSwatch);
-ComponentHelpers.CustomElements.defineComponent('devtools-css-var-swatch', CSSVarSwatch);
+ComponentHelpers.CustomElements.defineComponent('devtools-base-link-swatch', BaseLinkSwatch);
 ComponentHelpers.CustomElements.defineComponent('devtools-link-swatch', LinkSwatch);
+ComponentHelpers.CustomElements.defineComponent('devtools-css-var-swatch', CSSVarSwatch);
 
 declare global {
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
   interface HTMLElementTagNameMap {
+    'devtools-base-link-swatch': BaseLinkSwatch;
     'devtools-link-swatch': LinkSwatch;
-    'devtools-animation-name-swatch': AnimationNameSwatch;
     'devtools-css-var-swatch': CSSVarSwatch;
   }
 }
diff --git a/test/unittests/front_end/panels/elements/StylePropertyTreeElement_test.ts b/test/unittests/front_end/panels/elements/StylePropertyTreeElement_test.ts
index 96faa4d..6d22771 100644
--- a/test/unittests/front_end/panels/elements/StylePropertyTreeElement_test.ts
+++ b/test/unittests/front_end/panels/elements/StylePropertyTreeElement_test.ts
@@ -143,8 +143,8 @@
          });
     });
 
-    describe('animation-name swatch', () => {
-      it('should be rendered for animation-name declaration', () => {
+    describe('animation-name', () => {
+      it('should link-swatch be rendered for animation-name declaration', () => {
         const cssAnimationNameProperty = new SDK.CSSProperty.CSSProperty(
             mockCssStyleDeclaration, 0, 'animation-name', 'first-keyframe', true, false, true, false, '', undefined);
         const stylePropertyTreeElement = new Elements.StylePropertyTreeElement.StylePropertyTreeElement(
@@ -152,24 +152,24 @@
 
         stylePropertyTreeElement.updateTitle();
 
-        const animationNameSwatch =
-            stylePropertyTreeElement.valueElement?.querySelector('devtools-animation-name-swatch');
+        const animationNameSwatch = stylePropertyTreeElement.valueElement?.querySelector('devtools-link-swatch');
         assert.isNotNull(animationNameSwatch);
       });
 
-      it('should two swatches be rendered for animation-name declaration that contains two keyframe references', () => {
-        const cssAnimationNameProperty = new SDK.CSSProperty.CSSProperty(
-            mockCssStyleDeclaration, 0, 'animation-name', 'first-keyframe, second-keyframe', true, false, true, false,
-            '', undefined);
-        const stylePropertyTreeElement = new Elements.StylePropertyTreeElement.StylePropertyTreeElement(
-            mockStylesSidebarPane, mockMatchedStyles, cssAnimationNameProperty, false, false, false, true);
+      it('should two link-swatches be rendered for animation-name declaration that contains two keyframe references',
+         () => {
+           const cssAnimationNameProperty = new SDK.CSSProperty.CSSProperty(
+               mockCssStyleDeclaration, 0, 'animation-name', 'first-keyframe, second-keyframe', true, false, true,
+               false, '', undefined);
+           const stylePropertyTreeElement = new Elements.StylePropertyTreeElement.StylePropertyTreeElement(
+               mockStylesSidebarPane, mockMatchedStyles, cssAnimationNameProperty, false, false, false, true);
 
-        stylePropertyTreeElement.updateTitle();
+           stylePropertyTreeElement.updateTitle();
 
-        const animationNameSwatches =
-            stylePropertyTreeElement.valueElement?.querySelectorAll('devtools-animation-name-swatch');
-        assert.strictEqual(animationNameSwatches?.length, 2);
-      });
+           const animationNameSwatches =
+               stylePropertyTreeElement.valueElement?.querySelectorAll('devtools-link-swatch');
+           assert.strictEqual(animationNameSwatches?.length, 2);
+         });
     });
   });
 
diff --git a/test/unittests/front_end/panels/elements/StylesSidebarPane_test.ts b/test/unittests/front_end/panels/elements/StylesSidebarPane_test.ts
index 7deb13e..6513c83 100644
--- a/test/unittests/front_end/panels/elements/StylesSidebarPane_test.ts
+++ b/test/unittests/front_end/panels/elements/StylesSidebarPane_test.ts
@@ -205,6 +205,17 @@
     const node = renderer.renderValue();
     assert.deepEqual(node.textContent, nodeContents);
   });
+
+  it('runs positionFallbackHandler for position-fallback property', () => {
+    const nodeContents = 'nodeContents';
+    const renderer =
+        new Elements.StylesSidebarPane.StylesSidebarPropertyRenderer(null, null, 'position-fallback', '--compass');
+    renderer.setPositionFallbackHandler(() => document.createTextNode(nodeContents));
+
+    const node = renderer.renderValue();
+
+    assert.deepEqual(node.textContent, nodeContents);
+  });
 });
 
 describe('IdleCallbackManager', () => {
diff --git a/test/unittests/front_end/ui/legacy/components/inline_editor/LinkSwatch_test.ts b/test/unittests/front_end/ui/legacy/components/inline_editor/LinkSwatch_test.ts
index cccfc2b..f9e6b09 100644
--- a/test/unittests/front_end/ui/legacy/components/inline_editor/LinkSwatch_test.ts
+++ b/test/unittests/front_end/ui/legacy/components/inline_editor/LinkSwatch_test.ts
@@ -9,7 +9,7 @@
 
 const {assert} = chai;
 
-function assertSwatch(swatch: InlineEditor.LinkSwatch.CSSVarSwatch, expected: {
+function assertVarSwatch(swatch: InlineEditor.LinkSwatch.CSSVarSwatch, expected: {
   valueTooltip: string|null,
   linkTooltip: string,
   isDefined: boolean,
@@ -19,7 +19,7 @@
   assertShadowRoot(swatch.shadowRoot);
   const container = swatch.shadowRoot.querySelector('span');
   assertNotNullOrUndefined(container);
-  const linkSwatch = container.querySelector('devtools-link-swatch');
+  const linkSwatch = container.querySelector('devtools-base-link-swatch');
   assertNotNullOrUndefined(linkSwatch);
 
   assertShadowRoot(linkSwatch.shadowRoot);
@@ -53,7 +53,7 @@
       onLinkActivate: () => {},
     };
 
-    assertSwatch(component, {
+    assertVarSwatch(component, {
       valueTooltip: '2px',
       linkTooltip: '2px',
       isDefined: true,
@@ -71,7 +71,7 @@
       onLinkActivate: () => {},
     };
 
-    assertSwatch(component, {
+    assertVarSwatch(component, {
       valueTooltip: null,
       linkTooltip: '--undefined is not defined',
       isDefined: false,
@@ -89,7 +89,7 @@
       onLinkActivate: () => {},
     };
 
-    assertSwatch(component, {
+    assertVarSwatch(component, {
       valueTooltip: '3px',
       linkTooltip: '--undefined is not defined',
       isDefined: false,
@@ -107,7 +107,7 @@
       onLinkActivate: () => {},
     };
 
-    assertSwatch(component, {
+    assertVarSwatch(component, {
       valueTooltip: 'green',
       linkTooltip: '--undefined-color is not defined',
       isDefined: false,
@@ -125,7 +125,7 @@
       onLinkActivate: () => {},
     };
 
-    assertSwatch(component, {
+    assertVarSwatch(component, {
       valueTooltip: 'green',
       linkTooltip: '--undefined-color is not defined',
       isDefined: false,
@@ -143,7 +143,7 @@
       onLinkActivate: () => {},
     };
 
-    assertSwatch(component, {
+    assertVarSwatch(component, {
       valueTooltip: 'red',
       linkTooltip: 'red',
       isDefined: true,
@@ -161,7 +161,7 @@
       onLinkActivate: () => {},
     };
 
-    assertSwatch(component, {
+    assertVarSwatch(component, {
       valueTooltip: 'red',
       linkTooltip: 'red',
       isDefined: true,
@@ -179,7 +179,7 @@
       onLinkActivate: () => {},
     };
 
-    assertSwatch(component, {
+    assertVarSwatch(component, {
       valueTooltip: 'red',
       linkTooltip: 'red',
       isDefined: true,
@@ -188,40 +188,39 @@
   });
 });
 
-function assertAnimationNameSwatch(swatch: InlineEditor.LinkSwatch.AnimationNameSwatch, expected: {
-  animationName: string|null,
+function assertLinkSwatch(swatch: InlineEditor.LinkSwatch.LinkSwatch, expected: {
+  text: string|null,
   title: string|null,
   isDefined: boolean,
 }) {
   assertShadowRoot(swatch.shadowRoot);
   const container = swatch.shadowRoot.querySelector('span');
   assertNotNullOrUndefined(container);
-  const linkSwatch = container.querySelector('devtools-link-swatch');
+  const linkSwatch = container.querySelector('devtools-base-link-swatch');
   assertNotNullOrUndefined(linkSwatch);
 
   assertShadowRoot(linkSwatch.shadowRoot);
   const link = linkSwatch.shadowRoot.querySelector('.link-swatch-link');
   assertNotNullOrUndefined(link);
 
-  assert.strictEqual(
-      container.getAttribute('title'), expected.animationName, 'The animation name appears as a tooltip');
+  assert.strictEqual(container.getAttribute('title'), expected.text, 'The text appears as a tooltip');
   assert.strictEqual(
       link.classList.contains('undefined'), !expected.isDefined,
       'The link only has the class undefined when the property is undefined');
   assert.strictEqual(link.getAttribute('title'), expected.title, 'The link has the right tooltip');
-  assert.strictEqual(link.textContent, expected.animationName, 'The link has the right text content');
+  assert.strictEqual(link.textContent, expected.text, 'The link has the right text content');
 }
 
-describeWithLocale('AnimationNameSwatch', () => {
+describeWithLocale('LinkSwatch', () => {
   it('can be instantiated successfully', () => {
-    const component = new InlineEditor.LinkSwatch.AnimationNameSwatch();
+    const component = new InlineEditor.LinkSwatch.LinkSwatch();
     renderElementIntoDOM(component);
 
     assert.instanceOf(component, HTMLElement, 'The swatch is an instance of HTMLElement');
   });
 
-  it('renders a simple animation name', () => {
-    const component = new InlineEditor.LinkSwatch.AnimationNameSwatch();
+  it('renders a simple text', () => {
+    const component = new InlineEditor.LinkSwatch.LinkSwatch();
     component.data = {
       text: 'test',
       isDefined: true,
@@ -229,15 +228,15 @@
     };
     renderElementIntoDOM(component);
 
-    assertAnimationNameSwatch(component, {
-      animationName: 'test',
+    assertLinkSwatch(component, {
+      text: 'test',
       title: 'test',
       isDefined: true,
     });
   });
 
-  it('renders a missing animation name', () => {
-    const component = new InlineEditor.LinkSwatch.AnimationNameSwatch();
+  it('renders a missing test', () => {
+    const component = new InlineEditor.LinkSwatch.LinkSwatch();
     component.data = {
       text: 'test',
       isDefined: false,
@@ -245,15 +244,15 @@
     };
     renderElementIntoDOM(component);
 
-    assertAnimationNameSwatch(component, {
-      animationName: 'test',
+    assertLinkSwatch(component, {
+      text: 'test',
       title: 'test is not defined',
       isDefined: false,
     });
   });
 
   it('calls the onLinkActivate callback', async () => {
-    const component = new InlineEditor.LinkSwatch.AnimationNameSwatch();
+    const component = new InlineEditor.LinkSwatch.LinkSwatch();
     let callbackCalled = false;
     component.data = {
       text: 'testHandler',
@@ -264,7 +263,7 @@
     };
 
     const element = renderElementIntoDOM(component)
-                        ?.shadowRoot?.querySelector('devtools-link-swatch')
+                        ?.shadowRoot?.querySelector('devtools-base-link-swatch')
                         ?.shadowRoot?.querySelector('.link-swatch-link');
     assertNotNullOrUndefined(element);
     element.dispatchEvent(new MouseEvent('mousedown'));