| <!DOCTYPE html> | |
| <title>Test Script-Based Focus for Fenced Frames</title> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <script src="/resources/testdriver.js"></script> | |
| <script src="/resources/testdriver-actions.js"></script> | |
| <script src="/resources/testdriver-vendor.js"></script> | |
| <script src="/common/utils.js"></script> | |
| <script src="resources/utils.js"></script> | |
| <script src="/common/dispatcher/dispatcher.js"></script> | |
| <script src="/common/get-host-info.sub.js"></script> | |
| <body> | |
| <script> | |
| async function AttemptButtonFocus(frame, expecting_focus) { | |
| await frame.execute(async (expecting_focus) => { | |
| const button = document.createElement("button"); | |
| document.body.append(button); | |
| button.focus(); | |
| assert_equals(document.activeElement == button, expecting_focus, | |
| "Button's focus should match expected focus"); | |
| }, [expecting_focus]); | |
| } | |
| async function ClickOn(element, actions) { | |
| // Wait until the window size is initialized. | |
| while (window.innerWidth == 0) { | |
| await new Promise(resolve => requestAnimationFrame(resolve)); | |
| } | |
| await actions.pointerMove(0, 0, {origin: element}) | |
| .pointerDown() | |
| .pointerUp() | |
| .send(); | |
| } | |
| async function SetupTest(click=true) { | |
| // Clean up any leftover frames from prior tests. | |
| document.querySelectorAll("fencedframe").forEach(e => { | |
| e.remove(); | |
| }) | |
| const actions = new test_driver.Actions(); | |
| const frame = attachFencedFrameContext(); | |
| const fencedframe_element = frame.element; | |
| if (click) | |
| await ClickOn(document.body, actions); | |
| return [actions, frame, fencedframe_element]; | |
| } | |
| promise_test(async () => { | |
| const [actions, ff1, ff1_element] = await SetupTest(false); | |
| await ClickOn(ff1_element, actions); | |
| await AttemptButtonFocus(ff1, true); | |
| const button = document.createElement("button"); | |
| document.body.append(button); | |
| button.focus(); | |
| assert_true(document.activeElement == button, | |
| "The button should have focus"); | |
| assert_false(navigator.userActivation.isActive, | |
| "Window should not have user activation"); | |
| }, "An embedder can focus out of a fenced frame"); | |
| promise_test(async () => { | |
| const [actions, frame, fencedframe_element] = await SetupTest(); | |
| await AttemptButtonFocus(frame, false); | |
| await ClickOn(fencedframe_element, actions); | |
| await AttemptButtonFocus(frame, true); | |
| }, "Fenced frames can't pull script focus until getting user activation"); | |
| promise_test(async t => { | |
| const [actions, frame, fencedframe_element] = await SetupTest(); | |
| await ClickOn(fencedframe_element, actions); | |
| await ClickOn(document.body, actions); | |
| await AttemptButtonFocus(frame, true); | |
| // Give the browser time to receive the focus event before attempting | |
| // another focus. | |
| await t.step_timeout(async () => {await AttemptButtonFocus(frame, true);}, | |
| 500); | |
| }, "Focused fenced frames can move programmatic focus within frame"); | |
| promise_test(async () => { | |
| const [actions, frame, fencedframe_element] = await SetupTest(); | |
| await ClickOn(fencedframe_element, actions); | |
| await ClickOn(document.body, actions); | |
| // This will pull focus across a frame boundary and consume user activation. | |
| await AttemptButtonFocus(frame, true); | |
| await ClickOn(document.body, actions); | |
| await AttemptButtonFocus(frame, false); | |
| }, "Script focus into a fenced frame consumes user activation"); | |
| promise_test(async () => { | |
| const [actions, ff1, ff1_element] = await SetupTest(); | |
| const ff2 = attachFencedFrameContext(); | |
| const ff2_element = ff2.element; | |
| await ClickOn(ff1_element, actions); | |
| await AttemptButtonFocus(ff1, true); | |
| await AttemptButtonFocus(ff2, false); | |
| }, "Another fenced frame cannot pull focus out of a focused fenced frame"); | |
| promise_test(async () => { | |
| const [actions, ff1, ff1_element] = await SetupTest(); | |
| await ClickOn(ff1_element, actions); | |
| await AttemptButtonFocus(ff1, true); | |
| await ff1.execute(async () => { | |
| const ff2 = attachFencedFrameContext(); | |
| await ff2.execute(async () => { | |
| const button = document.createElement("button"); | |
| document.body.append(button); | |
| button.focus(); | |
| assert_false(document.activeElement == button, | |
| "The button should not have focus"); | |
| assert_false(navigator.userActivation.isActive, | |
| "The fenced frame should not have user activation"); | |
| }); | |
| }); | |
| }, "A fenced frame nested in another fenced frame cannot pull focus"); | |
| promise_test(async () => { | |
| const [actions, ff1, ff1_element] = await SetupTest(); | |
| await ClickOn(document.body, actions); | |
| const button = document.createElement("button"); | |
| document.body.append(button); | |
| button.focus(); | |
| assert_equals(document.activeElement, button, | |
| "The button in the main page should have focus."); | |
| await ff1.execute(async () => { | |
| assert_false(navigator.userActivation.isActive, | |
| "The fenced frame should not have user activation."); | |
| window.focus(); | |
| }); | |
| assert_equals(document.activeElement, button, | |
| "The button in the main page should still have focus."); | |
| }, "A fenced frame cannot pull window.focus() without user activation"); | |
| promise_test(async () => { | |
| const [actions, ff1, ff1_element] = await SetupTest(); | |
| await ClickOn(ff1_element, actions); | |
| await ClickOn(document.body, actions); | |
| const button = document.createElement("button"); | |
| document.body.append(button); | |
| button.focus(); | |
| assert_equals(document.activeElement, button, | |
| "The button should have focus."); | |
| await ff1.execute(async () => { | |
| assert_true(navigator.userActivation.isActive, | |
| "The fenced frame should have user activation."); | |
| window.focus(); | |
| assert_false(navigator.userActivation.isActive, | |
| "The fenced frame's user activation should be consumed by the focus"); | |
| }); | |
| assert_equals(document.activeElement, document.body, | |
| "The main page's focus should be pulled away from the button."); | |
| }, "A fenced frame can pull window.focus() after user activation"); | |
| promise_test(async () => { | |
| var actions = new test_driver.Actions(); | |
| const frame = attachIFrameContext( | |
| {origin: get_host_info().HTTPS_REMOTE_ORIGIN}); | |
| const iframe_element = | |
| document.body.getElementsByTagName('iframe')[0]; | |
| await frame.execute(async () => { | |
| const button = document.createElement("button"); | |
| document.body.append(button); | |
| button.focus(); | |
| assert_equals(document.activeElement, button, | |
| "The button in the iframe should have focus."); | |
| }, [true]); | |
| const button = document.createElement("button"); | |
| document.body.append(button); | |
| button.focus(); | |
| assert_equals(document.activeElement, button, | |
| "The button in the main page should have focus."); | |
| }, "An cross-origin iframe can pull focus back and forth without activation"); | |
| </script> | |
| </body> |