tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

head.js (32161B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 /* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
      4 
      5 "use strict";
      6 
      7 // Import the inspector's head.js first (which itself imports shared-head.js).
      8 Services.scriptloader.loadSubScript(
      9  "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
     10  this
     11 );
     12 
     13 const TAB_NAME = "animationinspector";
     14 
     15 const ANIMATION_L10N = new LocalizationHelper(
     16  "devtools/client/locales/animationinspector.properties"
     17 );
     18 
     19 /**
     20 * Open the toolbox, with the inspector tool visible and the animationinspector
     21 * sidebar selected.
     22 *
     23 * @return {Promise} that resolves when the inspector is ready.
     24 */
     25 const openAnimationInspector = async function () {
     26  const { inspector, toolbox } = await openInspectorSidebarTab(TAB_NAME);
     27  await inspector.once("inspector-updated");
     28  const animationInspector = inspector.getPanel("animationinspector");
     29  const panel = inspector.panelWin.document.getElementById(
     30    "animation-container"
     31  );
     32 
     33  info("Wait for loading first content");
     34  const count = getDisplayedGraphCount(animationInspector, panel);
     35  await waitUntil(
     36    () =>
     37      panel.querySelectorAll(".animation-summary-graph-path").length >= count &&
     38      panel.querySelectorAll(".animation-target .objectBox").length >= count
     39  );
     40 
     41  if (
     42    animationInspector.state.selectedAnimation &&
     43    animationInspector.state.detailVisibility
     44  ) {
     45    await waitUntil(() => panel.querySelector(".animated-property-list"));
     46  }
     47 
     48  return { animationInspector, toolbox, inspector, panel };
     49 };
     50 
     51 /**
     52 * Close the toolbox.
     53 *
     54 * @return {Promise} that resolves when the toolbox has closed.
     55 */
     56 const closeAnimationInspector = async function () {
     57  return gDevTools.closeToolboxForTab(gBrowser.selectedTab);
     58 };
     59 
     60 /**
     61 * Remove animated elements from document except given selectors.
     62 *
     63 * @param {Array} selectors
     64 * @return {Promise}
     65 */
     66 const removeAnimatedElementsExcept = function (selectors) {
     67  return SpecialPowers.spawn(
     68    gBrowser.selectedBrowser,
     69    [selectors],
     70    selectorsChild => {
     71      function isRemovableElement(animation, selectorsInner) {
     72        for (const selector of selectorsInner) {
     73          if (animation.effect.target.matches(selector)) {
     74            return false;
     75          }
     76        }
     77 
     78        return true;
     79      }
     80 
     81      for (const animation of content.document.getAnimations()) {
     82        if (isRemovableElement(animation, selectorsChild)) {
     83          animation.effect.target.remove();
     84        }
     85      }
     86    }
     87  );
     88 };
     89 
     90 /**
     91 * Click on an animation in the timeline to select it.
     92 *
     93 * @param {AnimationInspector} animationInspector.
     94 * @param {DOMElement} panel
     95 *        #animation-container element.
     96 * @param {number} index
     97 *        The index of the animation to click on.
     98 */
     99 const clickOnAnimation = async function (animationInspector, panel, index) {
    100  info("Click on animation " + index + " in the timeline");
    101  const animationItemEl = await findAnimationItemByIndex(panel, index);
    102  const summaryGraphEl = animationItemEl.querySelector(
    103    ".animation-summary-graph"
    104  );
    105  clickOnSummaryGraph(animationInspector, panel, summaryGraphEl);
    106 };
    107 
    108 /**
    109 * Click on an animation by given selector of node which is target element of animation.
    110 *
    111 * @param {AnimationInspector} animationInspector.
    112 * @param {DOMElement} panel
    113 *        #animation-container element.
    114 * @param {string} selector
    115 *        Selector of node which is target element of animation.
    116 */
    117 const clickOnAnimationByTargetSelector = async function (
    118  animationInspector,
    119  panel,
    120  selector
    121 ) {
    122  info(`Click on animation whose selector of target element is '${selector}'`);
    123  const animationItemEl = await findAnimationItemByTargetSelector(
    124    panel,
    125    selector
    126  );
    127  const summaryGraphEl = animationItemEl.querySelector(
    128    ".animation-summary-graph"
    129  );
    130  clickOnSummaryGraph(animationInspector, panel, summaryGraphEl);
    131 };
    132 
    133 /**
    134 * Click on close button for animation detail pane.
    135 *
    136 * @param {DOMElement} panel
    137 *        #animation-container element.
    138 */
    139 const clickOnDetailCloseButton = function (panel) {
    140  info("Click on close button for animation detail pane");
    141  const buttonEl = panel.querySelector(".animation-detail-close-button");
    142  const bounds = buttonEl.getBoundingClientRect();
    143  const x = bounds.width / 2;
    144  const y = bounds.height / 2;
    145  EventUtils.synthesizeMouse(buttonEl, x, y, {}, buttonEl.ownerGlobal);
    146 };
    147 
    148 /**
    149 * Click on pause/resume button.
    150 *
    151 * @param {AnimationInspector} animationInspector
    152 * @param {DOMElement} panel
    153 *        #animation-container element.
    154 */
    155 const clickOnPauseResumeButton = function (animationInspector, panel) {
    156  info("Click on pause/resume button");
    157  const buttonEl = panel.querySelector(".pause-resume-button");
    158  const bounds = buttonEl.getBoundingClientRect();
    159  const x = bounds.width / 2;
    160  const y = bounds.height / 2;
    161  EventUtils.synthesizeMouse(buttonEl, x, y, {}, buttonEl.ownerGlobal);
    162 };
    163 
    164 /**
    165 * Click on rewind button.
    166 *
    167 * @param {AnimationInspector} animationInspector
    168 * @param {DOMElement} panel
    169 *        #animation-container element.
    170 */
    171 const clickOnRewindButton = function (animationInspector, panel) {
    172  info("Click on rewind button");
    173  const buttonEl = panel.querySelector(".rewind-button");
    174  const bounds = buttonEl.getBoundingClientRect();
    175  const x = bounds.width / 2;
    176  const y = bounds.height / 2;
    177  EventUtils.synthesizeMouse(buttonEl, x, y, {}, buttonEl.ownerGlobal);
    178 };
    179 
    180 /**
    181 * Click on the scrubber controller pane to update the animation current time.
    182 *
    183 * @param {DOMElement} panel
    184 *        #animation-container element.
    185 * @param {number} mouseDownPosition
    186 *        rate on scrubber controller pane.
    187 *        This method calculates
    188 *        `mouseDownPosition * offsetWidth + offsetLeft of scrubber controller pane`
    189 *        as the clientX of MouseEvent.
    190 */
    191 const clickOnCurrentTimeScrubberController = function (
    192  animationInspector,
    193  panel,
    194  mouseDownPosition
    195 ) {
    196  const controllerEl = panel.querySelector(".current-time-scrubber-area");
    197  const bounds = controllerEl.getBoundingClientRect();
    198  const mousedonwX = bounds.width * mouseDownPosition;
    199 
    200  info(`Click ${mousedonwX} on scrubber controller`);
    201  EventUtils.synthesizeMouse(
    202    controllerEl,
    203    mousedonwX,
    204    0,
    205    {},
    206    controllerEl.ownerGlobal
    207  );
    208 };
    209 
    210 /**
    211 * Click on the inspect icon for the given AnimationTargetComponent.
    212 *
    213 * @param {AnimationInspector} animationInspector.
    214 * @param {DOMElement} panel
    215 *        #animation-container element.
    216 * @param {number} index
    217 *        The index of the AnimationTargetComponent to click on.
    218 */
    219 const clickOnInspectIcon = async function (animationInspector, panel, index) {
    220  info(`Click on an inspect icon in animation target component[${index}]`);
    221  const animationItemEl = await findAnimationItemByIndex(panel, index);
    222  const iconEl = animationItemEl.querySelector(
    223    ".animation-target .objectBox .highlight-node"
    224  );
    225  iconEl.scrollIntoView(false);
    226  // Use click instead of EventUtils.synthesizeMouseAtCenter because the latter
    227  // seems to trigger additional events (e.g. mouseover) that might interfere with tests
    228  iconEl.click();
    229 };
    230 
    231 /**
    232 * Change playback rate selector to select given rate.
    233 *
    234 * @param {AnimationInspector} animationInspector
    235 * @param {DOMElement} panel
    236 *        #animation-container element.
    237 * @param {number} rate
    238 */
    239 const changePlaybackRateSelector = async function (
    240  animationInspector,
    241  panel,
    242  rate
    243 ) {
    244  info(`Click on playback rate selector to select ${rate}`);
    245  const selectEl = panel.querySelector(".playback-rate-selector");
    246  const optionIndex = [...selectEl.options].findIndex(o => +o.value == rate);
    247 
    248  if (optionIndex == -1) {
    249    ok(
    250      false,
    251      `Could not find an option for rate ${rate} in the rate selector. ` +
    252        `Values are: ${[...selectEl.options].map(o => o.value)}`
    253    );
    254    return;
    255  }
    256 
    257  selectEl.focus();
    258 
    259  const win = selectEl.ownerGlobal;
    260  while (selectEl.selectedIndex != optionIndex) {
    261    const key = selectEl.selectedIndex > optionIndex ? "LEFT" : "RIGHT";
    262    EventUtils.sendKey(key, win);
    263  }
    264 };
    265 
    266 /**
    267 * Click on given summary graph element.
    268 *
    269 * @param {AnimationInspector} animationInspector
    270 * @param {DOMElement} panel
    271 *        #animation-container element.
    272 * @param {Element} summaryGraphEl
    273 */
    274 const clickOnSummaryGraph = function (
    275  animationInspector,
    276  panel,
    277  summaryGraphEl
    278 ) {
    279  // Disable pointer-events of the scrubber in order to avoid to click accidently.
    280  const scrubberEl = panel.querySelector(".current-time-scrubber");
    281  scrubberEl.style.pointerEvents = "none";
    282  // Scroll to show the timeBlock since the element may be out of displayed area.
    283  summaryGraphEl.scrollIntoView(false);
    284  EventUtils.synthesizeMouseAtCenter(
    285    summaryGraphEl,
    286    {},
    287    summaryGraphEl.ownerGlobal
    288  );
    289  // Restore the scrubber style.
    290  scrubberEl.style.pointerEvents = "unset";
    291 };
    292 
    293 /**
    294 * Click on the target node for the given AnimationTargetComponent index.
    295 *
    296 * @param {AnimationInspector} animationInspector.
    297 * @param {DOMElement} panel
    298 *        #animation-container element.
    299 * @param {number} index
    300 *        The index of the AnimationTargetComponent to click on.
    301 */
    302 const clickOnTargetNode = async function (animationInspector, panel, index) {
    303  const { inspector } = animationInspector;
    304  const { waitForHighlighterTypeShown } = getHighlighterTestHelpers(inspector);
    305  info(`Click on a target node in animation target component[${index}]`);
    306 
    307  const animationItemEl = await findAnimationItemByIndex(panel, index);
    308  const targetEl = animationItemEl.querySelector(
    309    ".animation-target .objectBox"
    310  );
    311  const onHighlight = waitForHighlighterTypeShown(
    312    inspector.highlighters.TYPES.BOXMODEL
    313  );
    314  EventUtils.synthesizeMouseAtCenter(targetEl, {}, targetEl.ownerGlobal);
    315  await onHighlight;
    316 };
    317 
    318 /**
    319 * Click on the target node for the given AnimationTargetComponent target element text
    320 *
    321 * @param {AnimationInspector} animationInspector.
    322 * @param {DOMElement} panel
    323 *        #animation-container element.
    324 * @param {string} targetText
    325 *        text displayed to represent the animation target in the panel.
    326 */
    327 const clickOnTargetNodeByTargetText = async function (
    328  animationInspector,
    329  panel,
    330  targetText
    331 ) {
    332  const { inspector } = animationInspector;
    333  const { waitForHighlighterTypeShown } = getHighlighterTestHelpers(inspector);
    334  info(`Click on a target node in animation target "${targetText}"`);
    335 
    336  const animationItemEl = await findAnimationItemByTargetText(
    337    panel,
    338    targetText
    339  );
    340  if (!animationItemEl) {
    341    throw new Error(`Couln't find target "${targetText}"`);
    342  }
    343  const targetEl = animationItemEl.querySelector(
    344    ".animation-target .objectBox"
    345  );
    346  const onHighlight = waitForHighlighterTypeShown(
    347    inspector.highlighters.TYPES.BOXMODEL
    348  );
    349  EventUtils.synthesizeMouseAtCenter(targetEl, {}, targetEl.ownerGlobal);
    350  await onHighlight;
    351 };
    352 
    353 /**
    354 * Drag on the scrubber to update the animation current time.
    355 *
    356 * @param {DOMElement} panel
    357 *        #animation-container element.
    358 * @param {number} mouseMovePixel
    359 *        Dispatch mousemove event with mouseMovePosition after mousedown.
    360 * @param {number} mouseYPixel
    361 *        Y of mouse in pixel.
    362 */
    363 const dragOnCurrentTimeScrubber = async function (
    364  animationInspector,
    365  panel,
    366  mouseMovePixel,
    367  mouseYPixel
    368 ) {
    369  const controllerEl = panel.querySelector(".current-time-scrubber");
    370  info(`Drag scrubber to X ${mouseMovePixel}`);
    371  EventUtils.synthesizeMouse(
    372    controllerEl,
    373    0,
    374    mouseYPixel,
    375    { type: "mousedown" },
    376    controllerEl.ownerGlobal
    377  );
    378  await waitUntilAnimationsPlayState(animationInspector, "paused");
    379 
    380  const animation = animationInspector.state.animations[0];
    381  let currentTime = animation.state.currentTime;
    382  EventUtils.synthesizeMouse(
    383    controllerEl,
    384    mouseMovePixel,
    385    mouseYPixel,
    386    { type: "mousemove" },
    387    controllerEl.ownerGlobal
    388  );
    389  await waitUntil(() => animation.state.currentTime !== currentTime);
    390 
    391  currentTime = animation.state.currentTime;
    392  EventUtils.synthesizeMouse(
    393    controllerEl,
    394    mouseMovePixel,
    395    mouseYPixel,
    396    { type: "mouseup" },
    397    controllerEl.ownerGlobal
    398  );
    399  await waitUntil(() => animation.state.currentTime !== currentTime);
    400 };
    401 
    402 /**
    403 * Drag on the scrubber controller pane to update the animation current time.
    404 *
    405 * @param {DOMElement} panel
    406 *        #animation-container element.
    407 * @param {number} mouseDownPosition
    408 *        rate on scrubber controller pane.
    409 *        This method calculates
    410 *        `mouseDownPosition * offsetWidth + offsetLeft of scrubber controller pane`
    411 *        as the clientX of MouseEvent.
    412 * @param {number} mouseMovePosition
    413 *        Dispatch mousemove event with mouseMovePosition after mousedown.
    414 *        Calculation for clinetX is same to above.
    415 */
    416 const dragOnCurrentTimeScrubberController = async function (
    417  animationInspector,
    418  panel,
    419  mouseDownPosition,
    420  mouseMovePosition
    421 ) {
    422  const controllerEl = panel.querySelector(".current-time-scrubber-area");
    423  const bounds = controllerEl.getBoundingClientRect();
    424  const mousedonwX = bounds.width * mouseDownPosition;
    425  const mousemoveX = bounds.width * mouseMovePosition;
    426 
    427  info(`Drag on scrubber controller from ${mousedonwX} to ${mousemoveX}`);
    428  EventUtils.synthesizeMouse(
    429    controllerEl,
    430    mousedonwX,
    431    0,
    432    { type: "mousedown" },
    433    controllerEl.ownerGlobal
    434  );
    435  await waitUntilAnimationsPlayState(animationInspector, "paused");
    436 
    437  const animation = animationInspector.state.animations[0];
    438  let currentTime = animation.state.currentTime;
    439  EventUtils.synthesizeMouse(
    440    controllerEl,
    441    mousemoveX,
    442    0,
    443    { type: "mousemove" },
    444    controllerEl.ownerGlobal
    445  );
    446  await waitUntil(() => animation.state.currentTime !== currentTime);
    447 
    448  currentTime = animation.state.currentTime;
    449  EventUtils.synthesizeMouse(
    450    controllerEl,
    451    mousemoveX,
    452    0,
    453    { type: "mouseup" },
    454    controllerEl.ownerGlobal
    455  );
    456  await waitUntil(() => animation.state.currentTime !== currentTime);
    457 };
    458 
    459 /**
    460 * Get current animation duration and rate of
    461 * clickOrDragOnCurrentTimeScrubberController in given pixels.
    462 *
    463 * @param {AnimationInspector} animationInspector
    464 * @param {DOMElement} panel
    465 *        #animation-container element.
    466 * @param {number} pixels
    467 * @return {object}
    468 *         {
    469 *           duration,
    470 *           rate,
    471 *         }
    472 */
    473 const getDurationAndRate = function (animationInspector, panel, pixels) {
    474  const controllerEl = panel.querySelector(".current-time-scrubber-area");
    475  const bounds = controllerEl.getBoundingClientRect();
    476  const duration =
    477    (animationInspector.state.timeScale.getDuration() / bounds.width) * pixels;
    478  const rate = (1 / bounds.width) * pixels;
    479  return { duration, rate };
    480 };
    481 
    482 /**
    483 * Mouse over the target node for the given AnimationTargetComponent index.
    484 *
    485 * @param {AnimationInspector} animationInspector.
    486 * @param {DOMElement} panel
    487 *        #animation-container element.
    488 * @param {number} index
    489 *        The index of the AnimationTargetComponent to click on.
    490 */
    491 const mouseOverOnTargetNode = function (animationInspector, panel, index) {
    492  info(`Mouse over on a target node in animation target component[${index}]`);
    493  const el = panel.querySelectorAll(".animation-target .objectBox")[index];
    494  el.scrollIntoView(false);
    495  EventUtils.synthesizeMouse(el, 10, 5, { type: "mouseover" }, el.ownerGlobal);
    496 };
    497 
    498 /**
    499 * Mouse out of the target node for the given AnimationTargetComponent index.
    500 *
    501 * @param {AnimationInspector} animationInspector.
    502 * @param {DOMElement} panel
    503 *        #animation-container element.
    504 * @param {number} index
    505 *        The index of the AnimationTargetComponent to click on.
    506 */
    507 const mouseOutOnTargetNode = function (animationInspector, panel, index) {
    508  info(`Mouse out on a target node in animation target component[${index}]`);
    509  const el = panel.querySelectorAll(".animation-target .objectBox")[index];
    510  el.scrollIntoView(false);
    511  EventUtils.synthesizeMouse(el, -1, -1, { type: "mouseout" }, el.ownerGlobal);
    512 };
    513 
    514 /**
    515 * Select animation inspector in sidebar and toolbar.
    516 *
    517 * @param {InspectorPanel} inspector
    518 */
    519 const selectAnimationInspector = async function (inspector) {
    520  await inspector.toolbox.selectTool("inspector");
    521  const onDispatched = waitForDispatch(inspector.store, "UPDATE_ANIMATIONS");
    522  inspector.sidebar.select("animationinspector");
    523  await onDispatched;
    524 };
    525 
    526 /**
    527 * Send keyboard event of space to given panel.
    528 *
    529 * @param {AnimationInspector} animationInspector
    530 * @param {DOMElement} target element.
    531 */
    532 const sendSpaceKeyEvent = function (animationInspector, element) {
    533  element.focus();
    534  EventUtils.sendKey("SPACE", element.ownerGlobal);
    535 };
    536 
    537 /**
    538 * Set a node class attribute to the given selector.
    539 *
    540 * @param {AnimationInspector} animationInspector
    541 * @param {string} selector
    542 * @param {string} cls
    543 *        e.g. ".ball.still"
    544 */
    545 const setClassAttribute = async function (animationInspector, selector, cls) {
    546  await SpecialPowers.spawn(
    547    gBrowser.selectedBrowser,
    548    [cls, selector],
    549    (attributeValue, selectorChild) => {
    550      const node = content.document.querySelector(selectorChild);
    551      if (!node) {
    552        return;
    553      }
    554 
    555      node.setAttribute("class", attributeValue);
    556    }
    557  );
    558 };
    559 
    560 /**
    561 * Set a new style properties to the node for the given selector.
    562 *
    563 * @param {AnimationInspector} animationInspector
    564 * @param {string} selector
    565 * @param {object} properties
    566 *        e.g. {
    567 *               animationDuration: "1000ms",
    568 *               animationTimingFunction: "linear",
    569 *             }
    570 */
    571 const setEffectTimingAndPlayback = async function (
    572  animationInspector,
    573  selector,
    574  effectTiming,
    575  playbackRate
    576 ) {
    577  await SpecialPowers.spawn(
    578    gBrowser.selectedBrowser,
    579    [selector, playbackRate, effectTiming],
    580    (selectorChild, playbackRateChild, effectTimingChild) => {
    581      let selectedAnimation = null;
    582 
    583      for (const animation of content.document.getAnimations()) {
    584        if (animation.effect.target.matches(selectorChild)) {
    585          selectedAnimation = animation;
    586          break;
    587        }
    588      }
    589 
    590      if (!selectedAnimation) {
    591        return;
    592      }
    593 
    594      selectedAnimation.playbackRate = playbackRateChild;
    595      selectedAnimation.effect.updateTiming(effectTimingChild);
    596    }
    597  );
    598 };
    599 
    600 /**
    601 * Set the sidebar width by given parameter.
    602 *
    603 * @param {string} width
    604 *        Change sidebar width by given parameter.
    605 * @param {InspectorPanel} inspector
    606 *        The instance of InspectorPanel currently loaded in the toolbox
    607 * @return {Promise} Resolves when the sidebar size changed.
    608 */
    609 const setSidebarWidth = async function (width, inspector) {
    610  const onUpdated = inspector.toolbox.once("inspector-sidebar-resized");
    611  inspector.splitBox.setState({ width });
    612  await onUpdated;
    613 };
    614 
    615 /**
    616 * Set a new style property declaration to the node for the given selector.
    617 *
    618 * @param {AnimationInspector} animationInspector
    619 * @param {string} selector
    620 * @param {string} propertyName
    621 *        e.g. "animationDuration"
    622 * @param {string} propertyValue
    623 *        e.g. "5.5s"
    624 */
    625 const setStyle = async function (
    626  animationInspector,
    627  selector,
    628  propertyName,
    629  propertyValue
    630 ) {
    631  await SpecialPowers.spawn(
    632    gBrowser.selectedBrowser,
    633    [selector, propertyName, propertyValue],
    634    (selectorChild, propertyNameChild, propertyValueChild) => {
    635      const node = content.document.querySelector(selectorChild);
    636      if (!node) {
    637        return;
    638      }
    639 
    640      node.style[propertyNameChild] = propertyValueChild;
    641    }
    642  );
    643 };
    644 
    645 /**
    646 * Set a new style properties to the node for the given selector.
    647 *
    648 * @param {AnimationInspector} animationInspector
    649 * @param {string} selector
    650 * @param {object} properties
    651 *        e.g. {
    652 *               animationDuration: "1000ms",
    653 *               animationTimingFunction: "linear",
    654 *             }
    655 */
    656 const setStyles = async function (animationInspector, selector, properties) {
    657  await SpecialPowers.spawn(
    658    gBrowser.selectedBrowser,
    659    [properties, selector],
    660    (propertiesChild, selectorChild) => {
    661      const node = content.document.querySelector(selectorChild);
    662      if (!node) {
    663        return;
    664      }
    665 
    666      for (const propertyName in propertiesChild) {
    667        const propertyValue = propertiesChild[propertyName];
    668        node.style[propertyName] = propertyValue;
    669      }
    670    }
    671  );
    672 };
    673 
    674 /**
    675 * Wait until current time of animations will be changed to given current time.
    676 *
    677 * @param {AnimationInspector} animationInspector
    678 * @param {number} currentTime
    679 */
    680 const waitUntilCurrentTimeChangedAt = async function (
    681  animationInspector,
    682  currentTime
    683 ) {
    684  info(`Wait until current time will be change to ${currentTime}`);
    685  await waitUntil(() =>
    686    animationInspector.state.animations.every(a => {
    687      return a.state.currentTime === currentTime;
    688    })
    689  );
    690 };
    691 
    692 /**
    693 * Wait until animations' play state will be changed to given state.
    694 *
    695 * @param {Array} animationInspector
    696 * @param {string} state
    697 */
    698 const waitUntilAnimationsPlayState = async function (
    699  animationInspector,
    700  state
    701 ) {
    702  info(`Wait until play state will be change to ${state}`);
    703  await waitUntil(() =>
    704    animationInspector.state.animations.every(a => a.state.playState === state)
    705  );
    706 };
    707 
    708 /**
    709 * Return count of graph that animation inspector is displaying.
    710 *
    711 * @param {AnimationInspector} animationInspector
    712 * @param {DOMElement} panel
    713 * @return {number} count
    714 */
    715 const getDisplayedGraphCount = (animationInspector, panel) => {
    716  const animationLength = animationInspector.state.animations.length;
    717  if (animationLength === 0) {
    718    return 0;
    719  }
    720 
    721  const inspectionPanelEl = panel.querySelector(".progress-inspection-panel");
    722  const itemEl = panel.querySelector(".animation-item");
    723  const listEl = panel.querySelector(".animation-list");
    724  const itemHeight = itemEl.offsetHeight;
    725  // This calculation should be same as AnimationListContainer.updateDisplayableRange.
    726  const count = Math.floor(listEl.offsetHeight / itemHeight) + 1;
    727  const index = Math.floor(inspectionPanelEl.scrollTop / itemHeight);
    728 
    729  return animationLength > index + count ? count : animationLength - index;
    730 };
    731 
    732 /**
    733 * Check whether the animations are pausing.
    734 *
    735 * @param {AnimationInspector} animationInspector
    736 */
    737 function assertAnimationsPausing(animationInspector) {
    738  assertAnimationsPausingOrRunning(animationInspector, true);
    739 }
    740 
    741 /**
    742 * Check whether the animations are pausing/running.
    743 *
    744 * @param {AnimationInspector} animationInspector
    745 * @param {boolean} shouldPause
    746 */
    747 function assertAnimationsPausingOrRunning(animationInspector, shouldPause) {
    748  const hasRunningAnimation = animationInspector.state.animations.some(
    749    ({ state }) => state.playState === "running"
    750  );
    751 
    752  if (shouldPause) {
    753    is(hasRunningAnimation, false, "All animations should be paused");
    754  } else {
    755    is(hasRunningAnimation, true, "Animations should be running at least one");
    756  }
    757 }
    758 
    759 /**
    760 * Check whether the animations are running.
    761 *
    762 * @param {AnimationInspector} animationInspector
    763 */
    764 function assertAnimationsRunning(animationInspector) {
    765  assertAnimationsPausingOrRunning(animationInspector, false);
    766 }
    767 
    768 /**
    769 * Check the <stop> element in the given linearGradientEl for the correct offset
    770 * and color attributes.
    771 *
    772 * @param {Element} linearGradientEl
    773          <linearGradient> element which has <stop> element.
    774 * @param {number} offset
    775 *        float which represents the "offset" attribute of <stop>.
    776 * @param {string} expectedColor
    777 *        e.g. rgb(0, 0, 255)
    778 */
    779 function assertLinearGradient(linearGradientEl, offset, expectedColor) {
    780  const stopEl = findStopElement(linearGradientEl, offset);
    781  ok(stopEl, `stop element at offset ${offset} should exist`);
    782  is(
    783    stopEl.getAttribute("stop-color"),
    784    expectedColor,
    785    `stop-color of stop element at offset ${offset} should be ${expectedColor}`
    786  );
    787 }
    788 
    789 /**
    790 * SummaryGraph is constructed by <path> element.
    791 * This function checks the vertex of path segments.
    792 *
    793 * @param {Element} pathEl
    794 *        <path> element.
    795 * @param {boolean} hasClosePath
    796 *        Set true if the path shoud be closing.
    797 * @param {object} expectedValues
    798 *        JSON object format. We can test the vertex and color.
    799 *        e.g.
    800 *        [
    801 *          { x: 0, y: 0 },
    802 *          { x: 0, y: 1 },
    803 *        ]
    804 */
    805 function assertPathSegments(pathEl, hasClosePath, expectedValues) {
    806  const pathData = pathEl.getPathData({ normalize: true });
    807  ok(
    808    expectedValues.every(value => isPassingThrough(pathData, value.x, value.y)),
    809    "unexpected path segment vertices"
    810  );
    811 
    812  if (hasClosePath) {
    813    ok(pathData.length, "Close path expected but path is empty");
    814    const closePathSeg = pathData.at(-1);
    815    Assert.strictEqual(
    816      closePathSeg.type.toLowerCase(),
    817      "z",
    818      "Close path not found"
    819    );
    820  }
    821 }
    822 
    823 /**
    824 * Check whether the given vertex is passing through on the path.
    825 *
    826 * @param {pathData} pathData - pathData of <path> element.
    827 * @param {float} x - x of vertex.
    828 * @param {float} y - y of vertex.
    829 * @return {boolean} true: passing through, false: no on the path.
    830 */
    831 function isPassingThrough(pathData, x, y) {
    832  let previousX, previousY;
    833  for (let i = 0; i < pathData.length; i++) {
    834    const pathSeg = pathData[i];
    835    if (!pathSeg.values.length) {
    836      continue;
    837    }
    838    let currentX, currentY;
    839    switch (pathSeg.type) {
    840      case "M":
    841      case "L":
    842        currentX = pathSeg.values[0];
    843        currentY = pathSeg.values[1];
    844        break;
    845      case "C":
    846        currentX = pathSeg.values[4];
    847        currentY = pathSeg.values[5];
    848        break;
    849    }
    850    currentX = parseFloat(currentX.toFixed(3));
    851    currentY = parseFloat(currentY.toFixed(3));
    852    if (currentX === x && currentY === y) {
    853      return true;
    854    }
    855    if (previousX === undefined && previousY === undefined) {
    856      previousX = currentX;
    857      previousY = currentY;
    858    }
    859    if (
    860      previousX <= x &&
    861      x <= currentX &&
    862      Math.min(previousY, currentY) <= y &&
    863      y <= Math.max(previousY, currentY)
    864    ) {
    865      return true;
    866    }
    867    previousX = currentX;
    868    previousY = currentY;
    869  }
    870  return false;
    871 }
    872 
    873 /**
    874 * Return animation item element by the index.
    875 *
    876 * @param {DOMElement} panel
    877 *        #animation-container element.
    878 * @param {number} index
    879 * @return {DOMElement}
    880 *        Animation item element.
    881 */
    882 async function findAnimationItemByIndex(panel, index) {
    883  const itemEls = [...panel.querySelectorAll(".animation-item")];
    884  const itemEl = itemEls[index];
    885  if (!itemEl) {
    886    return null;
    887  }
    888 
    889  itemEl.scrollIntoView(false);
    890 
    891  await waitUntil(
    892    () =>
    893      itemEl.querySelector(".animation-target .attrName") &&
    894      itemEl.querySelector(".animation-computed-timing-path")
    895  );
    896 
    897  return itemEl;
    898 }
    899 
    900 /**
    901 * Return animation item element by target node selector.
    902 * This function compares betweem animation-target textContent and given selector.
    903 * Then returns matched first item.
    904 *
    905 * @param {DOMElement} panel
    906 *        #animation-container element.
    907 * @param {string} selector
    908 *        Selector of tested element.
    909 * @return {DOMElement}
    910 *        Animation item element.
    911 */
    912 async function findAnimationItemByTargetSelector(panel, selector) {
    913  for (const itemEl of panel.querySelectorAll(".animation-item")) {
    914    itemEl.scrollIntoView(false);
    915 
    916    await waitUntil(
    917      () =>
    918        itemEl.querySelector(".animation-target .attrName") &&
    919        itemEl.querySelector(".animation-computed-timing-path")
    920    );
    921 
    922    const attrNameEl = itemEl.querySelector(".animation-target .attrName");
    923    const regexp = new RegExp(`\\${selector}(\\.|$)`, "gi");
    924    if (regexp.exec(attrNameEl.textContent)) {
    925      return itemEl;
    926    }
    927  }
    928 
    929  return null;
    930 }
    931 
    932 /**
    933 * Return animation item element by given target element text of animation.
    934 *
    935 * @param {DOMElement} panel
    936 *        #animation-container element.
    937 * @param {string} targetText
    938 *        text displayed to represent the animation target in the panel.
    939 * @return {DOMElement|null}
    940 *        Animation item element.
    941 */
    942 async function findAnimationItemByTargetText(panel, targetText) {
    943  for (const itemEl of panel.querySelectorAll(".animation-item")) {
    944    itemEl.scrollIntoView(false);
    945 
    946    await waitUntil(
    947      () =>
    948        itemEl.querySelector(".animation-target .attrName") &&
    949        itemEl.querySelector(".animation-computed-timing-path")
    950    );
    951 
    952    const attrNameEl = itemEl.querySelector(".animation-target .attrName");
    953    if (attrNameEl.textContent.trim() === targetText) {
    954      return itemEl;
    955    }
    956  }
    957 
    958  return null;
    959 }
    960 
    961 /**
    962 * Find the <stop> element which has the given offset in the given linearGradientEl.
    963 *
    964 * @param {Element} linearGradientEl
    965 *        <linearGradient> element which has <stop> element.
    966 * @param {number} offset
    967 *        Float which represents the "offset" attribute of <stop>.
    968 * @return {Element}
    969 *         If can't find suitable element, returns null.
    970 */
    971 function findStopElement(linearGradientEl, offset) {
    972  for (const stopEl of linearGradientEl.querySelectorAll("stop")) {
    973    if (offset <= parseFloat(stopEl.getAttribute("offset"))) {
    974      return stopEl;
    975    }
    976  }
    977 
    978  return null;
    979 }
    980 
    981 /**
    982 * Do test for keyframes-graph_computed-value-path-1/2.
    983 *
    984 * @param {Array} testData
    985 */
    986 async function testKeyframesGraphComputedValuePath(testData) {
    987  await addTab(URL_ROOT + "doc_multi_keyframes.html");
    988  await removeAnimatedElementsExcept(testData.map(t => `.${t.targetClass}`));
    989  const { animationInspector, panel } = await openAnimationInspector();
    990 
    991  for (const { properties, targetClass } of testData) {
    992    info(`Checking keyframes graph for ${targetClass}`);
    993    const onDetailRendered = animationInspector.once(
    994      "animation-keyframes-rendered"
    995    );
    996    await clickOnAnimationByTargetSelector(
    997      animationInspector,
    998      panel,
    999      `.${targetClass}`
   1000    );
   1001    await onDetailRendered;
   1002 
   1003    for (const property of properties) {
   1004      const {
   1005        name,
   1006        computedValuePathClass,
   1007        expectedPathSegments,
   1008        expectedStopColors,
   1009      } = property;
   1010 
   1011      const testTarget = `${name} in ${targetClass}`;
   1012      info(`Checking keyframes graph for ${testTarget}`);
   1013      info(`Checking keyframes graph path existence for ${testTarget}`);
   1014      const keyframesGraphPathEl = panel.querySelector(`.${name}`);
   1015      ok(
   1016        keyframesGraphPathEl,
   1017        `The keyframes graph path element of ${testTarget} should be existence`
   1018      );
   1019 
   1020      info(`Checking computed value path existence for ${testTarget}`);
   1021      const computedValuePathEl = keyframesGraphPathEl.querySelector(
   1022        `.${computedValuePathClass}`
   1023      );
   1024      ok(
   1025        computedValuePathEl,
   1026        `The computed value path element of ${testTarget} should be existence`
   1027      );
   1028 
   1029      info(`Checking path segments for ${testTarget}`);
   1030      const pathEl = computedValuePathEl.querySelector("path");
   1031      ok(pathEl, `The <path> element of ${testTarget} should be existence`);
   1032      assertPathSegments(pathEl, true, expectedPathSegments);
   1033 
   1034      if (!expectedStopColors) {
   1035        continue;
   1036      }
   1037 
   1038      info(`Checking linearGradient for ${testTarget}`);
   1039      const linearGradientEl =
   1040        computedValuePathEl.querySelector("linearGradient");
   1041      ok(
   1042        linearGradientEl,
   1043        `The <linearGradientEl> element of ${testTarget} should be existence`
   1044      );
   1045 
   1046      for (const expectedStopColor of expectedStopColors) {
   1047        const { offset, color } = expectedStopColor;
   1048        assertLinearGradient(linearGradientEl, offset, color);
   1049      }
   1050    }
   1051  }
   1052 }
   1053 
   1054 /**
   1055 * Check the adjusted current time and created time from specified two animations.
   1056 *
   1057 * @param {AnimationPlayerFront.state} animation1
   1058 * @param {AnimationPlayerFront.state} animation2
   1059 */
   1060 function checkAdjustingTheTime(animation1, animation2) {
   1061  const adjustedCurrentTimeDiff =
   1062    animation2.currentTime / animation2.playbackRate -
   1063    animation1.currentTime / animation1.playbackRate;
   1064  const createdTimeDiff = animation1.createdTime - animation2.createdTime;
   1065  Assert.less(
   1066    Math.abs(adjustedCurrentTimeDiff - createdTimeDiff),
   1067    0.1,
   1068    "Adjusted time is correct"
   1069  );
   1070 }