Bug 1454973 - Part 2.Add the animation inspector test of RTL environment. r?daisuke draft
authorMantaroh Yoshinaga <mantaroh@gmail.com>
Fri, 22 Jun 2018 13:43:36 +0900
changeset 809453 8169271ecc05c4e52ce7a58d468a55d8d4920e37
parent 809452 aab2e3f2dc1e3f3cf4e92887720a2a266dd79ac6
push id113681
push userbmo:mantaroh@gmail.com
push dateFri, 22 Jun 2018 04:45:45 +0000
reviewersdaisuke
bugs1454973
milestone62.0a1
Bug 1454973 - Part 2.Add the animation inspector test of RTL environment. r?daisuke The patch of part 1 changed the logic of scrubber and tick label/line. So this patch will add the related tests of this Furthermoroe, If clicking the edge of scrubber area, the test might fail in RTL environment due to a calculation of click position. This patch will add the 1px margin-inline-start in RTL environment in order to prevent this case. MozReview-Commit-ID: 4XcEBq80yk2
devtools/client/inspector/animation/test/browser.ini
devtools/client/inspector/animation/test/browser_animation_current-time-scrubber-rtl.js
devtools/client/inspector/animation/test/browser_animation_current-time-scrubber.js
devtools/client/inspector/animation/test/browser_animation_keyframes-graph_keyframe-marker-rtl.js
devtools/client/inspector/animation/test/browser_animation_keyframes-graph_keyframe-marker.js
devtools/client/inspector/animation/test/browser_animation_summary-graph_delay-sign-rtl.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-rtl.js
devtools/client/inspector/animation/test/browser_animation_summary-graph_end-delay-sign.js
devtools/client/inspector/animation/test/current-time-scrubber_head.js
devtools/client/inspector/animation/test/keyframes-graph_keyframe-marker_head.js
devtools/client/inspector/animation/test/summary-graph_delay-sign_head.js
devtools/client/inspector/animation/test/summary-graph_end-delay-sign_head.js
devtools/client/themes/animation.css
--- a/devtools/client/inspector/animation/test/browser.ini
+++ b/devtools/client/inspector/animation/test/browser.ini
@@ -1,21 +1,25 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
+  current-time-scrubber_head.js
   doc_custom_playback_rate.html
   doc_frame_script.js
   doc_multi_easings.html
   doc_multi_keyframes.html
   doc_multi_timings.html
   doc_mutations_fast.html
   doc_pseudo.html
   doc_simple_animation.html
   head.js
+  keyframes-graph_keyframe-marker_head.js
+  summary-graph_delay-sign_head.js
+  summary-graph_end-delay-sign_head.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor-registry.js
   !/devtools/client/shared/test/test-actor.js
 
@@ -30,25 +34,27 @@ support-files =
 [browser_animation_animation-list_select.js]
 [browser_animation_animation-target.js]
 [browser_animation_animation-target_highlight.js]
 [browser_animation_animation-target_select.js]
 [browser_animation_animation-timeline-tick.js]
 [browser_animation_css-transition-with-playstate-idle.js]
 [browser_animation_current-time-label.js]
 [browser_animation_current-time-scrubber.js]
+[browser_animation_current-time-scrubber-rtl.js]
 [browser_animation_current-time-scrubber_each-different-creation-time-animations.js]
 [browser_animation_empty_on_invalid_nodes.js]
 [browser_animation_indication-bar.js]
 [browser_animation_inspector_exists.js]
 [browser_animation_keyframes-graph_computed-value-path-01.js]
 [browser_animation_keyframes-graph_computed-value-path-02.js]
 [browser_animation_keyframes-graph_computed-value-path_easing-hint.js]
 skip-if = (verify && !debug)
 [browser_animation_keyframes-graph_keyframe-marker.js]
+[browser_animation_keyframes-graph_keyframe-marker-rtl.js]
 [browser_animation_keyframes-progress-bar.js]
 [browser_animation_keyframes-progress-bar_after-resuming.js]
 [browser_animation_logic_auto-stop.js]
 [browser_animation_logic_avoid-updating-during-hiding.js]
 [browser_animation_logic_created-time.js]
 [browser_animation_logic_mutations.js]
 [browser_animation_logic_mutations_fast.js]
 [browser_animation_logic_scroll-amount.js]
@@ -59,14 +65,16 @@ skip-if = (verify && !debug)
 [browser_animation_playback-rate-selector.js]
 [browser_animation_pseudo-element.js]
 [browser_animation_rewind-button.js]
 [browser_animation_summary-graph_animation-name.js]
 [browser_animation_summary-graph_compositor.js]
 [browser_animation_summary-graph_computed-timing-path.js]
 [browser_animation_summary-graph_computed-timing-path_different-timescale.js]
 [browser_animation_summary-graph_delay-sign.js]
+[browser_animation_summary-graph_delay-sign-rtl.js]
 [browser_animation_summary-graph_end-delay-sign.js]
+[browser_animation_summary-graph_end-delay-sign-rtl.js]
 [browser_animation_summary-graph_effect-timing-path.js]
 [browser_animation_summary-graph_layout-by-seek.js]
 [browser_animation_summary-graph_negative-delay-path.js]
 [browser_animation_summary-graph_negative-end-delay-path.js]
 [browser_animation_summary-graph_tooltip.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_current-time-scrubber-rtl.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from current-time-scrubber_head.js */
+
+// Test for CurrentTimeScrubber on RTL environment.
+
+add_task(async function() {
+  Services.scriptloader.loadSubScript(
+    CHROME_URL_ROOT + "current-time-scrubber_head.js", this);
+  await pushPref("intl.uidirection", 1);
+  // eslint-disable-next-line no-undef
+  await testCurrentTimeScrubber(true);
+});
--- a/devtools/client/inspector/animation/test/browser_animation_current-time-scrubber.js
+++ b/devtools/client/inspector/animation/test/browser_animation_current-time-scrubber.js
@@ -1,68 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Test for following CurrentTimeScrubber and CurrentTimeScrubberController components:
-// * element existence
-// * scrubber position validity
-// * make animations currentTime to change by click on the controller
-// * mouse drag on the scrubber
+/* import-globals-from current-time-scrubber_head.js */
 
 add_task(async function() {
-  await addTab(URL_ROOT + "doc_simple_animation.html");
-  await removeAnimatedElementsExcept([".long"]);
-  const { animationInspector, panel } = await openAnimationInspector();
-
-  info("Checking scrubber controller existence");
-  const controllerEl = panel.querySelector(".current-time-scrubber-area");
-  ok(controllerEl, "scrubber controller should exist");
-
-  info("Checking scrubber existence");
-  const scrubberEl = controllerEl.querySelector(".current-time-scrubber");
-  ok(scrubberEl, "scrubber should exist");
-
-  info("Checking scrubber changes current time of animation and the position");
-  const duration = animationInspector.state.timeScale.getDuration();
-  await clickOnCurrentTimeScrubberController(animationInspector, panel, 0);
-  assertAnimationsCurrentTime(animationInspector, 0);
-  assertPosition(scrubberEl, controllerEl, 0, animationInspector);
-
-  await clickOnCurrentTimeScrubberController(animationInspector, panel, 1);
-  assertAnimationsCurrentTime(animationInspector, duration);
-  assertPosition(scrubberEl, controllerEl, duration, animationInspector);
-
-  await clickOnCurrentTimeScrubberController(animationInspector, panel, 0.5);
-  assertAnimationsCurrentTime(animationInspector, duration * 0.5);
-  assertPosition(scrubberEl, controllerEl, duration * 0.5, animationInspector);
-
-  info("Checking current time scrubber position during running");
-  // Running again
-  await clickOnPauseResumeButton(animationInspector, panel);
-  let previousX = scrubberEl.getBoundingClientRect().x;
-  await wait(100);
-  let currentX = scrubberEl.getBoundingClientRect().x;
-  isnot(previousX, currentX, "Scrubber should be moved");
-
-  info("Checking draggable on scrubber over animation list");
-  await clickOnPauseResumeButton(animationInspector, panel);
-  previousX = scrubberEl.getBoundingClientRect().x;
-  await dragOnCurrentTimeScrubber(animationInspector, panel, 0.5, 2, 30);
-  currentX = scrubberEl.getBoundingClientRect().x;
-  isnot(previousX, currentX, "Scrubber should be draggable");
-
-  info("Checking a behavior which mouse out from animation inspector area " +
-       "during dragging from controller");
-  await dragOnCurrentTimeScrubberController(animationInspector, panel, 0.5, 2);
-  ok(!panel.querySelector(".animation-list-container")
-           .classList.contains("active-scrubber"), "Click and DnD should be inactive");
+  Services.scriptloader.loadSubScript(
+    CHROME_URL_ROOT + "current-time-scrubber_head.js", this);
+  await testCurrentTimeScrubber();
 });
-
-function assertPosition(scrubberEl, controllerEl, time, animationInspector) {
-  const controllerBounds = controllerEl.getBoundingClientRect();
-  const scrubberBounds = scrubberEl.getBoundingClientRect();
-  const scrubberX = scrubberBounds.x + scrubberBounds.width / 2 - controllerBounds.x;
-  const timeScale = animationInspector.state.timeScale;
-  const expected = Math.round(time / timeScale.getDuration() * controllerBounds.width);
-  is(scrubberX, expected, `Position should be ${ expected } at ${ time }ms`);
-}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_keyframes-graph_keyframe-marker-rtl.js
@@ -0,0 +1,12 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function() {
+  Services.scriptloader.loadSubScript(
+    CHROME_URL_ROOT + "keyframes-graph_keyframe-marker_head.js", this);
+  await pushPref("intl.uidirection", 1);
+  // eslint-disable-next-line no-undef
+  await testKeyframesGraphKeyframesMarker();
+});
--- 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,173 +1,11 @@
 /* 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 marginInlineStart style
-
-const TEST_DATA = [
-  {
-    targetClass: "multi-types",
-    properties: [
-      {
-        name: "background-color",
-        expectedValues: [
-          {
-            title: "rgb(255, 0, 0)",
-            marginInlineStart: "0%",
-          },
-          {
-            title: "rgb(0, 255, 0)",
-            marginInlineStart: "100%",
-          }
-        ],
-      },
-      {
-        name: "background-repeat",
-        expectedValues: [
-          {
-            title: "space round",
-            marginInlineStart: "0%",
-          },
-          {
-            title: "round space",
-            marginInlineStart: "100%",
-          }
-        ],
-      },
-      {
-        name: "font-size",
-        expectedValues: [
-          {
-            title: "10px",
-            marginInlineStart: "0%",
-          },
-          {
-            title: "20px",
-            marginInlineStart: "100%",
-          }
-        ],
-      },
-      {
-        name: "margin-left",
-        expectedValues: [
-          {
-            title: "0px",
-            marginInlineStart: "0%",
-          },
-          {
-            title: "100px",
-            marginInlineStart: "100%",
-          }
-        ],
-      },
-      {
-        name: "opacity",
-        expectedValues: [
-          {
-            title: "0",
-            marginInlineStart: "0%",
-          },
-          {
-            title: "1",
-            marginInlineStart: "100%",
-          }
-        ],
-      },
-      {
-        name: "text-align",
-        expectedValues: [
-          {
-            title: "right",
-            marginInlineStart: "0%",
-          },
-          {
-            title: "center",
-            marginInlineStart: "100%",
-          }
-        ],
-      },
-      {
-        name: "transform",
-        expectedValues: [
-          {
-            title: "translate(0px)",
-            marginInlineStart: "0%",
-          },
-          {
-            title: "translate(100px)",
-            marginInlineStart: "100%",
-          }
-        ],
-      },
-    ],
-  },
-  {
-    targetClass: "narrow-offsets",
-    properties: [
-      {
-        name: "opacity",
-        expectedValues: [
-          {
-            title: "0",
-            marginInlineStart: "0%",
-          },
-          {
-            title: "1",
-            marginInlineStart: "10%",
-          },
-          {
-            title: "0",
-            marginInlineStart: "13%",
-          },
-          {
-            title: "1",
-            marginInlineStart: "100%",
-          },
-        ],
-      },
-    ],
-  }
-];
-
 add_task(async function() {
-  await addTab(URL_ROOT + "doc_multi_keyframes.html");
-  await removeAnimatedElementsExcept(TEST_DATA.map(t => `.${ t.targetClass }`));
-  const { animationInspector, panel } = await openAnimationInspector();
-
-  for (const { properties, targetClass } of TEST_DATA) {
-    info(`Checking keyframe marker for ${ targetClass }`);
-    await clickOnAnimationByTargetSelector(animationInspector,
-                                           panel, `.${ targetClass }`);
-
-    for (const { name, expectedValues } of properties) {
-      const testTarget = `${ name } in ${ targetClass }`;
-      info(`Checking keyframe marker for ${ testTarget }`);
-      info(`Checking keyframe marker existence for ${ testTarget }`);
-      const markerEls = panel.querySelectorAll(`.${ name } .keyframe-marker-item`);
-      is(markerEls.length, expectedValues.length,
-        `Count of keyframe marker elements of ${ testTarget } ` +
-        `should be ${ expectedValues.length }`);
-
-      for (let i = 0; i < expectedValues.length; i++) {
-        const hintTarget = `.keyframe-marker-item[${ i }] of ${ testTarget }`;
-
-        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 marginInlineStart style in ${ hintTarget }`);
-        is(markerEl.style.marginInlineStart, expectedValue.marginInlineStart,
-          `marginInlineStart in ${ hintTarget } should be ` +
-          `${ expectedValue.marginInlineStart }`);
-      }
-    }
-  }
+  Services.scriptloader.loadSubScript(
+    CHROME_URL_ROOT + "keyframes-graph_keyframe-marker_head.js", this);
+  // eslint-disable-next-line no-undef
+  await testKeyframesGraphKeyframesMarker();
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_summary-graph_delay-sign-rtl.js
@@ -0,0 +1,12 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function() {
+  Services.scriptloader.loadSubScript(
+    CHROME_URL_ROOT + "summary-graph_delay-sign_head.js", this);
+  await pushPref("intl.uidirection", 1);
+  // eslint-disable-next-line no-undef
+  await testSummaryGraphDelaySign();
+});
--- 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,84 +1,11 @@
 /* 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
-// * marginInlineStart position
-// * width
-// * additinal class
-
-const TEST_DATA = [
-  {
-    targetClass: "delay-positive",
-    expectedResult: {
-      marginInlineStart: "25%",
-      width: "25%",
-    },
-  },
-  {
-    targetClass: "delay-negative",
-    expectedResult: {
-      additionalClass: "negative",
-      marginInlineStart: "0%",
-      width: "25%",
-    },
-  },
-  {
-    targetClass: "fill-backwards-with-delay-iterationstart",
-    expectedResult: {
-      additionalClass: "fill",
-      marginInlineStart: "25%",
-      width: "25%",
-    },
-  },
-  {
-    targetClass: "fill-both",
-  },
-  {
-    targetClass: "fill-both-width-delay-iterationstart",
-    expectedResult: {
-      additionalClass: "fill",
-      marginInlineStart: "25%",
-      width: "25%",
-    },
-  },
-  {
-    targetClass: "keyframes-easing-step",
-  },
-];
-
 add_task(async function() {
-  await addTab(URL_ROOT + "doc_multi_timings.html");
-  await removeAnimatedElementsExcept(TEST_DATA.map(t => `.${ t.targetClass }`));
-  const { panel } = await openAnimationInspector();
-
-  for (const { targetClass, expectedResult } of TEST_DATA) {
-    const animationItemEl =
-      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.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),
-           "delay sign element should not have " +
-           `${ expectedResult.additionalClass } class`);
-      }
-    } else {
-      ok(!delaySignEl, "The delay sign element should not be in animation item element");
-    }
-  }
+  Services.scriptloader.loadSubScript(
+    CHROME_URL_ROOT + "summary-graph_delay-sign_head.js", this);
+  // eslint-disable-next-line no-undef
+  await testSummaryGraphDelaySign();
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_summary-graph_end-delay-sign-rtl.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from summary-graph_end-delay-sign_head.js */
+
+add_task(async function() {
+  Services.scriptloader.loadSubScript(
+    CHROME_URL_ROOT + "summary-graph_end-delay-sign_head.js", this);
+  await pushPref("intl.uidirection", 1);
+  await testSummaryGraphEndDelaySign();
+});
--- 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,77 +1,11 @@
 /* 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
-// * marginInlineStart position
-// * width
-// * additinal class
-
-const TEST_DATA = [
-  {
-    targetClass: "enddelay-positive",
-    expectedResult: {
-      marginInlineStart: "75%",
-      width: "25%",
-    },
-  },
-  {
-    targetClass: "enddelay-negative",
-    expectedResult: {
-      additionalClass: "negative",
-      marginInlineStart: "50%",
-      width: "25%",
-    },
-  },
-  {
-    targetClass: "enddelay-with-fill-forwards",
-    expectedResult: {
-      additionalClass: "fill",
-      marginInlineStart: "75%",
-      width: "25%",
-    },
-  },
-  {
-    targetClass: "enddelay-with-iterations-infinity",
-  },
-  {
-    targetClass: "delay-negative",
-  },
-];
-
 add_task(async function() {
-  await addTab(URL_ROOT + "doc_multi_timings.html");
-  await removeAnimatedElementsExcept(TEST_DATA.map(t => `.${ t.targetClass }`));
-  const { panel } = await openAnimationInspector();
-
-  for (const { targetClass, expectedResult } of TEST_DATA) {
-    const animationItemEl =
-      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.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),
-           "endDelay sign element should not have " +
-           `${ expectedResult.additionalClass } class`);
-      }
-    } else {
-      ok(!endDelaySignEl,
-        "The endDelay sign element should not be in animation item element");
-    }
-  }
+  Services.scriptloader.loadSubScript(
+    CHROME_URL_ROOT + "summary-graph_end-delay-sign_head.js", this);
+  // eslint-disable-next-line no-undef
+  await testSummaryGraphEndDelaySign();
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/current-time-scrubber_head.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head.js */
+
+// Test for following CurrentTimeScrubber and CurrentTimeScrubberController components:
+// * element existence
+// * scrubber position validity
+// * make animations currentTime to change by click on the controller
+// * mouse drag on the scrubber
+
+// eslint-disable-next-line no-unused-vars
+async function testCurrentTimeScrubber(isRTL) {
+  await addTab(URL_ROOT + "doc_simple_animation.html");
+  await removeAnimatedElementsExcept([".long"]);
+  const { animationInspector, panel } = await openAnimationInspector();
+
+  info("Checking scrubber controller existence");
+  const controllerEl = panel.querySelector(".current-time-scrubber-area");
+  ok(controllerEl, "scrubber controller should exist");
+
+  info("Checking scrubber existence");
+  const scrubberEl = controllerEl.querySelector(".current-time-scrubber");
+  ok(scrubberEl, "scrubber should exist");
+
+  info("Checking scrubber changes current time of animation and the position");
+  const duration = animationInspector.state.timeScale.getDuration();
+  await clickOnCurrentTimeScrubberController(animationInspector,
+                                             panel,
+                                             isRTL ? 1 : 0);
+  assertAnimationsCurrentTime(animationInspector, 0);
+  assertPosition(scrubberEl,
+                 controllerEl,
+                 isRTL ? duration : 0,
+                 animationInspector);
+  await clickOnCurrentTimeScrubberController(animationInspector,
+                                             panel,
+                                             isRTL ? 0 : 1);
+  assertAnimationsCurrentTime(animationInspector, duration);
+  assertPosition(scrubberEl,
+                 controllerEl,
+                 isRTL ? 0 : duration,
+                 animationInspector);
+
+  await clickOnCurrentTimeScrubberController(animationInspector, panel, 0.5);
+  assertAnimationsCurrentTime(animationInspector, duration * 0.5);
+  assertPosition(scrubberEl, controllerEl, duration * 0.5, animationInspector);
+
+  info("Checking current time scrubber position during running");
+  // Running again
+  await clickOnPauseResumeButton(animationInspector, panel);
+  let previousX = scrubberEl.getBoundingClientRect().x;
+  await wait(100);
+  let currentX = scrubberEl.getBoundingClientRect().x;
+  isnot(previousX, currentX, "Scrubber should be moved");
+
+  info("Checking draggable on scrubber over animation list");
+  await clickOnPauseResumeButton(animationInspector, panel);
+  previousX = scrubberEl.getBoundingClientRect().x;
+  await dragOnCurrentTimeScrubber(animationInspector, panel, 0.5, 2, 30);
+  currentX = scrubberEl.getBoundingClientRect().x;
+  isnot(previousX, currentX, "Scrubber should be draggable");
+
+  info("Checking a behavior which mouse out from animation inspector area " +
+       "during dragging from controller");
+  await dragOnCurrentTimeScrubberController(animationInspector, panel, 0.5, 2);
+  ok(!panel.querySelector(".animation-list-container")
+           .classList.contains("active-scrubber"), "Click and DnD should be inactive");
+}
+
+function assertPosition(scrubberEl, controllerEl, time, animationInspector) {
+  const controllerBounds = controllerEl.getBoundingClientRect();
+  const scrubberBounds = scrubberEl.getBoundingClientRect();
+  const scrubberX = scrubberBounds.x + scrubberBounds.width / 2 - controllerBounds.x;
+  const timeScale = animationInspector.state.timeScale;
+  const expected = Math.round(time / timeScale.getDuration() * controllerBounds.width);
+  is(scrubberX, expected, `Position should be ${ expected } at ${ time }ms`);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/keyframes-graph_keyframe-marker_head.js
@@ -0,0 +1,182 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head.js */
+
+// Test for following keyframe marker.
+// * element existence
+// * title
+// * and marginInlineStart style
+
+const KEYFRAMES_TEST_DATA = [
+  {
+    targetClass: "multi-types",
+    properties: [
+      {
+        name: "background-color",
+        expectedValues: [
+          {
+            title: "rgb(255, 0, 0)",
+            marginInlineStart: "0%",
+          },
+          {
+            title: "rgb(0, 255, 0)",
+            marginInlineStart: "100%",
+          }
+        ],
+      },
+      {
+        name: "background-repeat",
+        expectedValues: [
+          {
+            title: "space round",
+            marginInlineStart: "0%",
+          },
+          {
+            title: "round space",
+            marginInlineStart: "100%",
+          }
+        ],
+      },
+      {
+        name: "font-size",
+        expectedValues: [
+          {
+            title: "10px",
+            marginInlineStart: "0%",
+          },
+          {
+            title: "20px",
+            marginInlineStart: "100%",
+          }
+        ],
+      },
+      {
+        name: "margin-left",
+        expectedValues: [
+          {
+            title: "0px",
+            marginInlineStart: "0%",
+          },
+          {
+            title: "100px",
+            marginInlineStart: "100%",
+          }
+        ],
+      },
+      {
+        name: "opacity",
+        expectedValues: [
+          {
+            title: "0",
+            marginInlineStart: "0%",
+          },
+          {
+            title: "1",
+            marginInlineStart: "100%",
+          }
+        ],
+      },
+      {
+        name: "text-align",
+        expectedValues: [
+          {
+            title: "right",
+            marginInlineStart: "0%",
+          },
+          {
+            title: "center",
+            marginInlineStart: "100%",
+          }
+        ],
+      },
+      {
+        name: "transform",
+        expectedValues: [
+          {
+            title: "translate(0px)",
+            marginInlineStart: "0%",
+          },
+          {
+            title: "translate(100px)",
+            marginInlineStart: "100%",
+          }
+        ],
+      },
+    ],
+  },
+  {
+    targetClass: "narrow-offsets",
+    properties: [
+      {
+        name: "opacity",
+        expectedValues: [
+          {
+            title: "0",
+            marginInlineStart: "0%",
+          },
+          {
+            title: "1",
+            marginInlineStart: "10%",
+          },
+          {
+            title: "0",
+            marginInlineStart: "13%",
+          },
+          {
+            title: "1",
+            marginInlineStart: "100%",
+          },
+        ],
+      },
+    ],
+  }
+];
+
+/**
+ * Do test for keyframes-graph_keyframe-marker-ltf/rtl.
+ *
+ * @param {Array} testData
+ */
+// eslint-disable-next-line no-unused-vars
+async function testKeyframesGraphKeyframesMarker() {
+  await addTab(URL_ROOT + "doc_multi_keyframes.html");
+  await removeAnimatedElementsExcept(KEYFRAMES_TEST_DATA.map(t => `.${ t.targetClass }`));
+  const { animationInspector, panel } = await openAnimationInspector();
+
+  for (const { properties, targetClass } of KEYFRAMES_TEST_DATA) {
+    info(`Checking keyframe marker for ${ targetClass }`);
+    await clickOnAnimationByTargetSelector(animationInspector,
+                                           panel, `.${ targetClass }`);
+
+    for (const { name, expectedValues } of properties) {
+      const testTarget = `${ name } in ${ targetClass }`;
+      info(`Checking keyframe marker for ${ testTarget }`);
+      info(`Checking keyframe marker existence for ${ testTarget }`);
+      const markerEls = panel.querySelectorAll(`.${ name } .keyframe-marker-item`);
+      is(markerEls.length, expectedValues.length,
+        `Count of keyframe marker elements of ${ testTarget } ` +
+        `should be ${ expectedValues.length }`);
+
+      for (let i = 0; i < expectedValues.length; i++) {
+        const hintTarget = `.keyframe-marker-item[${ i }] of ${ testTarget }`;
+
+        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 marginInlineStart style in ${ hintTarget }`);
+        is(markerEl.style.marginInlineStart, expectedValue.marginInlineStart,
+          `marginInlineStart in ${ hintTarget } should be ` +
+          `${ expectedValue.marginInlineStart }`);
+      }
+    }
+  }
+}
+
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/summary-graph_delay-sign_head.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head.js */
+
+// Test for following DelaySign component works.
+// * element existance
+// * marginInlineStart position
+// * width
+// * additinal class
+
+const TEST_DATA = [
+  {
+    targetClass: "delay-positive",
+    expectedResult: {
+      marginInlineStart: "25%",
+      width: "25%",
+    },
+  },
+  {
+    targetClass: "delay-negative",
+    expectedResult: {
+      additionalClass: "negative",
+      marginInlineStart: "0%",
+      width: "25%",
+    },
+  },
+  {
+    targetClass: "fill-backwards-with-delay-iterationstart",
+    expectedResult: {
+      additionalClass: "fill",
+      marginInlineStart: "25%",
+      width: "25%",
+    },
+  },
+  {
+    targetClass: "fill-both",
+  },
+  {
+    targetClass: "fill-both-width-delay-iterationstart",
+    expectedResult: {
+      additionalClass: "fill",
+      marginInlineStart: "25%",
+      width: "25%",
+    },
+  },
+  {
+    targetClass: "keyframes-easing-step",
+  },
+];
+
+// eslint-disable-next-line no-unused-vars
+async function testSummaryGraphDelaySign() {
+  await addTab(URL_ROOT + "doc_multi_timings.html");
+  await removeAnimatedElementsExcept(TEST_DATA.map(t => `.${ t.targetClass }`));
+  const { panel } = await openAnimationInspector();
+
+  for (const { targetClass, expectedResult } of TEST_DATA) {
+    const animationItemEl =
+      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.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),
+           "delay sign element should not have " +
+           `${ expectedResult.additionalClass } class`);
+      }
+    } else {
+      ok(!delaySignEl, "The delay sign element should not be in animation item element");
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/summary-graph_end-delay-sign_head.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head.js */
+
+// Test for following EndDelaySign component works.
+// * element existance
+// * marginInlineStart position
+// * width
+// * additinal class
+
+const TEST_DATA = [
+  {
+    targetClass: "enddelay-positive",
+    expectedResult: {
+      marginInlineStart: "75%",
+      width: "25%",
+    },
+  },
+  {
+    targetClass: "enddelay-negative",
+    expectedResult: {
+      additionalClass: "negative",
+      marginInlineStart: "50%",
+      width: "25%",
+    },
+  },
+  {
+    targetClass: "enddelay-with-fill-forwards",
+    expectedResult: {
+      additionalClass: "fill",
+      marginInlineStart: "75%",
+      width: "25%",
+    },
+  },
+  {
+    targetClass: "enddelay-with-iterations-infinity",
+  },
+  {
+    targetClass: "delay-negative",
+  },
+];
+
+// eslint-disable-next-line no-unused-vars
+async function testSummaryGraphEndDelaySign() {
+  await addTab(URL_ROOT + "doc_multi_timings.html");
+  await removeAnimatedElementsExcept(TEST_DATA.map(t => `.${ t.targetClass }`));
+  const { panel } = await openAnimationInspector();
+
+  for (const { targetClass, expectedResult } of TEST_DATA) {
+    const animationItemEl =
+      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.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),
+           "endDelay sign element should not have " +
+           `${ expectedResult.additionalClass } class`);
+      }
+    } else {
+      ok(!endDelaySignEl,
+        "The endDelay sign element should not be in animation item element");
+    }
+  }
+}
--- a/devtools/client/themes/animation.css
+++ b/devtools/client/themes/animation.css
@@ -101,26 +101,32 @@ select.playback-rate-selector.devtools-b
 }
 
 /* Current Time Scrubber */
 .current-time-scrubber-area {
   grid-column: 2 / 3;
   z-index: 2;
 }
 
-.current-time-scrubber-area::before {
+.current-time-scrubber-area:dir(ltr)::before,
+.current-time-scrubber-area:dir(rtl)::after {
   content: "";
   cursor: col-resize;
   height: var(--devtools-toolbar-height);
   pointer-events: auto;
   position: absolute;
   /* In order to click on edge of current-time-scrubber-controller element */
   width: calc(100% + 1px);
 }
 
+.current-time-scrubber-area:dir(rtl)::after {
+  /* In order to click on the start edge of current-time-scrubber-area element on RTL */
+  margin-inline-start: -1px;
+}
+
 .indication-bar.current-time-scrubber {
   cursor: col-resize;
   pointer-events: auto;
   width: 12px;
   transform: translateX(-6px);
 }
 
 .indication-bar.current-time-scrubber:dir(rtl) {