tor-browser

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

focus-utils.js (6121B)


      1 'use strict';
      2 
      3 function waitForRender() {
      4  return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
      5 }
      6 
      7 async function navigateFocusForward() {
      8  await waitForRender();
      9  const kTab = '\uE004';
     10  await new test_driver.send_keys(document.body, kTab);
     11  await waitForRender();
     12 }
     13 
     14 async function navigateFocusBackward() {
     15  await waitForRender();
     16  const kShift = '\uE008';
     17  const kTab = '\uE004';
     18  await new test_driver.Actions()
     19    .keyDown(kShift)
     20    .keyDown(kTab)
     21    .keyUp(kTab)
     22    .keyUp(kShift)
     23    .send();
     24  await waitForRender();
     25 }
     26 
     27 // If shadow root is open, can find element using element path
     28 // If shadow root is open, can find the shadowRoot from the element
     29 
     30 function innermostActiveElement(element) {
     31  element = element || document.activeElement;
     32  if (isIFrameElement(element)) {
     33    if (element.contentDocument.activeElement)
     34      return innermostActiveElement(element.contentDocument.activeElement);
     35    return element;
     36  }
     37  if (isShadowHost(element)) {
     38    let shadowRoot = element.shadowRoot;
     39    if (shadowRoot) {
     40      if (shadowRoot.activeElement)
     41        return innermostActiveElement(shadowRoot.activeElement);
     42    }
     43  }
     44  return element;
     45 }
     46 
     47 function isInnermostActiveElement(path) {
     48  const element = getNodeInComposedTree(path);
     49  if (!element)
     50    return false;
     51  return element === innermostActiveElement();
     52 }
     53 
     54 async function shouldNavigateFocus(fromElement, direction) {
     55  if (!fromElement)
     56    return false;
     57 
     58  fromElement.focus();
     59  if (fromElement !== innermostActiveElement())
     60    return false;
     61 
     62  if (direction == 'forward')
     63    await navigateFocusForward();
     64  else
     65    await navigateFocusBackward();
     66 
     67  return true;
     68 }
     69 
     70 async function assert_focus_navigation_element(fromPath, toPath, direction) {
     71  const fromElement = getNodeInComposedTree(fromPath);
     72  const result = await shouldNavigateFocus(fromElement, direction);
     73  assert_true(result, 'Failed to focus ' + fromPath);
     74 
     75  const message =
     76    `Focus should move ${direction} from ${fromPath} to ${toPath}`;
     77  const toElement = getNodeInComposedTree(toPath);
     78  assert_equals(innermostActiveElement(), toElement, message);
     79 }
     80 
     81 async function assert_focus_navigation_elements(elements, direction) {
     82  assert_true(
     83    elements.length >= 2,
     84    'length of elements should be greater than or equal to 2.');
     85  for (var i = 0; i + 1 < elements.length; ++i)
     86    await assert_focus_navigation_element(elements[i], elements[i + 1], direction);
     87 
     88 }
     89 
     90 async function assert_focus_navigation_forward(elements) {
     91  return assert_focus_navigation_elements(elements, 'forward');
     92 }
     93 
     94 async function assert_focus_navigation_backward(elements) {
     95  return assert_focus_navigation_elements(elements, 'backward');
     96 }
     97 
     98 async function assert_focus_navigation_bidirectional(elements) {
     99  await assert_focus_navigation_forward(elements);
    100  elements.reverse();
    101  await assert_focus_navigation_backward(elements);
    102 }
    103 
    104 
    105 // If shadow root is closed, need to pass shadowRoot and element to find
    106 // innermost active element
    107 
    108 function isShadowHostOfRoot(shadowRoot, node) {
    109  return shadowRoot && shadowRoot.host.isEqualNode(node);
    110 }
    111 
    112 function innermostActiveElementWithShadowRoot(shadowRoot, element) {
    113  element = element || document.activeElement;
    114  if (isIFrameElement(element)) {
    115    if (element.contentDocument.activeElement)
    116      return innermostActiveElementWithShadowRoot(shadowRoot, element.contentDocument.activeElement);
    117    return element;
    118  }
    119  if (isShadowHostOfRoot(shadowRoot, element)) {
    120    if (shadowRoot.activeElement)
    121      return innermostActiveElementWithShadowRoot(shadowRoot, shadowRoot.activeElement);
    122  }
    123  return element;
    124 }
    125 
    126 async function shouldNavigateFocusWithShadowRoot(from, direction) {
    127  const [fromElement, shadowRoot] = from;
    128  if (!fromElement)
    129    return false;
    130 
    131  fromElement.focus();
    132  if (fromElement !== innermostActiveElementWithShadowRoot(shadowRoot))
    133    return false;
    134 
    135  if (direction == 'forward')
    136    await navigateFocusForward();
    137  else
    138    await navigateFocusBackward();
    139 
    140  return true;
    141 }
    142 
    143 async function assert_focus_navigation_element_with_shadow_root(from, to, direction) {
    144  const result = await shouldNavigateFocusWithShadowRoot(from, direction);
    145  const [fromElement] = from;
    146  const [toElement, toShadowRoot] = to;
    147  assert_true(result, 'Failed to focus ' + fromElement.id);
    148  const message =
    149    `Focus should move ${direction} from ${fromElement.id} to ${toElement.id}`;
    150  assert_equals(innermostActiveElementWithShadowRoot(toShadowRoot), toElement, message);
    151 }
    152 
    153 async function assert_focus_navigation_elements_with_shadow_root(elements, direction) {
    154  assert_true(
    155    elements.length >= 2,
    156    'length of elements should be greater than or equal to 2.');
    157  for (var i = 0; i + 1 < elements.length; ++i)
    158    await assert_focus_navigation_element_with_shadow_root(elements[i], elements[i + 1], direction);
    159 }
    160 
    161 async function assert_focus_navigation_forward_with_shadow_root(elements) {
    162  return assert_focus_navigation_elements_with_shadow_root(elements, 'forward');
    163 }
    164 
    165 async function assert_focus_navigation_backward_with_shadow_root(elements) {
    166  return assert_focus_navigation_elements_with_shadow_root(elements, 'backward');
    167 }
    168 
    169 async function assert_focus_navigation_bidirectional_with_shadow_root(elements) {
    170  await assert_focus_navigation_forward_with_shadow_root(elements);
    171  elements.reverse();
    172  await assert_focus_navigation_backward_with_shadow_root(elements);
    173 }
    174 
    175 // This Promise will run each test case that is:
    176 // 1. Wrapped in an element with class name "test-case".
    177 // 2. Has data-expect attribute be an ordered list of elements to focus.
    178 // 3. Has data-description attribute be a string explaining the test.
    179 // e.g <div class="test-case" data-expect="b,a,c"
    180 //          data-description="Focus navigation">
    181 async function runFocusTestCases() {
    182  const testCases = Array.from(document.querySelectorAll('.test-case'));
    183  for (let testCase of testCases) {
    184    promise_test(async () => {
    185      const expected = testCase.dataset.expect.split(',');
    186      await assert_focus_navigation_bidirectional(expected);
    187    }, testCase.dataset.description);
    188  }
    189 }