tor-browser

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

async-navigator-clipboard-change-event.tentative.https.html (9218B)


      1 <!DOCTYPE html>
      2 <meta charset="utf-8" />
      3 <title>
      4  'clipboardchange' event should be fired upon setting clipboard using JS
      5 </title>
      6 <link rel="help" href="https://www.w3.org/TR/clipboard-apis/#clipboard-event-clipboardchange" />
      7 
      8 <body>
      9  Body needed for test_driver.click()
     10  <p><button id="button">Put payload in the clipboard</button></p>
     11  <div id="output"></div>
     12  <iframe id="iframe" srcdoc="<p>Some text</p>"></iframe>
     13  <link rel="help" href="https://issues.chromium.org/issues/41442253" />
     14 
     15  <script src="/resources/testharness.js"></script>
     16  <script src="/resources/testharnessreport.js"></script>
     17  <script src="/resources/testdriver.js"></script>
     18  <script src="/resources/testdriver-vendor.js"></script>
     19  <script src="resources/user-activation.js"></script>
     20 
     21  <script>
     22    function waitForRender() {
     23      return new Promise(resolve => {
     24        requestAnimationFrame(() => requestAnimationFrame(resolve));
     25      });
     26    }
     27 
     28    let typesToSet_ = ["text/html", "web txt/csv"];
     29    button.onclick = () => document.execCommand("copy");
     30    document.oncopy = (ev) => {
     31      ev.preventDefault();
     32      for (let i = 0; i < typesToSet_.length; i++) {
     33        const type = typesToSet_[i];
     34        const data = new Blob([`Test data for ${type}`], {type: type});
     35        ev.clipboardData.setData(type, data);
     36      }
     37    };
     38 
     39    function triggerCopyToClipboard(typesToSet) {
     40      if (typesToSet) {
     41        typesToSet_ = typesToSet;
     42      }
     43      return test_driver.click(button);
     44    }
     45 
     46    promise_test(async (test) => {
     47      let clipboardChangeEventCount = 0;
     48      let eventType = "";
     49      let capturedEventTypes = null;
     50      navigator.clipboard.addEventListener("clipboardchange", (ev) => {
     51        clipboardChangeEventCount++;
     52        eventType = ev.type;
     53        capturedEventTypes = ev.types;
     54      });
     55      await triggerCopyToClipboard();
     56      assert_equals(clipboardChangeEventCount, 1, "clipboardchange event should be called exactly once");
     57      assert_equals(eventType, "clipboardchange", "Event type should be 'clipboardchange'");
     58      assert_true(capturedEventTypes.includes("text/html"), "types should contain 'text/html'");
     59      assert_false(capturedEventTypes.includes("web txt/csv"), "types should not contain custom MIME type");
     60    }, "clipboardchange event is invoked");
     61 
     62    promise_test(async (test) => {
     63      await tryGrantWritePermission();
     64      let clipboardChangeEventCount = 0;
     65      let capturedEventTypes = null;
     66      navigator.clipboard.addEventListener("clipboardchange", (ev) => {
     67        clipboardChangeEventCount++;
     68        capturedEventTypes = ev.types;
     69      });
     70      await navigator.clipboard.writeText("Test text");
     71      await waitForRender();
     72      assert_equals(clipboardChangeEventCount, 1, "clipboardchange event should be called exactly once");
     73      assert_true(capturedEventTypes.includes("text/plain"), "types should contain 'text/plain'");
     74    }, "clipboardchange event is invoked with async clipboard API");
     75 
     76    promise_test(async (test) => {
     77      let onClipboardChangeAttributeCount = 0;
     78      let capturedEventTypes = null;
     79      navigator.clipboard.onclipboardchange = (ev) => {
     80        onClipboardChangeAttributeCount++;
     81        capturedEventTypes = ev.types;
     82      };
     83      await triggerCopyToClipboard();
     84      assert_equals(onClipboardChangeAttributeCount, 1, "onclipboardchange attribute should be called exactly once");
     85      assert_true(capturedEventTypes.includes("text/html"), "types should contain 'text/html'");
     86      assert_false(capturedEventTypes.includes("web txt/csv"), "types should not contain custom MIME type");
     87    }, "clipboardchange event is invoked using onclipboardchange attribute");
     88 
     89    promise_test(async (test) => {
     90      let onClipboardChangeAttributeCount = 0;
     91      let capturedEventTypes = null;
     92      navigator.clipboard.onclipboardchange = (ev) => {
     93        onClipboardChangeAttributeCount++;
     94        capturedEventTypes = ev.types;
     95      };
     96      await triggerCopyToClipboard(["web txt/csv"]);
     97      assert_equals(onClipboardChangeAttributeCount, 1, "onclipboardchange attribute should be called exactly once");
     98      assert_equals(capturedEventTypes.length, 0, "clipboardchange event should have no types");
     99    }, "clipboardchange event is invoked even when only custom MIME types are set");
    100 
    101    promise_test(async (test) => {
    102      let listenerCallCount = 0;
    103      function clipboardChangeListener() {
    104        listenerCallCount++;
    105      }
    106 
    107      // 1. Add listener and verify it's called
    108      navigator.clipboard.addEventListener("clipboardchange", clipboardChangeListener);
    109      await triggerCopyToClipboard();
    110      assert_equals(listenerCallCount, 1, "Event listener should be called exactly once after adding");
    111 
    112      // 2. Remove listener and verify it's not called
    113      navigator.clipboard.removeEventListener("clipboardchange", clipboardChangeListener);
    114      await triggerCopyToClipboard();
    115      assert_equals(listenerCallCount, 1, "Event listener should not be called after removing");
    116 
    117      // 3. Re-add listener and verify it's called again
    118      navigator.clipboard.addEventListener("clipboardchange", clipboardChangeListener);
    119      await triggerCopyToClipboard();
    120      assert_equals(listenerCallCount, 2, "Event listener should be called exactly once after re-adding");
    121    }, "clipboardchange event listener behavior when adding, removing, and re-adding");
    122 
    123    promise_test(async (test) => {
    124      // https://w3c.github.io/clipboard-apis/#mandatory-data-types-x
    125      const standardTypes = [
    126        "text/plain",
    127        "text/html",
    128        "image/png",
    129      ];
    130      const unsupportedTypes = [
    131        "web application/custom",
    132        "web web/proprietary",
    133        "web x-custom/type",
    134        "txt/json",
    135        "text/rtf",
    136        "image/svg+xml",
    137        "text/uri-list",
    138      ];
    139      const allTypesToSet = [...standardTypes, ...unsupportedTypes];
    140 
    141      let clipboardChangeEventCount = 0;
    142      let capturedEventTypes = null;
    143 
    144      navigator.clipboard.addEventListener("clipboardchange", (ev) => {
    145        clipboardChangeEventCount++;
    146        capturedEventTypes = ev.types;
    147      });
    148 
    149      await triggerCopyToClipboard(allTypesToSet);
    150 
    151      assert_true(clipboardChangeEventCount == 1, "clipboardchange event should be invoked once");
    152 
    153      // Check that types is a frozen array
    154      assert_true(Array.isArray(capturedEventTypes), "types should be an array");
    155      assert_true(Object.isFrozen(capturedEventTypes), "types should be frozen");
    156 
    157      // Verify all standard types are included
    158      for (const type of standardTypes) {
    159        assert_true(capturedEventTypes.includes(type), `types should contain standard MIME type '${type}'`);
    160      }
    161 
    162      // Verify custom types are filtered out
    163      for (const type of unsupportedTypes) {
    164        assert_false(capturedEventTypes.includes(type), `types should not contain custom MIME type '${type}'`);
    165      }
    166 
    167      // Verify we have exactly the standard types and nothing else
    168      assert_equals(capturedEventTypes.length, standardTypes.length,
    169        "clipboardchange event types should contain exactly the standard MIME types");
    170    }, "clipboardchange event exposes all standard MIME types and filters non-standard ones");
    171 
    172    promise_test(async (test) => {
    173      // Focus the document and acquire permission to write to the clipboard
    174      await test_driver.click(document.body);
    175      await tryGrantWritePermission();
    176 
    177      const iframe = document.getElementById('iframe');
    178 
    179      let frameEventCount = 0;
    180      let capturedEventTypes = null;
    181      let focusEventFired = false;
    182      iframe.contentWindow.addEventListener("focus", () => {
    183        focusEventFired = true;
    184      });
    185 
    186      // Add listener to iframe
    187      iframe.contentWindow.navigator.clipboard.addEventListener("clipboardchange", () => {
    188        assert_true(focusEventFired, "focus event should fire before clipboardchange event");
    189        frameEventCount++;
    190        capturedEventTypes = event.types;
    191      });
    192 
    193      // Ensure iFrame doesn't have the focus
    194      assert_false(iframe.contentWindow.document.hasFocus(), "iFrame should not have focus");
    195      assert_false(focusEventFired, "focus event should not have fired yet");
    196 
    197      // Trigger multiple clipboard changes
    198      await navigator.clipboard.writeText("Test text");
    199 
    200      // Write HTML to clipboard to ensure the event captured only html and not txt
    201      await navigator.clipboard.write([
    202        new ClipboardItem({
    203          "text/html": new Blob(["<p>Test HTML</p>"], {type: "text/html"})
    204        })
    205      ]);
    206      await waitForRender();
    207 
    208      assert_equals(frameEventCount, 0, "iframe should not recieve any clipboardchange event yet");
    209 
    210      iframe.focus();
    211      assert_true(iframe.contentWindow.document.hasFocus(), "iFrame should have focus");
    212      assert_equals(frameEventCount, 1, "iframe should receive event only 1 event after focus");
    213      assert_equals(capturedEventTypes.length, 1, "clipboardchange event should only have one type");
    214      assert_true(capturedEventTypes.includes("text/html"), "clipboardchange event should only have text/html type");
    215    }, "clipboardchange event should only fire in the focused context");
    216 
    217  </script>
    218 </body>