Skip to content

Commit

Permalink
Ensure iframe requests include Referer when location.replace or locat…
Browse files Browse the repository at this point in the history
…ion.assign is called

https://bugs.webkit.org/show_bug.cgi?id=263072

Reviewed by Chris Dumez and Alex Christensen.

When the loader for a Frame (e.g, an iframe) has no referrer, then this change
causes the loader's referrer to be set to the URL of its parent Frame’s Document.

That in turn ensures the associated request is sent with a Referer header —
including in the case where a document calls location.replace or
location.assign on an iframe element — which makes the WebKit behavior
in this case interoperable with existing behavior in Gecko and Blink.

Otherwise, without this change, no Referer header is sent in the
associated request, which breaks interoperability with Gecko and Blink.

* LayoutTests/imported/w3c/web-platform-tests/html/browsers/history/the-location-interface/assign-replace-from-iframe-expected.txt: Added.
* LayoutTests/imported/w3c/web-platform-tests/html/browsers/history/the-location-interface/assign-replace-from-iframe.html: Added.
* LayoutTests/imported/w3c/web-platform-tests/html/browsers/history/the-location-interface/assign-replace-from-top-to-nested-iframe-expected.txt: Added.
* LayoutTests/imported/w3c/web-platform-tests/html/browsers/history/the-location-interface/assign-replace-from-top-to-nested-iframe.html: Added.
* LayoutTests/imported/w3c/web-platform-tests/html/browsers/history/the-location-interface/assign-with-nested-iframe-expected.txt: Added.
* LayoutTests/imported/w3c/web-platform-tests/html/browsers/history/the-location-interface/assign-with-nested-iframe.html: Added.
* LayoutTests/imported/w3c/web-platform-tests/html/browsers/history/the-location-interface/replace-with-nested-iframe-expected.txt: Added.
* LayoutTests/imported/w3c/web-platform-tests/html/browsers/history/the-location-interface/replace-with-nested-iframe.html: Added.
* LayoutTests/imported/w3c/web-platform-tests/html/browsers/history/the-location-interface/resources/iframe-contents.sub.html: Added.
* LayoutTests/imported/w3c/web-platform-tests/html/browsers/history/the-location-interface/resources/iframe-postmessage-to-parent-parent.sub.html: Added.
* LayoutTests/imported/w3c/web-platform-tests/html/browsers/history/the-location-interface/resources/iframe-with-iframe.html: Added.
* LayoutTests/imported/w3c/web-platform-tests/html/browsers/history/the-location-interface/resources/replace-or-assign-call-on-iframe.html: Added.
* LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/open-close/no_window_open_when_term_nesting_level_nonzero.window-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-expected.txt:
* Source/WebCore/page/LocalDOMWindow.cpp:
(WebCore::LocalDOMWindow::setLocation):

Canonical link: https://commits.webkit.org/270741@main
  • Loading branch information
sideshowbarker authored and Ahmad Saleem committed Nov 15, 2023
1 parent fcf4ebb commit 1350b59
Show file tree
Hide file tree
Showing 15 changed files with 189 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

PASS Browser sends Referer header in iframe request when location.replace is called from an iframe
PASS Browser sends Referer header in iframe request when location.assign is called from an iframe

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Referer with location.replace and location.assign</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<iframe src="/resources/blank.html" hidden></iframe>
<script>
async_test(function(t) {
function on_message(e) {
const referrer = e.data;
assert_equals(referrer, window.location.href);
t.done();
}
window.addEventListener('message', t.step_func(on_message), false);
document.querySelector("iframe").contentWindow.location.replace("resources/iframe-contents.sub.html?replace");
}, "Browser sends Referer header in iframe request when location.replace is called from an iframe");
async_test(function(t) {
function on_message(e) {
const referrer = e.data;
assert_equals(referrer, window.location.href);
t.done();
}
window.addEventListener('message', t.step_func(on_message), false);
document.querySelector("iframe").contentWindow.location.assign("resources/iframe-contents.sub.html?assign");
}, "Browser sends Referer header in iframe request when location.assign is called from an iframe");
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

PASS Browser sends Referer header in nested iframe request when location.replace is called on an iframe
PASS Browser sends Referer header in nested iframe request when location.assign is called on an iframe

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Referer with location.replace and location.assign with nested iframes</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<iframe src="resources/iframe-with-iframe.html" hidden></iframe>
<script>
const iframe = document.querySelector("iframe");
async_test(function(t) {
function on_message(e) {
const referrer = e.data;
assert_equals(referrer, iframe.contentWindow.location.href);
t.done();
}
window.addEventListener('message', t.step_func(on_message), false);
window.addEventListener('load', function () {
iframe.contentDocument.querySelector("iframe").contentWindow.location.replace("/resources/blank.html");
}, false);
}, "Browser sends Referer header in nested iframe request when location.replace is called on an iframe");
async_test(function(t) {
function on_message(e) {
const referrer = e.data;
assert_equals(referrer, iframe.contentWindow.location.href);
t.done();
}
window.addEventListener('message', t.step_func(on_message), false);
window.addEventListener('load', function () {
iframe.contentDocument.querySelector("iframe").contentWindow.location.replace("/resources/blank.html");
}, false);
}, "Browser sends Referer header in nested iframe request when location.assign is called on an iframe");
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

PASS Browser sends Referer header when location.assign is called in iframe document on another nested iframe element

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Referer with location.assign and nested frames</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<iframe src="resources/replace-or-assign-call-on-iframe.html?assign" hidden></iframe>
<script>
async_test(function(t) {
function on_message(e) {
const nestedIframeReferrer = e.data;
assert_equals(nestedIframeReferrer, document.querySelector("iframe").contentWindow.location.href);
t.done();
}
window.addEventListener('message', t.step_func(on_message), false);
}, "Browser sends Referer header when location.assign is called in iframe document on another nested iframe element");
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

PASS Browser sends Referer header when location.replace is called in iframe document on another nested iframe element

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Referer with location.replace and nested frames</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<iframe src="resources/replace-or-assign-call-on-iframe.html?replace" hidden></iframe>
<script>
async_test(function(t) {
function on_message(e) {
const nestedIframeReferrer = e.data;
assert_equals(nestedIframeReferrer, document.querySelector("iframe").contentWindow.location.href);
t.done();
}
window.addEventListener('message', t.step_func(on_message), false);
}, "Browser sends Referer header when location.replace is called in iframe document on another nested iframe element");
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Resource file for test of Referer with location.replace</title>
</head>
<body>
<div></div>
<script>
const referer = "{{header_or_default(referer, missing)}}"
window.parent.postMessage(referer);
document.querySelector("div").textContent = `Referer header: ${referer}`;
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Resource file for test of Referer with location.replace</title>
</head>
<body>
<div></div>
<script>
const referer = "{{header_or_default(referer, missing)}}"
window.parent.parent.postMessage(referer);
document.querySelector("div").textContent = `Referer header: ${referer}`;
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Resource file for test of Referer with location.replace and location.assign</title>
</head>
<body>
<iframe src="iframe-postmessage-to-parent-parent.sub.html"></iframe>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Referer with location.replace and location.assign</title>
</head>
<body>
<iframe src="/resources/blank.html" hidden></iframe>
<script>
window.addEventListener('message', function (e) {
const referrer = e.data;
window.parent.postMessage(referrer);
});
if (window.location.search === "?replace") {
document.querySelector("iframe").contentWindow.location.replace("iframe-contents.sub.html?replace");
} else if (window.location.search === "?assign") {
document.querySelector("iframe").contentWindow.location.assign("iframe-contents.sub.html?assign");
}
</script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ CONSOLE MESSAGE: Error: assert_equals: expected no popup during unload expected
Harness Error (FAIL), message = Error: assert_equals: expected no popup during unload expected null but got object "[object Window]"

PASS no popups with frame removal
FAIL no popups with frame navigation assert_equals: expected no popup during visibilitychange expected null but got object "[object Window]"
FAIL no popups with frame navigation assert_equals: expected no popup during beforeunload expected null but got object "[object Window]"
FAIL no popups from synchronously reachable window assert_equals: expected no popup during beforeunload expected null but got object "[object Window]"
FAIL no popups from another synchronously reachable window assert_equals: expected no popup during beforeunload expected null but got object "[object Window]"

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ CONSOLE MESSAGE: Unhandled Promise Rejection: Error
CONSOLE MESSAGE: Unhandled Promise Rejection: Error
CONSOLE MESSAGE: Unhandled Promise Rejection: Error
CONSOLE MESSAGE: Unhandled Promise Rejection: InvalidStateError: Cannot create an ImageBitmap from an empty buffer
CONSOLE MESSAGE: Unhandled Promise Rejection: InvalidStateError: Cannot create an ImageBitmap from an empty buffer

PASS unhandledrejection: from Promise.reject
PASS unhandledrejection: from a synchronous rejection in new Promise
Expand Down Expand Up @@ -63,5 +64,5 @@ PASS delayed handling: delaying handling rejected promise created from createIma
PASS mutationObserverMicrotask vs. queueTask ordering is not disturbed inside unhandledrejection events
FAIL queueTask ordering vs. the task queued for unhandled rejection notification (1) assert_array_equals: expected property 1 to be "queueTask" but got object "[object Promise]" (expected array [object "[object Promise]", "queueTask", object "[object Promise]"] got [object "[object Promise]", object "[object Promise]", "queueTask"])
FAIL queueTask ordering vs. the task queued for unhandled rejection notification (2) assert_array_equals: expected property 0 to be "queueTask" but got object "[object Promise]" (expected array ["queueTask", object "[object Promise]"] got [object "[object Promise]", "queueTask"])
FAIL rejectionhandled is dispatched from a queued task, and not immediately assert_array_equals: expected property 4 to be "handled" but got "task after catch" (expected array ["unhandled", "after catch", "catch", "task before catch", "handled", "task after catch"] got ["unhandled", "after catch", "catch", "task before catch", "task after catch", "handled"])
FAIL rejectionhandled is dispatched from a queued task, and not immediately assert_array_equals: expected property 3 to be "task before catch" but got "handled" (expected array ["unhandled", "after catch", "catch", "task before catch", "handled", "task after catch"] got ["unhandled", "after catch", "catch", "handled", "task before catch", "task after catch"])

6 changes: 6 additions & 0 deletions Source/WebCore/page/LocalDOMWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2476,6 +2476,12 @@ void LocalDOMWindow::setLocation(LocalDOMWindow& activeWindow, const URL& comple
if (completedURL.protocolIsJavaScript() && frameElement() && !frameElement()->protectedDocument()->checkedContentSecurityPolicy()->allowJavaScriptURLs(aboutBlankURL().string(), { }, completedURL.string(), frameElement()))
return;

RefPtr localParent = dynamicDowncast<LocalFrame>(frame->tree().parent());
// If the loader for activeWindow's frame (browsing context) has no outgoing referrer, set its outgoing referrer
// to the URL of its parent frame's Document.
if (RefPtr activeFrame = activeWindow.frame(); activeFrame && activeFrame->loader().outgoingReferrer().isEmpty() && localParent)
activeFrame->loader().setOutgoingReferrer(document()->completeURL(localParent->document()->url().strippedForUseAsReferrer()));

// We want a new history item if we are processing a user gesture.
LockHistory lockHistory = (locking != SetLocationLocking::LockHistoryBasedOnGestureState || !UserGestureIndicator::processingUserGesture()) ? LockHistory::Yes : LockHistory::No;
LockBackForwardList lockBackForwardList = (locking != SetLocationLocking::LockHistoryBasedOnGestureState) ? LockBackForwardList::Yes : LockBackForwardList::No;
Expand Down

0 comments on commit 1350b59

Please sign in to comment.