web-platform-tests / wpt

Test suites for Web platform specs — including WHATWG, W3C, and others
https://web-platform-tests.org/
Other
5.01k stars 3.11k forks source link

`assert_throws_js` fails for an element adopted from an iframe #45405

Open mbrodesser-Igalia opened 7 months ago

mbrodesser-Igalia commented 7 months ago

Example:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      http-equiv="Content-Security-Policy"
      content="require-trusted-types-for 'script';"
    />
    <script src="/resources/testharness.js"></script>
    <script src="/resources/testharnessreport.js"></script>
  </head>
  <body>
    <iframe id="otherIframe"></iframe>
    <script>
      const passThroughPolicy = trustedTypes.createPolicy("passThrough", {
        createHTML: (s) => s,
      });

      async_test((t) => {
        const sourceFrame = document.createElement("iframe");

        sourceFrame.srcdoc = passThroughPolicy.createHTML(
          `<!DOCTYPE html>
            <head>
            <meta charset="utf-8">
            </head>
            <body><iframe srcdoc="v">
            doc without TT CSP.
            </body>`
        );

        sourceFrame.addEventListener(
          "load",
          t.step_func_done(() => {
            const sourceElement =
              sourceFrame.contentDocument.body.querySelector("iframe");
            const sourceAttr = sourceElement.getAttributeNode("srcdoc");
            sourceElement.removeAttributeNode(sourceAttr);

            document.body.append(sourceElement);

            assert_throws_js(TypeError, () => {
              const otherIframe = document.getElementById("otherIframe");
              // This should throw because trusted types are enabled.
              otherIframe.setAttributeNode(sourceAttr);
            }); // works

            assert_throws_js(TypeError, () => {
              // This should throw because trusted types are enabled.
              sourceElement.setAttributeNode(sourceAttr);
            }); // fails
          })
        );

        document.body.append(sourceFrame);
      }, `x`);
    </script>
  </body>
</html>

Error log:

Unexpected subtest result in /trusted-types/x.html:
  │ FAIL [expected PASS] x
  │   → assert_throws_js: function "() => {
              // This should throw because trusted types are enabled.
              sourceElement.setAttributeNode(sourceAttr);
            }" threw object "TypeError: Failed to execute 'setAttributeNode' on 'Element': This document requires 'TrustedHTML' assignment." ("TypeError") expected instance of function "function TypeError() { [native code] }" ("TypeError")
  │ 
  │     at Test.<anonymous> (http://web-platform.test:8000/trusted-types/x.html:47:13)
  │     at Test.step (http://web-platform.test:8000/resources/testharness.js:2622:25)
  └     at HTMLIFrameElement.<anonymous> (http://web-platform.test:8000/resources/testharness.js:2697:32)

  [1/1] No tests running.

Reproducible with Chrome; Firefox doesn't support Trusted Types yet.

The failure stems from https://searchfox.org/mozilla-central/rev/b4860b54810539f0e4ab1fc46a3246daf2428439/testing/web-platform/tests/resources/testharness.js#2122.

CC @lukewarlow

mbrodesser-Igalia commented 7 months ago

When debugging this, be aware that https://github.com/web-platform-tests/wpt/issues/44352 will prevent the above error log.

mbrodesser-Igalia commented 7 months ago

Adding

      console.log(
        "(e.constructor === constructor)=" + (e.constructor === constructor)
      );
      console.log("(e instanceof TypeError)=" + (e instanceof TypeError));
      console.log("e.constructor.name=" + e.constructor.name);

to https://searchfox.org/mozilla-central/rev/b4860b54810539f0e4ab1fc46a3246daf2428439/testing/web-platform/tests/resources/testharness.js#2122 reveals:

 "(e.constructor === constructor)=true", source: http://web-platform.test:8000/resources/testharness.js (2408)
 "(e instanceof TypeError)=true", source: http://web-platform.test:8000/resources/testharness.js (2411)
 "e.constructor.name=TypeError", source: http://web-platform.test:8000/resources/testharness.js (2412)

 "(e.constructor === constructor)=false", source: http://web-platform.test:8000/resources/testharness.js (2408)
 "(e instanceof TypeError)=false", source: http://web-platform.test:8000/resources/testharness.js (2411)
 "e.constructor.name=TypeError", source: http://web-platform.test:8000/resources/testharness.js (2412)
ptomato commented 7 months ago

It may be relevant for you, test262 takes this case into account in its assert.throws() function with a specific error message: https://github.com/tc39/test262/blob/837def66ac1700ce398df4a3b4eaeea915aa1df5/harness/assert.js#L87-L94

Ms2ger commented 7 months ago

This is unsurprising, yeah. Use otherIframe.contentWindow.TypeError or something like that

mbrodesser-Igalia commented 7 months ago

This is unsurprising, yeah. Use otherIframe.contentWindow.TypeError or something like that

@Ms2ger: please explain why it's unsurprising. It's not obvious.

Using

assert_throws_js(otherIframe.contentWindow.TypeError, () => {
              // This should throw because trusted types are enabled.
              sourceElement.setAttributeNode(sourceAttr);
            }); // fails

still fails.

mbrodesser-Igalia commented 7 months ago
assert_throws_js(sourceFrame.contentWindow.TypeError

works.

mbrodesser-Igalia commented 7 months ago

Admittedly, I still don't understand why so would be happy to learn about it.

Ms2ger commented 7 months ago

Every realm has its own set of intrinsics, including TypeError, and JavaScript does not go behind the scenes to match those up when you do instanceof. So if you have

error from page A → TypeError from page A
error from page B → TypeError from page B

you should by default not assume there's a connection between error from page A and TypeError from page B. I hope that clarifies a bit.

mbrodesser-Igalia commented 7 months ago

Every realm has its own set of intrinsics, including TypeError, and JavaScript does not go behind the scenes to match those up when you do instanceof. So if you have

error from page A → TypeError from page A
error from page B → TypeError from page B

you should by default not assume there's a connection between error from page A and TypeError from page B. I hope that clarifies a bit.

@Ms2ger: a bit. None of the documentation I read implied that, or I was unable to deduce those implications.