Bug 1454973 - Part 1. Support RTL in animation inspector. r?daisuke draft
authorMantaroh Yoshinaga <mantaroh@gmail.com>
Fri, 22 Jun 2018 11:28:50 +0900
changeset 809407 d9595db67cd1497920b485b0db9b5722b8f26170
parent 809377 27e90ec610a4c8f6b2a73d79cb1a4df38e822e6a
child 809408 03f439e7de6d55ebda1c23398404a09e5067b98a
push id113673
push userbmo:mantaroh@gmail.com
push dateFri, 22 Jun 2018 02:32:57 +0000
reviewersdaisuke
bugs1454973
milestone62.0a1
Bug 1454973 - Part 1. Support RTL in animation inspector. r?daisuke This patch will make animation inspector to support RTL environment. In order to support RTL in animation inspector, this patch introduce direction propertiy in each component. Toolbox get the direction once[1], so this patch will not change this direction property when changing the uidirection preference. [1] https://searchfox.org/mozilla-central/rev/42930ab9634ebf3f62aed60f7d1c1bf25c0bf00c/devtools/client/framework/toolbox.js#194-206 MozReview-Commit-ID: KA3eTuKaKPN
devtools/client/inspector/animation/animation.js
devtools/client/inspector/animation/components/AnimationListContainer.js
devtools/client/inspector/animation/components/App.js
devtools/client/inspector/animation/components/CurrentTimeScrubber.js
devtools/client/inspector/animation/components/IndicationBar.js
devtools/client/inspector/animation/components/TickLabels.js
devtools/client/inspector/animation/components/TickLines.js
devtools/client/inspector/animation/components/graph/AnimationName.js
devtools/client/inspector/animation/components/graph/DelaySign.js
devtools/client/inspector/animation/components/graph/EndDelaySign.js
devtools/client/inspector/animation/components/graph/SummaryGraph.js
devtools/client/inspector/animation/components/keyframes-graph/KeyframeMarkerItem.js
devtools/client/inspector/animation/test/browser_animation_animation-timeline-tick.js
devtools/client/inspector/animation/test/browser_animation_keyframes-graph_keyframe-marker.js
devtools/client/inspector/animation/test/browser_animation_pause-resume-button_end-time.js
devtools/client/inspector/animation/test/browser_animation_summary-graph_delay-sign.js
devtools/client/inspector/animation/test/browser_animation_summary-graph_end-delay-sign.js
devtools/client/themes/animation.css
--- a/devtools/client/inspector/animation/animation.js
+++ b/devtools/client/inspector/animation/animation.js
@@ -94,31 +94,33 @@ class AnimationInspector {
       setHighlightedNode,
       setSelectedNode,
       simulateAnimation,
       simulateAnimationForKeyframesProgressBar,
       toggleElementPicker,
     } = this;
 
     const target = this.inspector.target;
+    const direction = this.win.document.dir;
     this.animationsFront = new AnimationsFront(target.client, target.form);
     this.animationsFront.setWalkerActor(this.inspector.walker);
 
     this.animationsCurrentTimeListeners = [];
     this.isCurrentTimeSet = false;
 
     const provider = createElement(Provider,
       {
         id: "newanimationinspector",
         key: "newanimationinspector",
         store: this.inspector.store
       },
       App(
         {
           addAnimationsCurrentTimeListener,
+          direction,
           emitEventForTest,
           getAnimatedPropertyMap,
           getAnimationsCurrentTime,
           getComputedStyle,
           getNodeFromActor,
           isAnimationsRunning,
           onHideBoxModelHighlighter,
           onShowBoxModelHighlighterForNode,
--- a/devtools/client/inspector/animation/components/AnimationListContainer.js
+++ b/devtools/client/inspector/animation/components/AnimationListContainer.js
@@ -20,16 +20,17 @@ const { findOptimalTimeInterval } = requ
 // The minimum spacing between 2 time graduation headers in the timeline (px).
 const TIME_GRADUATION_MIN_SPACING = 40;
 
 class AnimationListContainer extends PureComponent {
   static get propTypes() {
     return {
       addAnimationsCurrentTimeListener: PropTypes.func.isRequired,
       animations: PropTypes.arrayOf(PropTypes.object).isRequired,
+      direction: PropTypes.string.isRequired,
       emitEventForTest: PropTypes.func.isRequired,
       getAnimatedPropertyMap: PropTypes.func.isRequired,
       getNodeFromActor: PropTypes.func.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
       removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
       selectAnimation: PropTypes.func.isRequired,
       setAnimationsCurrentTime: PropTypes.func.isRequired,
@@ -77,16 +78,17 @@ class AnimationListContainer extends Pur
 
     this.setState({ ticks });
   }
 
   render() {
     const {
       addAnimationsCurrentTimeListener,
       animations,
+      direction,
       emitEventForTest,
       getAnimatedPropertyMap,
       getNodeFromActor,
       onHideBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
       removeAnimationsCurrentTimeListener,
       selectAnimation,
       setAnimationsCurrentTime,
@@ -101,16 +103,17 @@ class AnimationListContainer extends Pur
       {
         className: "animation-list-container"
       },
       ProgressInspectionPanel(
         {
           indicator: CurrentTimeScrubber(
             {
               addAnimationsCurrentTimeListener,
+              direction,
               removeAnimationsCurrentTimeListener,
               setAnimationsCurrentTime,
               timeScale,
             }
           ),
           list: AnimationList(
             {
               animations,
--- a/devtools/client/inspector/animation/components/App.js
+++ b/devtools/client/inspector/animation/components/App.js
@@ -16,16 +16,17 @@ const NoAnimationPanel = createFactory(r
 const SplitBox = createFactory(require("devtools/client/shared/components/splitter/SplitBox"));
 
 class App extends Component {
   static get propTypes() {
     return {
       addAnimationsCurrentTimeListener: PropTypes.func.isRequired,
       animations: PropTypes.arrayOf(PropTypes.object).isRequired,
       detailVisibility: PropTypes.bool.isRequired,
+      direction: PropTypes.string.isRequired,
       emitEventForTest: PropTypes.func.isRequired,
       getAnimatedPropertyMap: PropTypes.func.isRequired,
       getAnimationsCurrentTime: PropTypes.func.isRequired,
       getComputedStyle: PropTypes.func.isRequired,
       getNodeFromActor: PropTypes.func.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
       removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
@@ -49,16 +50,17 @@ class App extends Component {
     return this.props.animations.length !== 0 || nextProps.animations.length !== 0;
   }
 
   render() {
     const {
       addAnimationsCurrentTimeListener,
       animations,
       detailVisibility,
+      direction,
       emitEventForTest,
       getAnimatedPropertyMap,
       getAnimationsCurrentTime,
       getComputedStyle,
       getNodeFromActor,
       onHideBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
       removeAnimationsCurrentTimeListener,
@@ -112,16 +114,17 @@ class App extends Component {
           ),
           endPanelControl: true,
           initialHeight: "50%",
           splitterSize: 1,
           startPanel: AnimationListContainer(
             {
               addAnimationsCurrentTimeListener,
               animations,
+              direction,
               emitEventForTest,
               getAnimatedPropertyMap,
               getNodeFromActor,
               onHideBoxModelHighlighter,
               onShowBoxModelHighlighterForNode,
               removeAnimationsCurrentTimeListener,
               selectAnimation,
               setAnimationsCurrentTime,
--- a/devtools/client/inspector/animation/components/CurrentTimeScrubber.js
+++ b/devtools/client/inspector/animation/components/CurrentTimeScrubber.js
@@ -10,16 +10,17 @@ const PropTypes = require("devtools/clie
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 
 const IndicationBar = createFactory(require("./IndicationBar"));
 
 class CurrentTimeScrubber extends PureComponent {
   static get propTypes() {
     return {
       addAnimationsCurrentTimeListener: PropTypes.func.isRequired,
+      direction: PropTypes.string.isRequired,
       removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
       setAnimationsCurrentTime: PropTypes.func.isRequired,
       timeScale: PropTypes.object.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
@@ -103,24 +104,31 @@ class CurrentTimeScrubber extends PureCo
     this.listenerTarget.removeEventListener("mouseup", this.onMouseUp);
     this.listenerTarget.classList.remove("active-scrubber");
     this.listenerTarget = null;
     this.controllerArea = null;
   }
 
   updateAnimationsCurrentTime(pageX, needRefresh) {
     const {
+      direction,
       setAnimationsCurrentTime,
       timeScale,
     } = this.props;
 
-    const time = pageX - this.controllerArea.x < 0 ?
-                   0 :
-                   (pageX - this.controllerArea.x) /
-                     this.controllerArea.width * timeScale.getDuration();
+    let progressRate = (pageX - this.controllerArea.x) / this.controllerArea.width;
+
+    if (progressRate < 0.0) {
+      progressRate = 0.0;
+    } else if (progressRate > 1.0) {
+      progressRate = 1.0;
+    }
+
+    const time = direction === "ltr" ? progressRate * timeScale.getDuration()
+                                     : (1 - progressRate) * timeScale.getDuration();
 
     setAnimationsCurrentTime(time, needRefresh);
   }
 
   render() {
     const { position } = this.state;
 
     return dom.div(
--- a/devtools/client/inspector/animation/components/IndicationBar.js
+++ b/devtools/client/inspector/animation/components/IndicationBar.js
@@ -52,17 +52,17 @@ class IndicationBar extends PureComponen
   render() {
     const { className } = this.props;
     const { offset } = this.state;
 
     return dom.div(
       {
         className: `indication-bar ${ className }`,
         style: {
-          transform: `translateX(${ offset }px)`,
+          marginInlineStart: offset + "px",
         },
       }
     );
   }
 }
 
 const mapStateToProps = state => {
   return {
--- a/devtools/client/inspector/animation/components/TickLabels.js
+++ b/devtools/client/inspector/animation/components/TickLabels.js
@@ -24,17 +24,17 @@ class TickLabels extends PureComponent {
     return dom.div(
       {
         className: "tick-labels"
       },
       ticks.map(tick =>
         dom.div(
           {
             className: "tick-label",
-            style: { left: `${ tick.position }%` },
+            style: { marginInlineStart: `${ tick.position }%` },
           },
           tick.label
         )
       )
     );
   }
 }
 
--- a/devtools/client/inspector/animation/components/TickLines.js
+++ b/devtools/client/inspector/animation/components/TickLines.js
@@ -24,17 +24,17 @@ class TickLines extends PureComponent {
     return dom.div(
       {
         className: "tick-lines"
       },
       ticks.map(tick =>
         dom.div(
           {
             className: "tick-line",
-            style: { left: `${ tick.position }%` }
+            style: { marginInlineStart: `${ tick.position }%` }
           }
         )
       )
     );
   }
 }
 
 module.exports = TickLines;
--- a/devtools/client/inspector/animation/components/graph/AnimationName.js
+++ b/devtools/client/inspector/animation/components/graph/AnimationName.js
@@ -22,17 +22,17 @@ class AnimationName extends PureComponen
 
     return dom.svg(
       {
         className: "animation-name",
       },
       dom.text(
         {
           y: "50%",
-          x: "100%"
+          x: "100%",
         },
         animation.state.name
       )
     );
   }
 }
 
 module.exports = AnimationName;
--- a/devtools/client/inspector/animation/components/graph/DelaySign.js
+++ b/devtools/client/inspector/animation/components/graph/DelaySign.js
@@ -40,16 +40,16 @@ class DelaySign extends PureComponent {
 
     return dom.div(
       {
         className: "animation-delay-sign" +
                    (delay < 0 ? " negative" : "") +
                    (fill === "both" || fill === "backwards" ? " fill" : ""),
         style: {
           width: `${ width }%`,
-          left: `${ offset }%`,
+          marginInlineStart: `${ offset }%`,
         },
       }
     );
   }
 }
 
 module.exports = DelaySign;
--- a/devtools/client/inspector/animation/components/graph/EndDelaySign.js
+++ b/devtools/client/inspector/animation/components/graph/EndDelaySign.js
@@ -44,16 +44,16 @@ class EndDelaySign extends PureComponent
 
     return dom.div(
       {
         className: "animation-end-delay-sign" +
                    (endDelay < 0 ? " negative" : "") +
                    (fill === "both" || fill === "forwards" ? " fill" : ""),
         style: {
           width: `${ width }%`,
-          left: `${ offset }%`,
+          marginInlineStart: `${ offset }%`,
         },
       }
     );
   }
 }
 
 module.exports = EndDelaySign;
--- a/devtools/client/inspector/animation/components/graph/SummaryGraph.js
+++ b/devtools/client/inspector/animation/components/graph/SummaryGraph.js
@@ -175,17 +175,17 @@ class SummaryGraph extends PureComponent
             timeScale,
           }
         )
       :
       null,
       animation.state.name ?
         AnimationName(
           {
-            animation
+            animation,
           }
         )
       :
       null
     );
   }
 }
 
--- a/devtools/client/inspector/animation/components/keyframes-graph/KeyframeMarkerItem.js
+++ b/devtools/client/inspector/animation/components/keyframes-graph/KeyframeMarkerItem.js
@@ -18,16 +18,16 @@ class KeyframeMarkerItem extends PureCom
   render() {
     const { keyframe } = this.props;
 
     return dom.li(
       {
         className: "keyframe-marker-item",
         title: keyframe.value,
         style: {
-          left: `${ keyframe.offset * 100 }%`,
+          marginInlineStart: `${ keyframe.offset * 100 }%`,
         },
       }
     );
   }
 }
 
 module.exports = KeyframeMarkerItem;
--- a/devtools/client/inspector/animation/test/browser_animation_animation-timeline-tick.js
+++ b/devtools/client/inspector/animation/test/browser_animation_animation-timeline-tick.js
@@ -57,17 +57,17 @@ function assertTickLabels(timeScale, lis
   const expectedTickItem = Math.ceil(animationDuration / interval);
 
   const timelineTickItemEls = timelineTickListEl.querySelectorAll(".tick-label");
   is(timelineTickItemEls.length, expectedTickItem,
     "The expected number of timeline ticks were found");
 
   info("Make sure graduations are evenly distributed and show the right times");
   for (const [index, tickEl] of timelineTickItemEls.entries()) {
-    const left = parseFloat(tickEl.style.left);
+    const left = parseFloat(tickEl.style.marginInlineStart);
     const expectedPos = index * interval * 100 / animationDuration;
     is(Math.round(left), Math.round(expectedPos),
       `Graduation ${ index } is positioned correctly`);
 
     // Note that the distancetoRelativeTime and formatTime functions are tested
     // separately in xpcshell test test_timeScale.js, so we assume that they
     // work here.
     const formattedTime =
--- a/devtools/client/inspector/animation/test/browser_animation_keyframes-graph_keyframe-marker.js
+++ b/devtools/client/inspector/animation/test/browser_animation_keyframes-graph_keyframe-marker.js
@@ -1,136 +1,136 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test for following keyframe marker.
 // * element existence
 // * title
-// * and left style
+// * and marginInlineStart style
 
 const TEST_DATA = [
   {
     targetClass: "multi-types",
     properties: [
       {
         name: "background-color",
         expectedValues: [
           {
             title: "rgb(255, 0, 0)",
-            left: "0%",
+            marginInlineStart: "0%",
           },
           {
             title: "rgb(0, 255, 0)",
-            left: "100%",
+            marginInlineStart: "100%",
           }
         ],
       },
       {
         name: "background-repeat",
         expectedValues: [
           {
             title: "space round",
-            left: "0%",
+            marginInlineStart: "0%",
           },
           {
             title: "round space",
-            left: "100%",
+            marginInlineStart: "100%",
           }
         ],
       },
       {
         name: "font-size",
         expectedValues: [
           {
             title: "10px",
-            left: "0%",
+            marginInlineStart: "0%",
           },
           {
             title: "20px",
-            left: "100%",
+            marginInlineStart: "100%",
           }
         ],
       },
       {
         name: "margin-left",
         expectedValues: [
           {
             title: "0px",
-            left: "0%",
+            marginInlineStart: "0%",
           },
           {
             title: "100px",
-            left: "100%",
+            marginInlineStart: "100%",
           }
         ],
       },
       {
         name: "opacity",
         expectedValues: [
           {
             title: "0",
-            left: "0%",
+            marginInlineStart: "0%",
           },
           {
             title: "1",
-            left: "100%",
+            marginInlineStart: "100%",
           }
         ],
       },
       {
         name: "text-align",
         expectedValues: [
           {
             title: "right",
-            left: "0%",
+            marginInlineStart: "0%",
           },
           {
             title: "center",
-            left: "100%",
+            marginInlineStart: "100%",
           }
         ],
       },
       {
         name: "transform",
         expectedValues: [
           {
             title: "translate(0px)",
-            left: "0%",
+            marginInlineStart: "0%",
           },
           {
             title: "translate(100px)",
-            left: "100%",
+            marginInlineStart: "100%",
           }
         ],
       },
     ],
   },
   {
     targetClass: "narrow-offsets",
     properties: [
       {
         name: "opacity",
         expectedValues: [
           {
             title: "0",
-            left: "0%",
+            marginInlineStart: "0%",
           },
           {
             title: "1",
-            left: "10%",
+            marginInlineStart: "10%",
           },
           {
             title: "0",
-            left: "13%",
+            marginInlineStart: "13%",
           },
           {
             title: "1",
-            left: "100%",
+            marginInlineStart: "100%",
           },
         ],
       },
     ],
   }
 ];
 
 add_task(async function() {
@@ -158,15 +158,16 @@ add_task(async function() {
         info(`Checking ${ hintTarget }`);
         const markerEl = markerEls[i];
         const expectedValue = expectedValues[i];
 
         info(`Checking title in ${ hintTarget }`);
         is(markerEl.getAttribute("title"), expectedValue.title,
          `title in ${ hintTarget } should be ${ expectedValue.title }`);
 
-        info(`Checking left style in ${ hintTarget }`);
-        is(markerEl.style.left, expectedValue.left,
-         `left in ${ hintTarget } should be ${ expectedValue.left }`);
+        info(`Checking marginInlineStart style in ${ hintTarget }`);
+        is(markerEl.style.marginInlineStart, expectedValue.marginInlineStart,
+          `marginInlineStart in ${ hintTarget } should be ` +
+          `${ expectedValue.marginInlineStart }`);
       }
     }
   }
 });
--- a/devtools/client/inspector/animation/test/browser_animation_pause-resume-button_end-time.js
+++ b/devtools/client/inspector/animation/test/browser_animation_pause-resume-button_end-time.js
@@ -45,11 +45,12 @@ function assertCurrentTimeLessThanDurati
   animations.forEach((animation, index) => {
     ok(animation.state.currentTime < animation.state.duration,
        `The current time of animation[${ index }] should be less than its duration`);
   });
 }
 
 function assertScrubberPosition(panel) {
   const scrubberEl = panel.querySelector(".current-time-scrubber");
-  const translateX = parseFloat(scrubberEl.style.transform.match(/-?\d+(\.\d+)?/)[0]);
-  ok(translateX >= 0, "The translateX of scrubber position should be zero or more");
+  const marginInlineStart = parseFloat(scrubberEl.style.marginInlineStart);
+  ok(marginInlineStart >= 0,
+     "The translateX of scrubber position should be zero or more");
 }
--- a/devtools/client/inspector/animation/test/browser_animation_summary-graph_delay-sign.js
+++ b/devtools/client/inspector/animation/test/browser_animation_summary-graph_delay-sign.js
@@ -1,51 +1,51 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test for following DelaySign component works.
 // * element existance
-// * left position
+// * marginInlineStart position
 // * width
 // * additinal class
 
 const TEST_DATA = [
   {
     targetClass: "delay-positive",
     expectedResult: {
-      left: "25%",
+      marginInlineStart: "25%",
       width: "25%",
     },
   },
   {
     targetClass: "delay-negative",
     expectedResult: {
       additionalClass: "negative",
-      left: "0%",
+      marginInlineStart: "0%",
       width: "25%",
     },
   },
   {
     targetClass: "fill-backwards-with-delay-iterationstart",
     expectedResult: {
       additionalClass: "fill",
-      left: "25%",
+      marginInlineStart: "25%",
       width: "25%",
     },
   },
   {
     targetClass: "fill-both",
   },
   {
     targetClass: "fill-both-width-delay-iterationstart",
     expectedResult: {
       additionalClass: "fill",
-      left: "25%",
+      marginInlineStart: "25%",
       width: "25%",
     },
   },
   {
     targetClass: "keyframes-easing-step",
   },
 ];
 
@@ -59,18 +59,18 @@ add_task(async function() {
       findAnimationItemElementsByTargetSelector(panel, `.${ targetClass }`);
 
     info(`Checking delay sign existance for ${ targetClass }`);
     const delaySignEl = animationItemEl.querySelector(".animation-delay-sign");
 
     if (expectedResult) {
       ok(delaySignEl, "The delay sign element should be in animation item element");
 
-      is(delaySignEl.style.left, expectedResult.left,
-        `Left position should be ${ expectedResult.left }`);
+      is(delaySignEl.style.marginInlineStart, expectedResult.marginInlineStart,
+        `marginInlineStart position should be ${ expectedResult.marginInlineStart }`);
       is(delaySignEl.style.width, expectedResult.width,
         `Width should be ${ expectedResult.width }`);
 
       if (expectedResult.additionalClass) {
         ok(delaySignEl.classList.contains(expectedResult.additionalClass),
           `delay sign element should have ${ expectedResult.additionalClass } class`);
       } else {
         ok(!delaySignEl.classList.contains(expectedResult.additionalClass),
--- a/devtools/client/inspector/animation/test/browser_animation_summary-graph_end-delay-sign.js
+++ b/devtools/client/inspector/animation/test/browser_animation_summary-graph_end-delay-sign.js
@@ -1,40 +1,40 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test for following EndDelaySign component works.
 // * element existance
-// * left position
+// * marginInlineStart position
 // * width
 // * additinal class
 
 const TEST_DATA = [
   {
     targetClass: "enddelay-positive",
     expectedResult: {
-      left: "75%",
+      marginInlineStart: "75%",
       width: "25%",
     },
   },
   {
     targetClass: "enddelay-negative",
     expectedResult: {
       additionalClass: "negative",
-      left: "50%",
+      marginInlineStart: "50%",
       width: "25%",
     },
   },
   {
     targetClass: "enddelay-with-fill-forwards",
     expectedResult: {
       additionalClass: "fill",
-      left: "75%",
+      marginInlineStart: "75%",
       width: "25%",
     },
   },
   {
     targetClass: "enddelay-with-iterations-infinity",
   },
   {
     targetClass: "delay-negative",
@@ -51,18 +51,18 @@ add_task(async function() {
       findAnimationItemElementsByTargetSelector(panel, `.${ targetClass }`);
 
     info(`Checking endDelay sign existance for ${ targetClass }`);
     const endDelaySignEl = animationItemEl.querySelector(".animation-end-delay-sign");
 
     if (expectedResult) {
       ok(endDelaySignEl, "The endDelay sign element should be in animation item element");
 
-      is(endDelaySignEl.style.left, expectedResult.left,
-        `Left position should be ${ expectedResult.left }`);
+      is(endDelaySignEl.style.marginInlineStart, expectedResult.marginInlineStart,
+        `marginInlineStart position should be ${ expectedResult.marginInlineStart }`);
       is(endDelaySignEl.style.width, expectedResult.width,
         `Width should be ${ expectedResult.width }`);
 
       if (expectedResult.additionalClass) {
         ok(endDelaySignEl.classList.contains(expectedResult.additionalClass),
           `endDelay sign element should have ${ expectedResult.additionalClass } class`);
       } else {
         ok(!endDelaySignEl.classList.contains(expectedResult.additionalClass),
--- a/devtools/client/themes/animation.css
+++ b/devtools/client/themes/animation.css
@@ -67,17 +67,17 @@
 .pause-resume-button.paused::before {
   background-image: var(--resume-image);
 }
 
 select.playback-rate-selector.devtools-button {
   background-image: url("chrome://devtools/skin/images/dropmarker.svg");
   background-position: calc(100% - 4px) center;
   background-repeat: no-repeat;
-  padding-right: 1em;
+  padding-inline-end: 1em;
   text-align: center;
 }
 
 select.playback-rate-selector.devtools-button:not(:empty):not(:disabled):not(.checked):hover {
   background: none;
   background-color: var(--toolbarbutton-background);
   background-image: url("chrome://devtools/skin/images/dropmarker.svg");
   background-position: calc(100% - 4px) center;
@@ -113,28 +113,32 @@ select.playback-rate-selector.devtools-b
   pointer-events: auto;
   position: absolute;
   /* In order to click on edge of current-time-scrubber-controller element */
   width: calc(100% + 1px);
 }
 
 .indication-bar.current-time-scrubber {
   cursor: col-resize;
-  margin-left: -6px;
   pointer-events: auto;
   width: 12px;
+  transform: translateX(-6px);
+}
+
+.indication-bar.current-time-scrubber:dir(rtl) {
+  transform: translateX(6px);
 }
 
 .indication-bar.current-time-scrubber::before {
   border-top-color: var(--scrubber-color);
   left: 0;
 }
 
 .indication-bar.current-time-scrubber::after {
-  border-left-color: var(--scrubber-color);
+  border-inline-start-color: var(--scrubber-color);
   left: 5px;
 }
 
 /* Animation Item */
 .animation-item.cssanimation {
   --graph-color: var(--cssanimation-color);
   --graph-opacity: 0.7;
 }
@@ -158,17 +162,23 @@ select.playback-rate-selector.devtools-b
 }
 
 /* Animation Target */
 .animation-target {
   align-items: center;
   display: flex;
   grid-column: 1 / 2;
   height: var(--graph-height);
-  padding-left: 4px;
+  padding-inline-start: 4px;
+  /* animation-target is tech term, so it should be displayed as ltr. */
+  direction: ltr;
+}
+
+.animation-item:dir(rtl).animation-target {
+  right:0;
 }
 
 /* Reps component */
 .animation-target .objectBox {
   display: flex;
   max-width: 100%;
 }
 
@@ -207,21 +217,30 @@ select.playback-rate-selector.devtools-b
   height: 100%;
   position: absolute;
   right: 0;
   top: 5px;
   width: 15px;
   -moz-context-properties: fill;
 }
 
+.animation-summary-graph.compositor:dir(rtl)::after {
+  right: unset;
+  left: 0;
+}
+
 .animation-summary-graph-path {
   height: 100%;
   width: 100%;
 }
 
+.animation-summary-graph:dir(rtl) .animation-summary-graph-path {
+  transform: scaleX(-1.0);
+}
+
 .animation-computed-timing-path path {
   fill: var(--graph-color);
   fill-opacity: var(--graph-opacity);
   vector-effect: non-scaling-stroke;
   transform: scale(1, -1);
 }
 
 .animation-computed-timing-path path.infinity:nth-child(n+2) {
@@ -268,30 +287,58 @@ select.playback-rate-selector.devtools-b
   width: 6px;
 }
 
 .animation-delay-sign.fill,
 .animation-end-delay-sign.fill {
   background-color: var(--graph-color);
 }
 
-.animation-delay-sign.negative::before {
+/* These are delay sign directions. Basically, we can't use
+  the transform due to pseudo element, So we use the left/right align. */
+
+.animation-delay-sign:dir(ltr)::before {
+  left: -3px;
+  right: unset;
+}
+
+.animation-delay-sign:dir(rtl)::before {
+  left: unset;
+  right: -3px;
+}
+
+.animation-delay-sign.negative:dir(ltr)::before {
   left: unset;
   right: -3px;
 }
 
-.animation-end-delay-sign::before {
+.animation-delay-sign.negative:dir(rtl)::before {
+  left: -3px;
+  right: unset;
+}
+
+.animation-end-delay-sign:dir(ltr)::before {
+  left: unset;
   right: -3px;
 }
 
-.animation-end-delay-sign.negative::before {
+.animation-end-delay-sign:dir(rtl)::before {
   left: -3px;
   right: unset;
 }
 
+.animation-end-delay-sign.negative:dir(ltr)::before {
+  left: -3px;
+  right: unset;
+}
+.animation-end-delay-sign.negative:dir(rtl)::before {
+  left: unset;
+  right: -3px;
+}
+
 .animation-name {
   height: 100%;
   left: 0;
   pointer-events: none;
   position: absolute;
   top: 0;
   width: calc(100% - 20px);
 }
@@ -302,16 +349,24 @@ select.playback-rate-selector.devtools-b
   paint-order: stroke;
   stroke: var(--theme-body-background);
   stroke-linejoin: round;
   stroke-opacity: .5;
   stroke-width: 4;
   text-anchor: end;
 }
 
+.animation-summary-graph:dir(rtl) .animation-name text {
+  transform: translateX(-100%);
+}
+
+.animation-summary-graph:dir(rtl) .animation-name {
+  right:0;
+}
+
 /* Animation Detail */
 .animation-detail-container {
   background-color: var(--theme-sidebar-background);
   display: flex;
   flex-direction: column;
   height: 100%;
   overflow: hidden;
   width: 100%;
@@ -360,75 +415,82 @@ select.playback-rate-selector.devtools-b
   z-index: 2;
 }
 
 .indication-bar.keyframes-progress-bar::before {
   border-top-color: var(--progress-bar-color);
 }
 
 .indication-bar.keyframes-progress-bar::after {
-  border-left-color: var(--progress-bar-color);
+  border-inline-start-color: var(--progress-bar-color);
 }
 
 /* Animated Property Item */
 .animated-property-item.unchanged {
   opacity: 0.6;
 }
 
 /* Animated Property Name */
 .animated-property-name {
   align-items: center;
   display: flex;
   height: var(--graph-height);
   justify-content: flex-end;
-  padding-right: 10px;
+  padding-inline-end: 10px;
 }
 
 .animated-property-name.compositor span {
-  padding-left: 15px;
+  padding-inline-start: 15px;
   position: relative;
 }
 
 .animated-property-list-container.cssanimation .animated-property-name.compositor {
   --fast-track-color: var(--cssanimation-color);
 }
 
 .animated-property-list-container.csstransition .animated-property-name.compositor {
   --fast-track-color: var(--csstransition-color);
 }
 
 .animated-property-list-container.scriptanimation .animated-property-name.compositor {
   --fast-track-color: var(--scriptanimation-color);
 }
 
-.animated-property-name.compositor span::before {
+.animated-property-name.compositor span:dir(ltr)::before,
+.animated-property-name.compositor span:dir(rtl)::after {
   background-image: var(--fast-track-image);
   background-repeat: no-repeat;
   background-size: contain;
   content: "";
   fill: var(--fast-track-color);
   height: 100%;
   position: absolute;
+  /* In order to support RTL/LTR both of environment, set the left and right to zero */
   left: 0;
+  right: 0;
   width: 15px;
   -moz-context-properties: fill;
 }
 
 .animated-property-name.warning span {
   text-decoration: underline dotted;
 }
 
 /* Keyframes Graph */
 .keyframes-graph {
   grid-column: 2 / 3;
   height: var(--graph-height);
   padding-top: 5px;
   position: relative;
 }
 
+.keyframes-graph:dir(rtl) .keyframes-graph-path {
+  transform: scaleX(-1.0);
+}
+
 .keyframes-graph-path {
   height: 100%;
   width: 100%;
 }
 
 .keyframes-graph-path path {
   fill: var(--teal-60);
   fill-opacity: 0.5;
@@ -478,29 +540,34 @@ select.playback-rate-selector.devtools-b
 /* Keyframe Marker List */
 .keyframe-marker-list {
   pointer-events: none;
   position: absolute;
   height: 100%;
   list-style-type: none;
   top: 0%;
   width: 100%;
+  padding-inline-start: 0;
 }
 
 .keyframe-marker-item {
   box-shadow: 0 0 0 1px var(--keyframe-marker-shadow-color);
   border-radius: 100%;
   pointer-events: auto;
   position: absolute;
   top: 50%;
   height: 10px;
   transform: translate(-5px, -3px);
   width: 10px;
 }
 
+.keyframe-marker-item:dir(rtl) {
+  transform: translate(5px, -3px);
+}
+
 .animated-property-list-container.cssanimation .keyframe-marker-item {
   background-color: var(--cssanimation-color);
 }
 
 .animated-property-list-container.csstransition .keyframe-marker-item {
   background-color: var(--csstransition-color);
 }
 
@@ -542,60 +609,64 @@ select.playback-rate-selector.devtools-b
   position: relative;
 }
 
 .tick-line {
   position: absolute;
 }
 
 .tick-line::before {
-  border-left: var(--tick-line-style);
+  border-inline-start: var(--tick-line-style);
   content: "";
   height: 100vh;
   position: fixed;
 }
 
 /* Tick Labels */
 .tick-labels {
   grid-column: 2 / 3;
   height: 100%;
   position: relative;
 }
 
 .tick-label {
-  border-left: var(--tick-line-style);
+  border-inline-start: var(--tick-line-style);
   height: 100%;
   position: absolute;
 }
 
 .animated-property-list-container .tick-label:last-child {
-  border-left: none;
-  border-right: var(--tick-line-style);
+  border-inline-start: unset;
+  border-inline-end: var(--tick-line-style);
   transform: translateX(calc(-100% + 0.5px));
 }
 
+.animated-property-list-container .tick-label:dir(rtl):last-child {
+  transform: translateX(calc(100% - 0.6px));
+}
+
 /* Indication Bar */
 .indication-bar {
   height: 100%;
   position: absolute;
 }
 
 .indication-bar::before {
-  border-left: 5px solid transparent;
-  border-right: 5px solid transparent;
+  border-inline-start: 5px solid transparent;
+  border-inline-end: 5px solid transparent;
   border-top: 5px solid;
   content: "";
   left: -5px;
   position: absolute;
   top: 0;
   width: 0;
 }
 
 .indication-bar::after {
-  border-left: 1px solid;
+  border-inline-start: 1px solid;
   content: "";
   height: 100%;
   position: absolute;
   top: 0;
   width: 0;
 }
 
 /* No Animation Panel */