Skip to content

Commit a48e87e

Browse files
Activation across navigation: add a WPT for cross-origin redirection
This CL adds a new WPT for user activation states after a cross-origin redirection, using `RemoteContext` API in dispatcher.js. This behavior is not fully defined in the spec [1], and the test was written based on the behavior expected in a PR discussion [2]. This CL also simplifies the existing same-origin test using the same API, and fixes the visibility of 3 private methods in utils.js. [1] https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigation-and-session-history:~:text=Welcome%20to%20the%20dragon%27s%20maw. [2] whatwg/html#11454 Bug: 480993679 Change-Id: I9c7e5ad836404d7b1bf8e484a89fc2824b7c0364 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7769229 Commit-Queue: Mustaq Ahmed <mustaq@chromium.org> Reviewed-by: Vladimir Levin <vmpstr@chromium.org> Cr-Commit-Position: refs/heads/main@{#1616504}
1 parent eab2c2c commit a48e87e

4 files changed

Lines changed: 178 additions & 91 deletions

File tree

common/utils.js

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010
* @returns {string}
1111
*/
1212
function make_absolute_url(options) {
13+
function get(obj, name, default_val) {
14+
if (obj.hasOwnProperty(name)) {
15+
return obj[name];
16+
}
17+
return default_val;
18+
}
19+
1320
var loc = window.location;
1421
var protocol = get(options, "protocol", loc.protocol);
1522
if (protocol[protocol.length - 1] != ":") {
@@ -52,47 +59,37 @@ function make_absolute_url(options) {
5259
return url;
5360
}
5461

55-
/** @private */
56-
function get(obj, name, default_val) {
57-
if (obj.hasOwnProperty(name)) {
58-
return obj[name];
59-
}
60-
return default_val;
61-
}
62-
6362
/**
6463
* Generate a new UUID.
6564
* @returns {string}
6665
*/
6766
function token() {
67+
function rand_int(bits) {
68+
if (bits < 1 || bits > 53) {
69+
throw new TypeError();
70+
} else {
71+
if (bits >= 1 && bits <= 30) {
72+
return 0 | ((1 << bits) * Math.random());
73+
} else {
74+
var high = (0 | ((1 << (bits - 30)) * Math.random())) * (1 << 30);
75+
var low = 0 | ((1 << 30) * Math.random());
76+
return high + low;
77+
}
78+
}
79+
}
80+
81+
function to_hex(x, length) {
82+
var rv = x.toString(16);
83+
while (rv.length < length) {
84+
rv = "0" + rv;
85+
}
86+
return rv;
87+
}
88+
6889
var uuid = [to_hex(rand_int(32), 8),
6990
to_hex(rand_int(16), 4),
7091
to_hex(0x4000 | rand_int(12), 4),
7192
to_hex(0x8000 | rand_int(14), 4),
7293
to_hex(rand_int(48), 12)].join("-")
7394
return uuid;
7495
}
75-
76-
/** @private */
77-
function rand_int(bits) {
78-
if (bits < 1 || bits > 53) {
79-
throw new TypeError();
80-
} else {
81-
if (bits >= 1 && bits <= 30) {
82-
return 0 | ((1 << bits) * Math.random());
83-
} else {
84-
var high = (0 | ((1 << (bits - 30)) * Math.random())) * (1 << 30);
85-
var low = 0 | ((1 << 30) * Math.random());
86-
return high + low;
87-
}
88-
}
89-
}
90-
91-
/** @private */
92-
function to_hex(x, length) {
93-
var rv = x.toString(16);
94-
while (rv.length < length) {
95-
rv = "0" + rv;
96-
}
97-
return rv;
98-
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<!DOCTYPE html>
2+
<title>User activation propagation across a cross-origin redirection</title>
3+
<script src="/common/dispatcher/dispatcher.js"></script>
4+
<script src="/common/get-host-info.sub.js"></script>
5+
<script src="/common/utils.js"></script>
6+
<script src="/resources/testharness.js"></script>
7+
<script src="/resources/testharnessreport.js"></script>
8+
<script src="/resources/testdriver.js"></script>
9+
<script src="/resources/testdriver-vendor.js"></script>
10+
<script src="/resources/testdriver-actions.js"></script>
11+
<script src="resources/utils.js"></script>
12+
13+
<body>
14+
<p>Non-zero-sized body to allow clicks.</p>
15+
</body>
16+
17+
<script>
18+
'use strict';
19+
20+
let uuid1 = token();
21+
let uuid2 = token();
22+
23+
let origin = get_host_info().HTTP_ORIGIN;
24+
let remote_origin = get_host_info().HTTP_REMOTE_ORIGIN;
25+
let clicked_link =
26+
`${remote_origin}/common/redirect.py?location=` +
27+
`${origin}/common/dispatcher/remote-executor.html?uuid=${uuid2}`;
28+
29+
const getUserActivationState = () =>
30+
[navigator.userActivation.isActive, navigator.userActivation.hasBeenActive];
31+
32+
function assertUserActivationState(list,
33+
expected_transient_bit, expected_sticky_bit, msg) {
34+
assert_equals(list[0], expected_transient_bit, "Transient bit " + msg);
35+
assert_equals(list[1], expected_sticky_bit, "Sticky bit " + msg);
36+
}
37+
38+
promise_test(async test => {
39+
// Open a new window that we will navigate away and observe.
40+
let w;
41+
document.body.onclick = () => {
42+
w = window.open(`/common/dispatcher/remote-executor.html?uuid=${uuid1}`);
43+
};
44+
45+
await test_driver.click(document.body);
46+
assert_true(!!w, "A window is opened");
47+
48+
// Populate the window: add two elements (a non-link and a link) to allow
49+
// the following clicks.
50+
{
51+
const context = new RemoteContext(uuid1);
52+
await context.execute_script(link => {
53+
// Not sure why the first click below fails w/o this line!
54+
document.body.textContent = "";
55+
56+
let elem1 = document.createElement("div");
57+
elem1.textContent = "Dummy text to make it clickable";
58+
document.body.appendChild(elem1);
59+
60+
let elem2 = document.createElement("a");
61+
elem2.textContent = "Dummy text to make it clickable";
62+
elem2.href = link;
63+
64+
document.body.appendChild(elem2);
65+
}, [clicked_link]);
66+
}
67+
68+
// Check that the new window does not have user activation initially.
69+
{
70+
const context = new RemoteContext(uuid1);
71+
const result = await context.execute_script(getUserActivationState);
72+
assertUserActivationState(result, false, false, "after window opened");
73+
}
74+
75+
// Confirm that the new window gets user activation after a click.
76+
await test_driver.click(w.document.body.firstElementChild);
77+
{
78+
const context = new RemoteContext(uuid1);
79+
const result = await context.execute_script(getUserActivationState);
80+
assertUserActivationState(result, true, true, "after window clicked");
81+
}
82+
83+
// Click on the link to navigate away.
84+
await test_driver.click(w.document.body.lastElementChild);
85+
86+
// Confirm that the new window does not have user activation.
87+
{
88+
const context = new RemoteContext(uuid2);
89+
const result = await context.execute_script(getUserActivationState);
90+
assertUserActivationState(result, false, false,
91+
"after window nagivated and redirected");
92+
}
93+
}, "User activation propagation across a same-origin navigation");
94+
</script>
Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<!DOCTYPE html>
22
<title>User activation propagation across a same-origin navigation</title>
3+
<script src="/common/dispatcher/dispatcher.js"></script>
4+
<script src="/common/utils.js"></script>
35
<script src="/resources/testharness.js"></script>
46
<script src="/resources/testharnessreport.js"></script>
57
<script src="/resources/testdriver.js"></script>
@@ -8,51 +10,77 @@
810
<script src="resources/utils.js"></script>
911

1012
<body>
11-
<p>Placeholder</p>
13+
<p>Non-zero-sized body to allow clicks.</p>
1214
</body>
1315

1416
<script>
1517
'use strict';
1618

17-
let w;
18-
document.body.onclick = () => {
19-
w = window.open("resources/opened-window.html");
20-
};
19+
let uuid1 = token();
20+
let uuid2 = token();
21+
let clicked_link = `/common/dispatcher/remote-executor.html?uuid=${uuid2}`
22+
23+
const getUserActivationState = () =>
24+
[navigator.userActivation.isActive, navigator.userActivation.hasBeenActive];
25+
26+
function assertUserActivationState(list,
27+
expected_transient_bit, expected_sticky_bit, msg) {
28+
assert_equals(list[0], expected_transient_bit, "Transient bit " + msg);
29+
assert_equals(list[1], expected_sticky_bit, "Sticky bit " + msg);
30+
}
2131

2232
promise_test(async test => {
23-
let window_opened_msg_promise = receiveMessage("window-opened");
33+
// Open a new window that we will navigate away and observe.
34+
let w;
35+
document.body.onclick = () => {
36+
w = window.open(`/common/dispatcher/remote-executor.html?uuid=${uuid1}`);
37+
};
2438

2539
await test_driver.click(document.body);
2640
assert_true(!!w, "A window is opened");
2741

42+
// Populate the window: add two elements (a non-link and a link) to allow
43+
// the following clicks.
2844
{
29-
let window_opened_data = await window_opened_msg_promise;
30-
assert_false(window_opened_data.isActive,
31-
"Transient activation after window opened");
32-
assert_false(window_opened_data.hasBeenActive,
33-
"Sticky activation after window opened");
34-
}
45+
const context = new RemoteContext(uuid1);
46+
await context.execute_script(link => {
47+
// Not sure why the first click below fails w/o this line!
48+
document.body.textContent = "";
49+
50+
let elem1 = document.createElement("div");
51+
elem1.textContent = "Dummy text to make it clickable";
52+
document.body.appendChild(elem1);
3553

36-
let link_clicked_msg_promise = receiveMessage("link-clicked");
37-
let window_navigated_msg_promise = receiveMessage("window-navigated");
54+
let elem2 = document.createElement("a");
55+
elem2.textContent = "Dummy text to make it clickable";
56+
elem2.href = link;
57+
document.body.appendChild(elem2);
58+
}, [clicked_link]);
59+
}
3860

39-
const link = w.document.getElementById("link");
40-
await test_driver.click(link);
61+
// Check that the new window does not have user activation initially.
62+
{
63+
const context = new RemoteContext(uuid1);
64+
const result = await context.execute_script(getUserActivationState);
65+
assertUserActivationState(result, false, false, "after window opened");
66+
}
4167

68+
// Confirm that the new window gets user activation after a click.
69+
await test_driver.click(w.document.body.firstElementChild);
4270
{
43-
let link_clicked_data = await link_clicked_msg_promise;
44-
assert_true(link_clicked_data.isActive,
45-
"Transient activation after link clicked");
46-
assert_true(link_clicked_data.hasBeenActive,
47-
"Sticky activation after link clicked");
71+
const context = new RemoteContext(uuid1);
72+
const result = await context.execute_script(getUserActivationState);
73+
assertUserActivationState(result, true, true, "after window clicked");
4874
}
4975

76+
// Click on the link to navigate away.
77+
await test_driver.click(w.document.body.lastElementChild);
78+
79+
// Confirm that the new window does not have user activation.
5080
{
51-
let window_navigated_data = await window_navigated_msg_promise;
52-
assert_false(window_navigated_data.isActive,
53-
"Transient activation after window navigated");
54-
assert_true(window_navigated_data.hasBeenActive,
55-
"Sticky activation after window navigated");
81+
const context = new RemoteContext(uuid2);
82+
const result = await context.execute_script(getUserActivationState);
83+
assertUserActivationState(result, false, true, "after window redirected");
5684
}
57-
}, "User activation propagation across a same-origin navigation");
85+
}, "User activation propagation across a same-origin navigation");
5886
</script>

html/user-activation/resources/opened-window.html

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)