Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix flaky consumption-sameorigin.html iframe loading #36304

Closed
wants to merge 3 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions html/user-activation/consumption-sameorigin.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
assert_false(navigator.userActivation.hasBeenActive);
}, "Parent frame initial state");

test_driver.click(document.getElementById("child-so"));
return test_driver.click(document.getElementById("child-so"));
}

function finishReportPhase() {
Expand All @@ -40,7 +40,7 @@
consumption_test.done();
}

window.addEventListener("message", event => {
window.addEventListener("message", async event => {
var msg = JSON.parse(event.data);

if (msg.type == 'child-one-loaded') {
Expand Down Expand Up @@ -84,25 +84,32 @@
// Phase switching.
if (msg.type.endsWith("-loaded")) {
if (--num_children_to_load == 0)
finishLoadPhase();
await finishLoadPhase();
} else if (msg.type.endsWith("-report")) {
if (--num_children_to_report == 0)
finishReportPhase();
}
});
async function createIframes() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this really improve the flakes? I am asking because the block in Line 46-55 already handles the case added here: the child frames pings the top frame when they see load events.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. The problem is that the (html) iframes were randomly in different order.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In L85-87, the "load phase" ends after all 3 frames are loaded in any order.

const child1 = document.createElement("iframe");
child1.src = "resources/child-one.html";
child1.id = "child1";
document.body.appendChild(child1);

await new Promise((resolve) => (child1.onload = resolve));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Is iframe.onload event really tied to child frame's window.onload? The normative note at the end of this spec section seems to suggest we have to wait explicitly for child frame's window.onload, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be more correct, yes. However, my testing this on a few hundred runs seems to confirm it's fine (all that is needed here is that the child1 loads first).

Copy link
Member

@annevk annevk Oct 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're misreading that note @mustaqahmed. That's just about event propagation. iframe's load event will fire after its inner document load event. (In general I don't recommend reading the UI Events spec for this as it doesn't define dispatching. I'm somewhat surprised the load event is still there.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Questions for @annevk: Does the dispatching algorithm enforce a "load" event order across a frame boundary (even with a possibly slow cross-origin frame)?

I am asking because a "yes" here means the test could be simpler because it would be sufficient to wait for only the top window.onload (the top window.onload would follow the iframe.onload event which would follow inner window.onload event). Is this correct?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's correct.

Copy link
Contributor Author

@marcoscaceres marcoscaceres Oct 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mustaqahmed, can you help me understand what cause the consumption of the user activation here?

I'd still prefer not to rewrite the test if I can avoid it.

However, if I flatten the test to:

async function runTest() {
  promise_test(async (t) => {
      const child1 = document.getElementById("child1");
      const child1Activation = child1.contentWindow.navigator.userActivation;
      const child2 = document.getElementById("child2");
      const child2Activation = child2.contentWindow.navigator.userActivation;
      const grandChild = child2.contentDocument.getElementById("grandchild");
      const grandChildActivation = grandChild.contentWindow.navigator.userActivation;
      
      assert_false(navigator.userActivation.isActive, "Parent frame isActive initial state");
      assert_false(navigator.userActivation.hasBeenActive, "Parent frame hasBeenActive initial state");
      assert_false(child1Activation.isActive, "Child1 frame isActive initial state");
      assert_false(child1Activation.hasBeenActive, "Child1 frame hasBeenActive initial state");
      assert_false(child2Activation.isActive, "Child2 frame isActive initial state");
      assert_false(child2Activation.hasBeenActive, "Child2 frame hasBeenActive initial state");
      assert_false(grandChildActivation.isActive, "Grandchild frame isActive initial state");
      assert_false(grandChildActivation.hasBeenActive, "Grandchild frame hasBeenActive initial state");
      
      await test_driver.bless("click", null, child1.contentWindow);

      assert_true(navigator.userActivation.isActive, "Parent frame isActive after click");
      assert_true(navigator.userActivation.hasBeenActive, "Parent frame hasBeenActive after click");
      assert_true(child1Activation.isActive, "Child1 frame isActive after click");
      assert_true(child1Activation.hasBeenActive, "Child1 frame hasBeenActive after click");
      assert_true(child2Activation.isActive, "Child2 frame isActive after click");
      assert_true(child2Activation.hasBeenActive, "Child2 frame hasBeenActive after click");
      assert_true(grandChildActivation.isActive, "Grandchild frame isActive after click");
      assert_true(grandChildActivation.hasBeenActive, "Grandchild frame hasBeenActive after click");
  });
}

I get different results? I can't see anything in the iframes that consume the user activation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get different results? I can't see anything in the iframes that consume the user activation?

We shouldn't get a different result, because a consumption call affects the whole frame tree.

Could you please elaborate which assertions are different between flattened vs un-flattened frame load code?

Is there a chance test_driver implementation is causing the flake here? This same-origin test is not flaky in Chrome's internal bots, but the cross-origin one is flaky and we can't fix that because of a test_driver impl limitation.

In any case, I think a virtual meeting would be more efficient here to share our thoughts. Let's plan one.

Copy link
Contributor Author

@marcoscaceres marcoscaceres Oct 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't get a different result, because a consumption call affects the whole frame tree.

Oh, wait! But the test again does something non-standard:

window.open().close();

That shouldn't consume the user activation: at least, there is nothing in HTML saying that either window.open() or window.close() consumes it.

The flattened version should be this (all false, click(), all true):

async function runTest() {
  promise_test(async (t) => {
      const child1 = document.getElementById("child1");
      const child1Activation = child1.contentWindow.navigator.userActivation;
      const child2 = document.getElementById("child2");
      const child2Activation = child2.contentWindow.navigator.userActivation;
      const grandChild = child2.contentDocument.getElementById("grandchild");
      const grandChildActivation = grandChild.contentWindow.navigator.userActivation;
      
      assert_false(navigator.userActivation.isActive, "Parent frame isActive initial state");
      assert_false(navigator.userActivation.hasBeenActive, "Parent frame hasBeenActive initial state");
      assert_false(child1Activation.isActive, "Child1 frame isActive initial state");
      assert_false(child1Activation.hasBeenActive, "Child1 frame hasBeenActive initial state");
      assert_false(child2Activation.isActive, "Child2 frame isActive initial state");
      assert_false(child2Activation.hasBeenActive, "Child2 frame hasBeenActive initial state");
      assert_false(grandChildActivation.isActive, "Grandchild frame isActive initial state");
      assert_false(grandChildActivation.hasBeenActive, "Grandchild frame hasBeenActive initial state");
      
      await test_driver.bless("click", null, child1.contentWindow);

      assert_true(navigator.userActivation.isActive, "Parent frame isActive after click");
      assert_true(navigator.userActivation.hasBeenActive, "Parent frame hasBeenActive after click");
      assert_true(child1Activation.isActive, "Child1 frame isActive after click");
      assert_true(child1Activation.hasBeenActive, "Child1 frame hasBeenActive after click");
      assert_true(child2Activation.isActive, "Child2 frame isActive after click");
      assert_true(child2Activation.hasBeenActive, "Child2 frame hasBeenActive after click");
      assert_true(grandChildActivation.isActive, "Grandchild frame isActive after click");
      assert_true(grandChildActivation.hasBeenActive, "Grandchild frame hasBeenActive after click");
  });
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case, I think a virtual meeting would be more efficient here to share our thoughts. Let's plan one.

Happy to chat, but I think the biggest points of contention are that we don't have a consistent way of consuming the user activation.

So far, the tests have used two non-standard ways to try to consume it:

  1. requestFullscreen() + exitFullscreen()
  2. window.open() + window.close()

We should also avoid window.open() tests. They don't work well on mobile devices.

I'm thinking we should remove or "-tentative" such tests until we have an actual solution to prevent codifying non-standard behavior.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const childSO = document.createElement("iframe");
childSO.id = "child-so";
childSO.src = "resources/consumption-sameorigin-child.html";
document.body.appendChild(childSO);
await new Promise((resolve) => (childSO.onload = resolve));
marcoscaceres marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we are still relying on the load event behavior @annevk mentioned above...I mean the fact that childSO's load is ultimately blocked by "grandchild" frame's load, right? I find this mix of flattening-vs-not a bit too awkward!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this line. There is no need to await for it to load. What matters is just that the first iframe loads in order.

I've made similar changes to all the other dual iframe tests. I'll have them re-viewed upstream as part of my WebKit patch and will just send them reviewed as part of that.

marcoscaceres marked this conversation as resolved.
Show resolved Hide resolved
}
</script>
</head>
<body>
<body onload="createIframes()" >
<h1>User activation consumption across same-origin frame boundary</h1>
<p>Tests that user activation consumption resets the transient states in all same-origin frames.</p>
<ol id="instructions">
<li>Click anywhere on the green area (child frame).
</ol>
<iframe id="child1" width="300px" height="40px"
src="resources/child-one.html">
</iframe>
<iframe id="child-so" width="300px" height="140px"
src="resources/consumption-sameorigin-child.html">
</iframe>
</body>
</html>