tor-browser

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

updateWith-call-immediate-manual.https.html (6444B)


      1 <!doctype html>
      2 <meta charset="utf8">
      3 <link rel="help" href="https://www.w3.org/TR/payment-request/#updatewith()-method">
      4 <link rel="help" href="https://github.com/w3c/payment-request/pull/591">
      5 <title>
      6  PaymentRequestUpdateEvent.updateWith() needs to be called immediately
      7 </title>
      8 <script src="/resources/testharness.js"></script>
      9 <script src="/resources/testharnessreport.js"></script>
     10 <script>
     11 setup({ explicit_done: true, explicit_timeout: true });
     12 const applePay = Object.freeze({
     13  supportedMethods: "https://apple.com/apple-pay",
     14  data: {
     15    version: 3,
     16    merchantIdentifier: "merchant.com.example",
     17    countryCode: "US",
     18    merchantCapabilities: ["supports3DS"],
     19    supportedNetworks: ["visa"],
     20  }
     21 });
     22 const validMethod = Object.freeze({ supportedMethods: "basic-card" });
     23 const validMethods = Object.freeze([validMethod, applePay]);
     24 const validAmount = Object.freeze({ currency: "USD", value: "5.00" });
     25 const validTotal = Object.freeze({
     26  label: "label",
     27  amount: validAmount,
     28 });
     29 const validShippingOptionA = Object.freeze({
     30  id: "a-shipping-option",
     31  label: "A shipping option",
     32  amount: validAmount,
     33  selected: true,
     34 });
     35 const validShippingOptionB = Object.freeze({
     36  id: "b-shipping-option",
     37  label: "B shipping option",
     38  amount: validAmount,
     39 });
     40 const validDetails = Object.freeze({
     41  total: validTotal,
     42  shippingOptions: [validShippingOptionA, validShippingOptionB],
     43 });
     44 const validOptions = Object.freeze({
     45  requestShipping: true,
     46 });
     47 
     48 function testImmediateUpdate({ textContent: testName }) {
     49  promise_test(async t => {
     50    const request = new PaymentRequest(
     51      validMethods,
     52      validDetails,
     53      validOptions
     54    );
     55    const eventPromise = new Promise((resolve, reject) => {
     56      request.addEventListener(
     57        "shippingaddresschange",
     58        ev => {
     59          // Forces updateWith() to be run in the next event loop tick so that
     60          // [[waitForUpdate]] is already true when it runs.
     61          t.step_timeout(() => {
     62            try {
     63              ev.updateWith(validDetails);
     64              resolve(); // This is bad.
     65            } catch (err) {
     66              reject(err); // this is good.
     67            }
     68          });
     69        },
     70        { once: true }
     71      );
     72    });
     73    const acceptPromise = request.show();
     74    await promise_rejects_dom(
     75      t,
     76      "InvalidStateError",
     77      eventPromise,
     78      "The event loop already spun, so [[waitForUpdate]] is now true"
     79    );
     80    const response = await acceptPromise;
     81    await response.complete();
     82  }, testName.trim());
     83 }
     84 
     85 function testSubsequentUpdateWithCalls({ textContent: testName }) {
     86  promise_test(async t => {
     87    const request = new PaymentRequest(
     88      validMethods,
     89      validDetails,
     90      validOptions
     91    );
     92    const eventPromise = new Promise((resolve, reject) => {
     93      request.addEventListener("shippingaddresschange", async ev => {
     94        const p = Promise.resolve(validDetails);
     95        ev.updateWith(p);
     96        await p;
     97        try {
     98          ev.updateWith(validDetails);
     99          resolve(); // this is bad, we should never get to here.
    100        } catch (err) {
    101          reject(err); // this is good!
    102        }
    103      });
    104    });
    105    const responsePromise = request.show();
    106    await promise_rejects_dom(
    107      t,
    108      "InvalidStateError",
    109      eventPromise,
    110      "Expected eventPromise to have rejected, because updateWith() was a called twice"
    111    );
    112    const response = await responsePromise;
    113    await response.complete();
    114  }, testName.trim());
    115 }
    116 
    117 function testRecycleEvents({ textContent: testName }) {
    118  promise_test(async t => {
    119    const request = new PaymentRequest(
    120      validMethods,
    121      validDetails,
    122      validOptions
    123    );
    124 
    125    // Register both listeners.
    126    const addressChangedPromise = new Promise(resolve => {
    127      request.addEventListener("shippingaddresschange", resolve, {
    128        once: true,
    129      });
    130    });
    131 
    132    const optionChangedPromise = new Promise(resolve => {
    133      request.addEventListener("shippingoptionchange", resolve, {
    134        once: true,
    135      });
    136    });
    137 
    138    const responsePromise = request.show();
    139 
    140    // Let's wait for the address to change.
    141    const addressChangeEvent = await addressChangedPromise;
    142 
    143    // Sets [[waitingForUpdate]] to true.
    144    addressChangeEvent.updateWith(validDetails);
    145 
    146    // Let's wait for the shippingOption.
    147    const optionChangeEvent = await optionChangedPromise;
    148 
    149    // Here, we try to be sneaky, and reuse the addressChangeEvent to perform the update.
    150    // However, addressChangeEvent [[waitingForUpdate]] is true, so it throws.
    151    assert_throws_dom(
    152      "InvalidStateError",
    153      () => {
    154        addressChangeEvent.updateWith(validDetails);
    155      },
    156      "addressChangeEvent [[waitingForUpdate]] is true, so it must throw"
    157    );
    158 
    159    // But optionChangeEvent is still usable tho, so...
    160    optionChangeEvent.updateWith(validDetails);
    161 
    162    assert_throws_dom(
    163      "InvalidStateError",
    164      () => {
    165        optionChangeEvent.updateWith(validDetails);
    166      },
    167      "optionChangeEvent [[waitingForUpdate]] is true, so it must throw"
    168    );
    169 
    170    const response = await responsePromise;
    171    await response.complete();
    172  }, testName.trim());
    173 }
    174 </script>
    175 <h2>updateWith() method</h2>
    176 <p>
    177  Click on each button in sequence from top to bottom without refreshing the page.
    178  Each button will bring up the Payment Request UI window.
    179 </p>
    180 <p>
    181  When the payment sheet is shown, select a different shipping address once. Then pay.
    182 </p>
    183 <ol>
    184  <li id="test-0">
    185    <button onclick="testImmediateUpdate(this);">
    186      updateWith() must be called immediately, otherwise must throw an InvalidStateError.
    187    </button>
    188  </li>
    189  <li id="test-1">
    190    <button onclick="testSubsequentUpdateWithCalls(this);">
    191      Once the event has performed an update, subsequent calls to updateWith() must throw InvalidStateError.
    192    </button>
    193  </li>
    194  <li id="test-2">
    195    <button onclick="testRecycleEvents(this);">
    196      Recycling events must not be possible.
    197    </button> When the payment sheet is shown, select a different shipping address once, then change shipping option once. Then pay.
    198  </li>
    199  <li>
    200    <button onclick="done();">Done!</button>
    201  </li>
    202 </ol>
    203 <small>
    204  If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
    205  and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
    206 </small>