tor-browser

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

helper.sub.js (8117B)


      1 // Helpers called on the main test HTMLs.
      2 // Functions in `RemoteContext.execute_script()`'s 1st argument are evaluated
      3 // on the executors (`executor.html`), and helpers available on the executors
      4 // are defined in `executor.html`.
      5 
      6 const originSameOrigin =
      7  location.protocol === 'http:' ?
      8  'http://{{host}}:{{ports[http][0]}}' :
      9  'https://{{host}}:{{ports[https][0]}}';
     10 const originSameSite =
     11  location.protocol === 'http:' ?
     12  'http://{{host}}:{{ports[http][1]}}' :
     13  'https://{{host}}:{{ports[https][1]}}';
     14 const originCrossSite =
     15  location.protocol === 'http:' ?
     16  'http://{{hosts[alt][www]}}:{{ports[http][0]}}' :
     17  'https://{{hosts[alt][www]}}:{{ports[https][0]}}';
     18 
     19 const executorPath =
     20  '/html/browsers/browsing-the-web/back-forward-cache/resources/executor.html?uuid=';
     21 
     22 // Asserts that the executor `target` is (or isn't, respectively)
     23 // restored from BFCache. These should be used in the following fashion:
     24 // 1. Call prepareNavigation() on the executor `target`.
     25 // 2. Navigate the executor to another page.
     26 // 3. Navigate back to the executor `target`.
     27 // 4. Call assert_bfcached() or assert_not_bfcached() on the main test HTML.
     28 async function assert_bfcached(target) {
     29  const status = await getBFCachedStatus(target);
     30  assert_implements_optional(status === 'BFCached',
     31      "Could have been BFCached but actually wasn't");
     32 }
     33 
     34 async function assert_not_bfcached(target) {
     35  const status = await getBFCachedStatus(target);
     36  assert_implements(status !== 'BFCached',
     37      'Should not be BFCached but actually was');
     38 }
     39 
     40 async function getBFCachedStatus(target) {
     41  const [loadCount, isPageshowFired, isPageshowPersisted] =
     42    await target.execute_script(() => [
     43        window.loadCount, window.isPageshowFired, window.isPageshowPersisted]);
     44 
     45  if (loadCount === 1 && isPageshowFired === true &&
     46      isPageshowPersisted === true) {
     47    return 'BFCached';
     48  } else if (loadCount === 2 && isPageshowFired === false) {
     49    return 'Not BFCached';
     50  } else {
     51    // This can occur for example when this is called before first navigating
     52    // away (loadCount = 1, isPageshowFired = false), e.g. when
     53    // 1. sending a script for navigation and then
     54    // 2. calling getBFCachedStatus() without waiting for the completion of
     55    //    the script on the `target` page.
     56    assert_unreached(
     57      `Got unexpected BFCache status: loadCount = ${loadCount}, ` +
     58      `isPageshowFired = ${isPageshowFired}, ` +
     59      `isPageshowPersisted = ${isPageshowPersisted}`);
     60  }
     61 }
     62 
     63 // Always call `await remoteContext.execute_script(waitForPageShow);` after
     64 // triggering to navigation to the page, to wait for pageshow event on the
     65 // remote context.
     66 const waitForPageShow = () => window.pageShowPromise;
     67 
     68 // Run a test that navigates A->B->A:
     69 // 1. Page A is opened by `params.openFunc(url)`.
     70 // 2. `params.funcBeforeNavigation(params.argsBeforeNavigation)` is executed
     71 //    on page A.
     72 // 3. The window is navigated to page B on `params.targetOrigin`.
     73 // 4. The window is back navigated to page A (expecting BFCached).
     74 //
     75 // Events `params.events` (an array of strings) are observed on page A and
     76 // `params.expectedEvents` (an array of strings) is expected to be recorded.
     77 // See `event-recorder.js` for event recording.
     78 //
     79 // Parameters can be omitted. See `defaultParams` below for default.
     80 function runEventTest(params, description) {
     81  const defaultParams = {
     82    openFunc(url) {
     83      window.open(
     84        `${url}&events=${this.events.join(',')}`,
     85        '_blank',
     86        'noopener'
     87      )
     88    },
     89    events: ['pagehide', 'pageshow', 'load'],
     90    expectedEvents: [
     91      'window.load',
     92      'window.pageshow',
     93      'window.pagehide.persisted',
     94      'window.pageshow.persisted'
     95    ],
     96    async funcAfterAssertion(pageA) {
     97      assert_array_equals(
     98        await pageA.execute_script(() => getRecordedEvents()),
     99        this.expectedEvents);
    100    }
    101  }
    102  // Apply defaults.
    103  params = { ...defaultParams, ...params };
    104 
    105  runBfcacheTest(params, description);
    106 }
    107 
    108 async function navigateAndThenBack(pageA, pageB, urlB, scripts = [],
    109                                   funcBeforeBackNavigation,
    110                                   argsBeforeBackNavigation) {
    111  await pageA.execute_script(
    112    (url) => {
    113      prepareNavigation(() => {
    114        location.href = url;
    115      });
    116    },
    117    [urlB]
    118  );
    119 
    120  await pageB.execute_script(waitForPageShow);
    121 
    122  for (const src of scripts) {
    123    await pageB.execute_script((src) => {
    124      const script = document.createElement("script");
    125      script.src = src;
    126      document.head.append(script);
    127      return new Promise(resolve => script.onload = resolve);
    128    }, [src]);
    129  }
    130 
    131  if (funcBeforeBackNavigation) {
    132    await pageB.execute_script(funcBeforeBackNavigation,
    133                               argsBeforeBackNavigation);
    134  }
    135  await pageB.execute_script(
    136    () => {
    137      prepareNavigation(() => { history.back(); });
    138    }
    139  );
    140 
    141  await pageA.execute_script(waitForPageShow);
    142 }
    143 
    144 function runBfcacheTest(params, description) {
    145  const defaultParams = {
    146    openFunc: url => window.open(url, '_blank', 'noopener'),
    147    scripts: [],
    148    funcBeforeNavigation: () => {},
    149    argsBeforeNavigation: [],
    150    targetOrigin: originCrossSite,
    151    funcBeforeBackNavigation: () => {},
    152    argsBeforeBackNavigation: [],
    153    shouldBeCached: true,
    154    funcAfterAssertion: () => {},
    155  }
    156  // Apply defaults.
    157  params = {...defaultParams, ...params };
    158 
    159  promise_test(async t => {
    160    const pageA = new RemoteContext(token());
    161    const pageB = new RemoteContext(token());
    162 
    163    const urlA = executorPath + pageA.context_id;
    164    const urlB = params.targetOrigin + executorPath + pageB.context_id;
    165 
    166    // So that tests can refer to these URLs for assertions if necessary.
    167    pageA.url = originSameOrigin + urlA;
    168    pageB.url = urlB;
    169 
    170    params.openFunc(urlA);
    171 
    172    await pageA.execute_script(waitForPageShow);
    173 
    174    for (const src of params.scripts) {
    175      await pageA.execute_script((src) => {
    176        const script = document.createElement("script");
    177        script.src = src;
    178        document.head.append(script);
    179        return new Promise(resolve => script.onload = resolve);
    180      }, [src]);
    181    }
    182 
    183    await pageA.execute_script(params.funcBeforeNavigation,
    184                               params.argsBeforeNavigation);
    185    await navigateAndThenBack(pageA, pageB, urlB, params.scripts,
    186                              params.funcBeforeBackNavigation,
    187                              params.argsBeforeBackNavigation);
    188 
    189    if (params.shouldBeCached) {
    190      await assert_bfcached(pageA);
    191    } else {
    192      await assert_not_bfcached(pageA);
    193    }
    194 
    195    if (params.funcAfterAssertion) {
    196      await params.funcAfterAssertion(pageA, pageB, t);
    197    }
    198  }, description);
    199 }
    200 
    201 // Call clients.claim() on the service worker
    202 async function claim(t, worker) {
    203  const channel = new MessageChannel();
    204  const sawMessage = new Promise(function(resolve) {
    205    channel.port1.onmessage = t.step_func(function(e) {
    206      assert_equals(e.data, 'PASS', 'Worker call to claim() should fulfill.');
    207      resolve();
    208    });
    209  });
    210  worker.postMessage({type: "claim", port: channel.port2}, [channel.port2]);
    211  await sawMessage;
    212 }
    213 
    214 // Assigns the current client to a local variable on the service worker.
    215 async function storeClients(t, worker) {
    216  const channel = new MessageChannel();
    217  const sawMessage = new Promise(function(resolve) {
    218    channel.port1.onmessage = t.step_func(function(e) {
    219      assert_equals(e.data, 'PASS', 'storeClients');
    220      resolve();
    221    });
    222  });
    223  worker.postMessage({type: "storeClients", port: channel.port2}, [channel.port2]);
    224  await sawMessage;
    225 }
    226 
    227 // Call storedClients.postMessage("") on the service worker
    228 async function postMessageToStoredClients(t, worker) {
    229  const channel = new MessageChannel();
    230  const sawMessage = new Promise(function(resolve) {
    231    channel.port1.onmessage = t.step_func(function(e) {
    232      assert_equals(e.data, 'PASS', 'postMessageToStoredClients');
    233      resolve();
    234    });
    235  });
    236  worker.postMessage({type: "postMessageToStoredClients",
    237                      port: channel.port2}, [channel.port2]);
    238  await sawMessage;
    239 }