| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Ensure fragment is kept across redirects</title> |
| <meta name="timeout" content="long"> |
| <link rel=help href="https://www.w3.org/TR/cuap/#uri"> |
| <link rel=help href="https://tools.ietf.org/html/rfc7231#section-7.1.2"> |
| <link rel=help href="https://bugs.webkit.org/show_bug.cgi?id=158420"> |
| <link rel=help href="https://bugs.webkit.org/show_bug.cgi?id=24175"> |
| <script src="/common/get-host-info.sub.js"></script> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script> |
| let frame; |
| let message; |
| |
| const HTTP_SAME_ORIGIN = "HTTP - SameOrigin"; |
| const HTTPS_SAME_ORIGIN = "HTTPS - SameOrigin"; |
| const HTTP_CROSS_ORIGIN = "HTTP - CrossOrigin"; |
| const HTTPS_CROSS_ORIGIN = "HTTPS - CrossOrigin"; |
| |
| function messageReceived(f) { |
| return new Promise((resolve) => { |
| window.addEventListener("message", (e) => { |
| message = e.data; |
| resolve(); |
| }, {once: true}); |
| f(); |
| }); |
| } |
| |
| function getHostname(navigation_type) { |
| switch (navigation_type) { |
| case HTTP_SAME_ORIGIN: |
| return get_host_info().HTTP_ORIGIN; |
| case HTTPS_SAME_ORIGIN: |
| return get_host_info().HTTPS_ORIGIN |
| case HTTP_CROSS_ORIGIN: |
| return get_host_info().HTTP_REMOTE_ORIGIN |
| case HTTPS_CROSS_ORIGIN: |
| return get_host_info().HTTPS_REMOTE_ORIGIN |
| } |
| |
| return 'nonexistent' |
| } |
| |
| // Turns |path| from a relative to this file path into a full URL, with |
| // the host being determined by one of the ORIGIN strings above. |
| function relativePathToFull(path, navigation_type) { |
| let host = getHostname(navigation_type); |
| |
| const pathname = window.location.pathname; |
| const base_path = pathname.substring(0, pathname.lastIndexOf('/') + 1); |
| |
| return host + base_path + path; |
| } |
| |
| // Constructs a URL to redirect.py which will respond with the given |
| // redirect status |code| to the provided |to_url|. Optionally adds on a |
| // |fragment|, if provided, to use in the initial request to redirect.py |
| function buildRedirectUrl(to_url, code, fragment) { |
| to_url = encodeURIComponent(to_url); |
| let dest = `/common/redirect.py?status=${code}&location=${to_url}`; |
| if (fragment) |
| dest = dest + '#' + fragment; |
| return dest; |
| } |
| |
| async function redirectTo(url, code, navigation_type, fragment) { |
| const dest = buildRedirectUrl(url, code, fragment); |
| await messageReceived( () => { |
| frame.contentWindow.location = getHostname(navigation_type) + dest; |
| }); |
| } |
| |
| async function doubleRedirectTo(url, code, navigation_type, fragment, intermediate_fragment) { |
| const second_redirection = buildRedirectUrl(url, code, intermediate_fragment); |
| const first_redirection = buildRedirectUrl(second_redirection, code, fragment); |
| await messageReceived( () => { |
| frame.contentWindow.location = getHostname(navigation_type) + first_redirection; |
| }); |
| } |
| |
| onload = () => { |
| frame = document.getElementById("frame"); |
| |
| // The tests in this file verify fragments are correctly propagated in |
| // a number of HTTP redirect scenarios. Each test is run for every |
| // relevant redirect status code. We also run each scenario under each |
| // combination of navigating to cross/same origin and using http/https. |
| const status_codes = [301, 302, 303, 307, 308]; |
| const navigation_types = [HTTP_SAME_ORIGIN, |
| HTTPS_SAME_ORIGIN, |
| HTTP_CROSS_ORIGIN, |
| HTTPS_CROSS_ORIGIN]; |
| |
| for (let navigation_type of navigation_types) { |
| // Navigate to a URL with a fragment. The URL redirects to a different |
| // page. Ensure we land on the redirected page with the fragment |
| // specified in the initial navigation's URL. |
| // |
| // Redirect chain: urlA#target -> urlB |
| // |
| for (let code of status_codes) { |
| promise_test(async () => { |
| const to_url = relativePathToFull('resources/destination.html', navigation_type); |
| await redirectTo(to_url, code, navigation_type, "target"); |
| assert_true(message.url.endsWith('#target')); |
| assert_equals(message.scrollY, 2000, "scrolls to fragment from initial navigation."); |
| }, `[${navigation_type}] Preserve fragment in ${code} redirect`); |
| } |
| |
| // Navigate to a URL with a fragment. The URL redirects to a different |
| // URL that also contains a fragment. Ensure we land on the redirected |
| // page using the fragment specified in the redirect response and not |
| // the one in the initial navigation. |
| // |
| // Redirect chain: urlA#target -> urlB#fromRedirect |
| // |
| for (let code of status_codes) { |
| promise_test(async () => { |
| const to_url = relativePathToFull('resources/destination.html#fromRedirect', navigation_type); |
| await redirectTo(to_url, code, navigation_type, "target"); |
| assert_true(message.url.endsWith('#fromRedirect'), `Unexpected fragment: ${message.url}`); |
| assert_equals(message.scrollY, 4000, "scrolls to fragment from redirect."); |
| }, `[${navigation_type}] Redirect URL fragment takes precedence in ${code} redirect`); |
| } |
| |
| // Perform two redirects. The initial navigation has a fragment and |
| // will redirect to a URL that also responds with a redirect. Ensure we |
| // land on the final page with the fragment from the original |
| // navigation. |
| // |
| // Redirect chain: urlA#target -> urlB -> urlC |
| // |
| for (let code of status_codes) { |
| promise_test(async () => { |
| const to_url = relativePathToFull('resources/destination.html', navigation_type); |
| await doubleRedirectTo(to_url, code, navigation_type, "target"); |
| assert_true(message.url.endsWith('#target'), `Unexpected fragment: ${message.url}`); |
| assert_equals(message.scrollY, 2000, "scrolls to fragment from initial navigation."); |
| }, `[${navigation_type}] Preserve fragment in multiple ${code} redirects`); |
| } |
| |
| // Perform two redirects. The initial navigation has a fragment and |
| // will redirect to a URL that also responds with a redirect. The |
| // second redirection to the final page also has a fragment. Ensure we |
| // land on the final page with the fragment from the redirection |
| // response URL. |
| // |
| // Redirect chain: urlA#target -> urlB -> urlC#fromRedirect |
| // |
| for (let code of status_codes) { |
| promise_test(async () => { |
| const to_url = relativePathToFull('resources/destination.html#fromRedirect', navigation_type); |
| await doubleRedirectTo(to_url, code, navigation_type, "target"); |
| assert_true(message.url.endsWith('#fromRedirect'), `Unexpected fragment: ${message.url}`); |
| assert_equals(message.scrollY, 4000, "scrolls to fragment from redirect."); |
| }, `[${navigation_type}] Destination URL fragment takes precedence in multiple ${code} redirects`); |
| } |
| |
| // Perform two redirects. The initial navigation has a fragment and |
| // will redirect to a URL that also responds with a redirect. This |
| // time, both redirect response have a fragment. Ensure we land on the |
| // final page with the fragment from the last redirection response URL. |
| // |
| // Redirect chain: urlA#target -> urlB#intermediate -> urlC#fromRedirect |
| // |
| for (let code of status_codes) { |
| promise_test(async () => { |
| const to_url = relativePathToFull('resources/destination.html#fromRedirect', navigation_type); |
| await doubleRedirectTo(to_url, code, navigation_type, "target", "intermediate"); |
| assert_true(message.url.endsWith('#fromRedirect'), `Unexpected fragment: ${message.url}`); |
| assert_equals(message.scrollY, 4000, "scrolls to fragment from redirect."); |
| }, `[${navigation_type}] Final redirect fragment takes precedence over intermediate in multiple ${code} redirects`); |
| } |
| |
| // Perform two redirects. The initial navigation has a fragment and |
| // will redirect to a URL that also responds with a redirect. The first |
| // redirect response has a fragment but the second doesn't. Ensure we |
| // land on the final page with the fragment from the first redirection |
| // response URL. |
| // |
| // Redirect chain: urlA#target -> urlB#fromRedirect -> urlC |
| // |
| for (let code of status_codes) { |
| promise_test(async () => { |
| const to_url = relativePathToFull('resources/destination.html', navigation_type); |
| await doubleRedirectTo(to_url, code, navigation_type, "target", "fromRedirect"); |
| assert_true(message.url.endsWith('#fromRedirect'), `Unexpected fragment: ${message.url}`); |
| assert_equals(message.scrollY, 4000, "scrolls to fragment from redirect."); |
| }, `[${navigation_type}] Preserve intermediate fragment in multiple ${code} redirects`); |
| } |
| } |
| } |
| </script> |
| </head> |
| <body> |
| <iframe id="frame" src=""></iframe> |
| </body> |
| </html> |