tor-browser

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

clients-matchall-order.https.html (12103B)


      1 <!DOCTYPE html>
      2 <title>Service Worker: Clients.matchAll ordering</title>
      3 <meta name=timeout content=long>
      4 <script src="/resources/testharness.js"></script>
      5 <script src="/resources/testharnessreport.js"></script>
      6 <script src="resources/test-helpers.sub.js"></script>
      7 <script>
      8 
      9 // Utility function for URLs this test will open.
     10 function makeURL(name, num, type) {
     11  let u = new URL('resources/empty.html', location);
     12  u.searchParams.set('name', name);
     13  if (num !== undefined) {
     14    u.searchParams.set('q', num);
     15  }
     16  if (type === 'nested') {
     17    u.searchParams.set('nested', true);
     18  }
     19  return u.href;
     20 }
     21 
     22 // Non-test URLs that will be open during each test.  The harness URLs
     23 // are from the WPT harness.  The "extra" URL is a final window opened
     24 // by the test.
     25 const EXTRA_URL = makeURL('extra');
     26 const TEST_HARNESS_URL = location.href;
     27 const TOP_HARNESS_URL = new URL('/testharness_runner.html', location).href;
     28 
     29 // Utility function to open an iframe in the target parent window.  We
     30 // can't just use with_iframe() because it does not support a configurable
     31 // parent window.
     32 function openFrame(parentWindow, url) {
     33  return new Promise(resolve => {
     34    let frame = parentWindow.document.createElement('iframe');
     35    frame.src = url;
     36    parentWindow.document.body.appendChild(frame);
     37 
     38    frame.contentWindow.addEventListener('load', evt => {
     39      resolve(frame);
     40    }, { once: true });
     41  });
     42 }
     43 
     44 // Utility function to open a window and wait for it to load.  The
     45 // window may optionally have a nested iframe as well.  Returns
     46 // a result like `{ top: <frame ref> nested: <nested frame ref if present> }`.
     47 function openFrameConfig(opts) {
     48  let url = new URL(opts.url, location.href);
     49  return openFrame(window, url.href).then(top => {
     50    if (!opts.withNested) {
     51      return { top: top };
     52    }
     53 
     54    url.searchParams.set('nested', true);
     55    return openFrame(top.contentWindow, url.href).then(nested => {
     56      return { top: top, nested: nested };
     57    });
     58  });
     59 }
     60 
     61 // Utility function that takes a list of configurations and opens the
     62 // corresponding windows in sequence.  An array of results is returned.
     63 function openFrameConfigList(optList) {
     64  let resultList = [];
     65  function openNextWindow(optList, nextWindow) {
     66    if (nextWindow >= optList.length) {
     67      return resultList;
     68    }
     69    return openFrameConfig(optList[nextWindow]).then(result => {
     70      resultList.push(result);
     71      return openNextWindow(optList, nextWindow + 1);
     72    });
     73  }
     74  return openNextWindow(optList, 0);
     75 }
     76 
     77 // Utility function that focuses the given entry in window result list.
     78 function executeFocus(frameResultList, opts) {
     79  return new Promise(resolve => {
     80    let w = frameResultList[opts.index][opts.type];
     81    let target = w.contentWindow ? w.contentWindow : w;
     82    target.addEventListener('focus', evt => {
     83      resolve();
     84    }, { once: true });
     85    target.focus();
     86  });
     87 }
     88 
     89 // Utility function that performs a list of focus commands in sequence
     90 // based on the window result list.
     91 function executeFocusList(frameResultList, optList) {
     92  function executeNextCommand(frameResultList, optList, nextCommand) {
     93    if (nextCommand >= optList.length) {
     94      return;
     95    }
     96    return executeFocus(frameResultList, optList[nextCommand]).then(_ => {
     97      return executeNextCommand(frameResultList, optList, nextCommand + 1);
     98    });
     99  }
    100  return executeNextCommand(frameResultList, optList, 0);
    101 }
    102 
    103 // Perform a `clients.matchAll()` in the service worker with the given
    104 // options dictionary.
    105 function doMatchAll(worker, options) {
    106  return new Promise(resolve => {
    107    let channel = new MessageChannel();
    108    channel.port1.onmessage = evt => {
    109      resolve(evt.data);
    110    };
    111    worker.postMessage({ port: channel.port2, options: options, disableSort: true },
    112                       [channel.port2]);
    113  });
    114 }
    115 
    116 // Function that performs a single test case.  It takes a configuration object
    117 // describing the windows to open, how to focus them, the matchAll options,
    118 // and the resulting expectations.  See the test cases for examples of how to
    119 // use this.
    120 function matchAllOrderTest(t, opts) {
    121  let script = 'resources/clients-matchall-worker.js';
    122  let worker;
    123  let frameResultList;
    124  let extraWindowResult;
    125  return service_worker_unregister_and_register(t, script, opts.scope).then(swr => {
    126    t.add_cleanup(() => service_worker_unregister(t, opts.scope));
    127 
    128    worker = swr.installing;
    129    return wait_for_state(t, worker, 'activated');
    130  }).then(_ => {
    131    return openFrameConfigList(opts.frameConfigList);
    132  }).then(results => {
    133    frameResultList = results;
    134    return openFrameConfig({ url: EXTRA_URL });
    135  }).then(result => {
    136    extraWindowResult = result;
    137    return executeFocusList(frameResultList, opts.focusConfigList);
    138  }).then(_ => {
    139    return doMatchAll(worker, opts.matchAllOptions);
    140  }).then(data => {
    141    assert_equals(data.length, opts.expected.length);
    142    for (let i = 0; i < data.length; ++i) {
    143      assert_equals(data[i][2], opts.expected[i], 'expected URL index ' + i);
    144    }
    145  }).then(_ => {
    146    frameResultList.forEach(result => result.top.remove());
    147    extraWindowResult.top.remove();
    148  }).catch(e => {
    149    if (frameResultList) {
    150      frameResultList.forEach(result => result.top.remove());
    151    }
    152    if (extraWindowResult) {
    153      extraWindowResult.top.remove();
    154    }
    155    throw(e);
    156  });
    157 }
    158 
    159 // ----------
    160 // Test cases
    161 // ----------
    162 
    163 promise_test(t => {
    164  let name = 'no-focus-controlled-windows';
    165  let opts = {
    166    scope: makeURL(name),
    167 
    168    frameConfigList: [
    169      { url: makeURL(name, 0), withNested: false },
    170      { url: makeURL(name, 1), withNested: false },
    171      { url: makeURL(name, 2), withNested: false },
    172    ],
    173 
    174    focusConfigList: [
    175      // no focus commands
    176    ],
    177 
    178    matchAllOptions: {
    179      includeUncontrolled: false
    180    },
    181 
    182    expected: [
    183      makeURL(name, 0),
    184      makeURL(name, 1),
    185      makeURL(name, 2),
    186    ],
    187  };
    188 
    189  return matchAllOrderTest(t, opts);
    190 }, 'Clients.matchAll() returns non-focused controlled windows in creation order.');
    191 
    192 promise_test(t => {
    193  let name = 'focus-controlled-windows-1';
    194  let opts = {
    195    scope: makeURL(name),
    196 
    197    frameConfigList: [
    198      { url: makeURL(name, 0), withNested: false },
    199      { url: makeURL(name, 1), withNested: false },
    200      { url: makeURL(name, 2), withNested: false },
    201    ],
    202 
    203    focusConfigList: [
    204      { index: 0, type: 'top' },
    205      { index: 1, type: 'top' },
    206      { index: 2, type: 'top' },
    207    ],
    208 
    209    matchAllOptions: {
    210      includeUncontrolled: false
    211    },
    212 
    213    expected: [
    214      makeURL(name, 2),
    215      makeURL(name, 1),
    216      makeURL(name, 0),
    217    ],
    218  };
    219 
    220  return matchAllOrderTest(t, opts);
    221 }, 'Clients.matchAll() returns controlled windows in focus order.  Case 1.');
    222 
    223 promise_test(t => {
    224  let name = 'focus-controlled-windows-2';
    225  let opts = {
    226    scope: makeURL(name),
    227 
    228    frameConfigList: [
    229      { url: makeURL(name, 0), withNested: false },
    230      { url: makeURL(name, 1), withNested: false },
    231      { url: makeURL(name, 2), withNested: false },
    232    ],
    233 
    234    focusConfigList: [
    235      { index: 2, type: 'top' },
    236      { index: 1, type: 'top' },
    237      { index: 0, type: 'top' },
    238    ],
    239 
    240    matchAllOptions: {
    241      includeUncontrolled: false
    242    },
    243 
    244    expected: [
    245      makeURL(name, 0),
    246      makeURL(name, 1),
    247      makeURL(name, 2),
    248    ],
    249  };
    250 
    251  return matchAllOrderTest(t, opts);
    252 }, 'Clients.matchAll() returns controlled windows in focus order.  Case 2.');
    253 
    254 promise_test(t => {
    255  let name = 'no-focus-uncontrolled-windows';
    256  let opts = {
    257    scope: makeURL(name + '-outofscope'),
    258 
    259    frameConfigList: [
    260      { url: makeURL(name, 0), withNested: false },
    261      { url: makeURL(name, 1), withNested: false },
    262      { url: makeURL(name, 2), withNested: false },
    263    ],
    264 
    265    focusConfigList: [
    266      // no focus commands
    267    ],
    268 
    269    matchAllOptions: {
    270      includeUncontrolled: true
    271    },
    272 
    273    expected: [
    274      // The harness windows have been focused, so appear first
    275      TEST_HARNESS_URL,
    276      TOP_HARNESS_URL,
    277 
    278      // Test frames have not been focused, so appear in creation order
    279      makeURL(name, 0),
    280      makeURL(name, 1),
    281      makeURL(name, 2),
    282      EXTRA_URL,
    283    ],
    284  };
    285 
    286  return matchAllOrderTest(t, opts);
    287 }, 'Clients.matchAll() returns non-focused uncontrolled windows in creation order.');
    288 
    289 promise_test(t => {
    290  let name = 'focus-uncontrolled-windows-1';
    291  let opts = {
    292    scope: makeURL(name + '-outofscope'),
    293 
    294    frameConfigList: [
    295      { url: makeURL(name, 0), withNested: false },
    296      { url: makeURL(name, 1), withNested: false },
    297      { url: makeURL(name, 2), withNested: false },
    298    ],
    299 
    300    focusConfigList: [
    301      { index: 0, type: 'top' },
    302      { index: 1, type: 'top' },
    303      { index: 2, type: 'top' },
    304    ],
    305 
    306    matchAllOptions: {
    307      includeUncontrolled: true
    308    },
    309 
    310    expected: [
    311      // The test harness window is a parent of all test frames.  It will
    312      // always have the same focus time or later as its frames.  So it
    313      // appears first.
    314      TEST_HARNESS_URL,
    315 
    316      makeURL(name, 2),
    317      makeURL(name, 1),
    318      makeURL(name, 0),
    319 
    320      // The overall harness has been focused
    321      TOP_HARNESS_URL,
    322 
    323      // The extra frame was never focused
    324      EXTRA_URL,
    325    ],
    326  };
    327 
    328  return matchAllOrderTest(t, opts);
    329 }, 'Clients.matchAll() returns uncontrolled windows in focus order.  Case 1.');
    330 
    331 promise_test(t => {
    332  let name = 'focus-uncontrolled-windows-2';
    333  let opts = {
    334    scope: makeURL(name + '-outofscope'),
    335 
    336    frameConfigList: [
    337      { url: makeURL(name, 0), withNested: false },
    338      { url: makeURL(name, 1), withNested: false },
    339      { url: makeURL(name, 2), withNested: false },
    340    ],
    341 
    342    focusConfigList: [
    343      { index: 2, type: 'top' },
    344      { index: 1, type: 'top' },
    345      { index: 0, type: 'top' },
    346    ],
    347 
    348    matchAllOptions: {
    349      includeUncontrolled: true
    350    },
    351 
    352    expected: [
    353      // The test harness window is a parent of all test frames.  It will
    354      // always have the same focus time or later as its frames.  So it
    355      // appears first.
    356      TEST_HARNESS_URL,
    357 
    358      makeURL(name, 0),
    359      makeURL(name, 1),
    360      makeURL(name, 2),
    361 
    362      // The overall harness has been focused
    363      TOP_HARNESS_URL,
    364 
    365      // The extra frame was never focused
    366      EXTRA_URL,
    367    ],
    368  };
    369 
    370  return matchAllOrderTest(t, opts);
    371 }, 'Clients.matchAll() returns uncontrolled windows in focus order.  Case 2.');
    372 
    373 promise_test(t => {
    374  let name = 'focus-controlled-nested-windows';
    375  let opts = {
    376    scope: makeURL(name),
    377 
    378    frameConfigList: [
    379      { url: makeURL(name, 0), withNested: true },
    380      { url: makeURL(name, 1), withNested: true },
    381      { url: makeURL(name, 2), withNested: true },
    382    ],
    383 
    384    focusConfigList: [
    385      { index: 0, type: 'top' },
    386 
    387      // Note, some browsers don't let programmatic focus of a frame unless
    388      // an ancestor window is already focused.  So focus the window and
    389      // then the frame.
    390      { index: 1, type: 'top' },
    391      { index: 1, type: 'nested' },
    392 
    393      { index: 2, type: 'top' },
    394    ],
    395 
    396    matchAllOptions: {
    397      includeUncontrolled: false
    398    },
    399 
    400    expected: [
    401      // Focus order for window 2, but not its frame.  We only focused
    402      // the window.
    403      makeURL(name, 2),
    404 
    405      // Window 1 is next via focus order, but the window is always
    406      // shown first here.  The window gets its last focus time updated
    407      // when the frame is focused.  Since the times match between the
    408      // two it falls back to creation order.  The window was created
    409      // before the frame.  This behavior is being discussed in:
    410      // https://github.com/w3c/ServiceWorker/issues/1080
    411      makeURL(name, 1),
    412      makeURL(name, 1, 'nested'),
    413 
    414      // Focus order for window 0, but not its frame.  We only focused
    415      // the window.
    416      makeURL(name, 0),
    417 
    418      // Creation order of the frames since they are not focused by
    419      // default when they are created.
    420      makeURL(name, 0, 'nested'),
    421      makeURL(name, 2, 'nested'),
    422    ],
    423  };
    424 
    425  return matchAllOrderTest(t, opts);
    426 }, 'Clients.matchAll() returns controlled windows and frames in focus order.');
    427 </script>