0

Fenced frame: Set referrer header for all event-level beacons.

A previous CL set the "Referer" header for reportEvent() beacons to the
frame's origin. This CL sets that header for automatic beacons as well,
and puts the change behind a new feature flag. Automatic beacons from
component ads will not have their "Referer" header set to the frames
origin for privacy reasons, and instead will be set to the root ad
frame's origin. To track this, ad component configs will now store the
origin of the root ad frame's config on the browser side.

This CL also updates WPTs and other tests to account for the changes.

Previous CL:
https://chromium-review.googlesource.com/c/chromium/src/+/5551871

Design Doc:
https://docs.google.com/document/d/1gKRZ9g_X5HCZbW__GinViwcC8iWEai_kBXsiJCXrt80/edit?usp=sharing

Change-Id: Idfaeee92ad5bf471b34002a6a701290d1ae31cf1
Bug: 341884774
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5544804
Commit-Queue: Liam Brady <lbrady@google.com>
Reviewed-by: Garrett Tanzer <gtanzer@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1310677}
This commit is contained in:
Liam Brady
2024-06-05 16:35:34 +00:00
committed by Chromium LUCI CQ
parent 424ce61433
commit 27da6a274b
39 changed files with 240 additions and 106 deletions
content
testing/variations
third_party/blink

@ -2248,7 +2248,8 @@ class FencedFrameParameterizedBrowserTest : public FencedFrameBrowserTestBase {
{blink::features::kFencedFramesLocalUnpartitionedDataAccess, {}},
{blink::features::
kFencedFramesCrossOriginEventReportingUnlabeledTraffic,
{}}},
{}},
{blink::features::kFencedFramesReportEventHeaderChanges, {}}},
{/* disabled_features */});
}

@ -384,7 +384,8 @@ bool FencedFrameReporter::SendReport(
std::string& error_message,
blink::mojom::ConsoleMessageLevel& console_message_level,
int initiator_frame_tree_node_id,
std::optional<int64_t> navigation_id) {
std::optional<int64_t> navigation_id,
std::optional<url::Origin> ad_root_origin) {
DCHECK(request_initiator_frame);
if (reporting_destination ==
@ -444,9 +445,20 @@ bool FencedFrameReporter::SendReport(
}
}
const url::Origin& request_initiator =
url::Origin request_initiator =
request_initiator_frame->GetLastCommittedOrigin();
// |ad_root_origin| is only set for ad components. Automatic beacons sent from
// ad components should not have the ad component's origin present in the
// beacon, as that is additional information that should not be made available
// to the reporting server. Set it to the root ad frame's origin instead.
if (base::FeatureList::IsEnabled(
blink::features::kFencedFramesReportEventHeaderChanges) &&
ad_root_origin.has_value()) {
CHECK(absl::holds_alternative<AutomaticBeaconEvent>(event_variant));
request_initiator = ad_root_origin.value();
}
// If the reporting URL map is pending, queue the event.
NotifyIsBeaconQueued(
event_variant,
@ -678,10 +690,13 @@ bool FencedFrameReporter::SendReportInternal(
} else {
request->method = net::HttpRequestHeaders::kPostMethod;
}
if (absl::holds_alternative<DestinationEnumEvent>(event_variant) ||
absl::holds_alternative<DestinationURLEvent>(event_variant)) {
request->referrer = request_initiator.GetURL();
if (base::FeatureList::IsEnabled(
blink::features::kFencedFramesReportEventHeaderChanges)) {
// For automatic beacons initiating from component ad frames, the
// request_initiator will have already been set to the root ad frame's
// origin by this point.
request->referrer_policy = net::ReferrerPolicy::ORIGIN;
request->referrer = request_initiator.GetURL();
}
request->trusted_params = network::ResourceRequest::TrustedParams();
request->trusted_params->isolation_info =

@ -250,6 +250,8 @@ class CONTENT_EXPORT FencedFrameReporter
// will be set to the ID of the navigation request initiated from the fenced
// frame and targeting the new top-level frame. In all other cases (including
// the fence.reportEvent() case), the navigation id will be null.
// Note: `ad_root_origin` will only be set for automatic beacons originating
// from ad components.
bool SendReport(
const DestinationVariant& event_variant,
blink::FencedFrame::ReportingDestination reporting_destination,
@ -259,7 +261,8 @@ class CONTENT_EXPORT FencedFrameReporter
std::string& error_message,
blink::mojom::ConsoleMessageLevel& console_message_level,
int initiator_frame_tree_node_id = RenderFrameHost::kNoFrameTreeNodeId,
std::optional<int64_t> navigation_id = std::nullopt);
std::optional<int64_t> navigation_id = std::nullopt,
std::optional<url::Origin> ad_root_origin = std::nullopt);
// Called when a mapping for private aggregation requests of non-reserved
// event types is received. Currently it is only called inside

@ -104,8 +104,9 @@ class FencedFrameReporterTest : public RenderViewHostTestHarness {
public:
FencedFrameReporterTest() {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{blink::features::
kFencedFramesAutomaticBeaconCredentials},
/*enabled_features=*/
{blink::features::kFencedFramesAutomaticBeaconCredentials,
blink::features::kFencedFramesReportEventHeaderChanges},
/*disabled_features=*/{});
}
@ -131,21 +132,14 @@ class FencedFrameReporterTest : public RenderViewHostTestHarness {
void ValidateRequest(const network::ResourceRequest& request,
const GURL& expected_url,
const std::optional<std::string>& event_data,
bool should_have_referrer = true) {
const std::optional<std::string>& event_data) {
EXPECT_EQ(request.url, expected_url);
EXPECT_EQ(request.mode, network::mojom::RequestMode::kCors);
EXPECT_EQ(request.credentials_mode, network::mojom::CredentialsMode::kOmit);
EXPECT_TRUE(request.trusted_params->isolation_info.network_isolation_key()
.IsTransient());
if (should_have_referrer) {
EXPECT_EQ(request.referrer, main_frame_origin_.GetURL());
EXPECT_EQ(request.referrer_policy, net::ReferrerPolicy::ORIGIN);
} else {
EXPECT_EQ(request.referrer, GURL());
EXPECT_EQ(request.referrer_policy, net::ReferrerPolicy::NEVER_CLEAR);
}
EXPECT_EQ(request.referrer, main_frame_origin_.GetURL());
EXPECT_EQ(request.referrer_policy, net::ReferrerPolicy::ORIGIN);
// Checks specific to DestinationURL events.
if (!event_data.has_value()) {
@ -1576,7 +1570,7 @@ TEST_F(FencedFrameReporterTest, SendReportsRecordHistogramsAutomaticBeacon) {
console_message_level));
EXPECT_EQ(test_url_loader_factory_.NumPending(), 1);
ValidateRequest((*test_url_loader_factory_.pending_requests())[0].request,
report_destination3_, "event_data3", false);
report_destination3_, "event_data3");
test_url_loader_factory_.SimulateResponseForPendingRequest(
report_destination3_.spec(), "");
@ -1598,7 +1592,7 @@ TEST_F(FencedFrameReporterTest, SendReportsRecordHistogramsAutomaticBeacon) {
console_message_level));
EXPECT_EQ(test_url_loader_factory_.NumPending(), 1);
ValidateRequest((*test_url_loader_factory_.pending_requests())[0].request,
report_destination3_, "event_data3", false);
report_destination3_, "event_data3");
test_url_loader_factory_.SimulateResponseForPendingRequest(
report_destination3_.spec(), "", net::HTTP_NOT_FOUND);

@ -996,6 +996,18 @@ bool FrameTreeNode::IsInFencedFrameTree() const {
return fenced_frame_status_ != FencedFrameStatus::kNotNestedInFencedFrame;
}
FrameTreeNode* FrameTreeNode::GetClosestAncestorWithFencedFrameProperties() {
FrameTreeNode* node = this;
while (node) {
if (node->fenced_frame_properties_.has_value()) {
return node;
}
node = node->parent() ? node->parent()->frame_tree_node() : nullptr;
}
return nullptr;
}
std::optional<FencedFrameProperties>& FrameTreeNode::GetFencedFrameProperties(
FencedFramePropertiesNodeSource node_source) {
if (node_source == FencedFramePropertiesNodeSource::kFrameTreeRoot) {
@ -1006,15 +1018,9 @@ std::optional<FencedFrameProperties>& FrameTreeNode::GetFencedFrameProperties(
// properties are obtained by a bottom-up traversal.
CHECK_EQ(node_source, FencedFramePropertiesNodeSource::kClosestAncestor);
FrameTreeNode* node = this;
while (node) {
if (node->fenced_frame_properties_.has_value()) {
return node->fenced_frame_properties_;
}
node = node->parent() ? node->parent()->frame_tree_node() : nullptr;
}
FrameTreeNode* node = GetClosestAncestorWithFencedFrameProperties();
return fenced_frame_properties_;
return node ? node->fenced_frame_properties_ : fenced_frame_properties_;
}
void FrameTreeNode::MaybeResetFencedFrameAutomaticBeaconReportEventData(

@ -598,6 +598,11 @@ class CONTENT_EXPORT FrameTreeNode : public RenderFrameHostOwner {
FencedFramePropertiesNodeSource node_source =
FencedFramePropertiesNodeSource::kClosestAncestor);
// Helper function for getting the FrameTreeNode that houses the relevant
// FencedFrameProperties when GetFencedFrameProperties() is called with
// kClosestAncestor.
FrameTreeNode* GetClosestAncestorWithFencedFrameProperties();
bool HasFencedFrameProperties() const {
return fenced_frame_properties_.has_value();
}

@ -9263,12 +9263,27 @@ void RenderFrameHostImpl::SendFencedFrameReportingBeaconInternal(
return;
}
// Automatic beacons that originate from component ads shouldn't expose the ad
// component's origin in the beacon. Instead, use the origin of the ad frame
// root.
std::optional<url::Origin> ad_root_origin = std::nullopt;
if (frame_tree_node_->GetFencedFrameProperties()->is_ad_component()) {
FrameTreeNode* ad_component_root =
frame_tree_node_->GetClosestAncestorWithFencedFrameProperties();
FrameTreeNode* ad_root =
ad_component_root->GetParentOrOuterDocument()
->frame_tree_node()
->GetClosestAncestorWithFencedFrameProperties();
ad_root_origin = ad_root->current_frame_host()->GetLastCommittedOrigin();
}
if (!frame_tree_node_->GetFencedFrameProperties()
->fenced_frame_reporter()
->SendReport(
event_variant, destination, /*request_initiator_frame=*/this,
fenced_frame_data->features(), error_message,
console_message_level, GetFrameTreeNodeId(), navigation_id)) {
->SendReport(event_variant, destination,
/*request_initiator_frame=*/this,
fenced_frame_data->features(), error_message,
console_message_level, GetFrameTreeNodeId(),
navigation_id, ad_root_origin)) {
AddMessageToConsole(console_message_level, error_message);
}
}

@ -56,7 +56,8 @@ FencedFrameTestHelper::FencedFrameTestHelper() {
{blink::features::kFencedFramesM120FeaturesPart2, {}},
{blink::features::kFencedFramesLocalUnpartitionedDataAccess, {}},
{blink::features::kFencedFramesCrossOriginEventReportingUnlabeledTraffic,
{}}},
{}},
{blink::features::kFencedFramesReportEventHeaderChanges, {}}},
{/* disabled_features */});
}

@ -8905,6 +8905,26 @@
]
}
],
"FencedFramesEnableReportEventHeaderChanges": [
{
"platforms": [
"android",
"chromeos",
"chromeos_lacros",
"linux",
"mac",
"windows"
],
"experiments": [
{
"name": "Enabled",
"enable_features": [
"FencedFramesReportEventHeaderChanges"
]
}
]
}
],
"FenderAutoPreconnectLcpOrigins": [
{
"platforms": [

@ -903,6 +903,10 @@ BASE_FEATURE(kFencedFramesLocalUnpartitionedDataAccess,
"FencedFramesLocalUnpartitionedDataAccess",
base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE(kFencedFramesReportEventHeaderChanges,
"FencedFramesReportEventHeaderChanges",
base::FEATURE_DISABLED_BY_DEFAULT);
// Controls access to an API to exempt certain URLs from fenced frame
// network revocation to facilitate testing.
BASE_FEATURE(kExemptUrlFromNetworkRevocationForTesting,

@ -464,6 +464,7 @@ BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
kFencedFramesLocalUnpartitionedDataAccess);
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kFencedFramesReportEventHeaderChanges);
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
kExemptUrlFromNetworkRevocationForTesting);

@ -1811,7 +1811,7 @@
"external/wpt/shared-storage",
"http/tests/inspector-protocol/shared-storage"
],
"args": ["--enable-features=SharedStorageAPI,FencedFrames:implementation_type/mparch,PrivacySandboxAdsAPIsOverride,FencedFramesAPIChanges,FencedFramesDefaultMode,SharedStorageAPIM118,SharedStorageAPIM125,SharedStorageAPIEnableWALForDatabase,FencedFramesEnforceFocus",
"args": ["--enable-features=SharedStorageAPI,FencedFrames:implementation_type/mparch,PrivacySandboxAdsAPIsOverride,FencedFramesAPIChanges,FencedFramesDefaultMode,SharedStorageAPIM118,SharedStorageAPIM125,SharedStorageAPIEnableWALForDatabase,FencedFramesEnforceFocus,FencedFramesReportEventHeaderChanges",
"--disable-threaded-compositing", "--disable-threaded-animation"],
"expires": "Jul 31, 2024"
},
@ -1858,7 +1858,7 @@
"external/wpt/fetch/private-network-access/fenced-frame.tentative.https.window.js",
"external/wpt/fetch/private-network-access/fenced-frame-no-preflight-required.tentative.https.window.js"
],
"args": ["--enable-features=PrivacySandboxAdsAPIsOverride,Fledge,AdInterestGroupAPI,FencedFramesEnforceFocus,FencedFramesLocalUnpartitionedDataAccess,ExemptUrlFromNetworkRevocationForTesting,FencedFramesCrossOriginEventReportingAllTraffic",
"args": ["--enable-features=PrivacySandboxAdsAPIsOverride,Fledge,AdInterestGroupAPI,FencedFramesEnforceFocus,FencedFramesLocalUnpartitionedDataAccess,ExemptUrlFromNetworkRevocationForTesting,FencedFramesCrossOriginEventReportingAllTraffic,FencedFramesReportEventHeaderChanges",
"--enable-blink-features=FencedFramesDefaultMode",
"--disable-threaded-compositing", "--disable-threaded-animation"],
"expires": "May 1, 2025"

@ -52,7 +52,8 @@ promise_test(async(t) => {
.pointerUp()
.send();
await verifyBeaconData(beacon_event.eventType, beacon_event.eventData);
await verifyBeaconData(beacon_event.eventType, beacon_event.eventData,
get_host_info().HTTPS_REMOTE_ORIGIN);
// Leaving this fenced frame around for subsequent tests can lead to
// flakiness.

@ -49,8 +49,10 @@ promise_test(async(t) => {
.pointerUp()
.send();
await verifyBeaconData(start_event.eventType, start_event.eventData);
await verifyBeaconData(commit_event.eventType, commit_event.eventData);
await verifyBeaconData(start_event.eventType, start_event.eventData,
get_host_info().HTTPS_REMOTE_ORIGIN);
await verifyBeaconData(commit_event.eventType, commit_event.eventData,
get_host_info().HTTPS_REMOTE_ORIGIN);
// Leaving this fenced frame around for subsequent tests can lead to
// flakiness.

@ -18,6 +18,7 @@ promise_test(async(t) => {
const fencedframe = await attachFencedFrameContext({
generator_api: 'fledge',
register_beacon: true,
component_origin: get_host_info().HTTPS_REMOTE_ORIGIN,
num_components: 1,
// These headers will also be given to the component ad.
headers: [["Allow-Fenced-Frame-Automatic-Beacons", "true"]]
@ -49,6 +50,7 @@ promise_test(async(t) => {
.send();
// The component frame should not use the data set in its parent.
// The referrer header should be set to the root ad frame's origin.
await verifyBeaconData(beacon_event.eventType, "<No data>");
}, 'Automatic beacon in an ad component should send without data with a ' +
'header opt-in.');

@ -35,7 +35,8 @@ promise_test(async(t) => {
.pointerUp()
.send();
await verifyBeaconData(beacon_event.eventType, "<No data>");
await verifyBeaconData(beacon_event.eventType, "<No data>",
get_host_info().HTTPS_REMOTE_ORIGIN);
}, 'Automatic beacon in a cross-origin subframe should send without data ' +
'when crossOrigin=false.');
</script>

@ -40,8 +40,10 @@ promise_test(async(t) => {
.pointerUp()
.send();
await verifyBeaconData("reserved.top_navigation_start", "<No data>");
await verifyBeaconData("reserved.top_navigation_commit", "<No data>");
await verifyBeaconData("reserved.top_navigation_start", "<No data>",
get_host_info().HTTPS_REMOTE_ORIGIN);
await verifyBeaconData("reserved.top_navigation_commit", "<No data>",
get_host_info().HTTPS_REMOTE_ORIGIN);
}, 'Automatic beacon in a cross-origin subframe with no beacon data set');
</script>
</body>

@ -37,8 +37,8 @@ promise_test(async(t) => {
.pointerUp()
.send();
await verifyBeaconData(beacon_event.eventType, beacon_event.eventData, false,
t);
await verifyBeaconData(beacon_event.eventType, beacon_event.eventData,
get_host_info().HTTPS_REMOTE_ORIGIN, false, t);
}, 'Automatic beacon in a cross-origin subframe with no opt-in header should ' +
'not send.');
</script>

@ -37,7 +37,8 @@ promise_test(async (t) => {
// An automatic beacon should be sent out, but no data should be sent as part
// of the beacon because the "buyer" destination was not specified in
// setReportEventDataForAutomaticBeacons().
await verifyBeaconData(beacon_event.eventType, "<No data>");
await verifyBeaconData(beacon_event.eventType, "<No data>",
get_host_info().HTTPS_REMOTE_ORIGIN);
}, "Set and trigger an automatic beacon with no destination specified");
</script>

@ -40,8 +40,8 @@ promise_test(async (t) => {
.pointerDown()
.pointerUp()
.send();
await verifyBeaconData("reserved.top_navigation_start", "<No data>", false,
t);
await verifyBeaconData("reserved.top_navigation_start", "<No data>", null,
false, t);
}, "Automatic beacons will not send if the document does not opt in.");
</script>

@ -37,8 +37,10 @@ promise_test(async(t) => {
.pointerUp()
.send();
await verifyBeaconData(start_event.eventType, start_event.eventData);
await verifyBeaconData(commit_event.eventType, commit_event.eventData);
await verifyBeaconData(start_event.eventType, start_event.eventData,
get_host_info().HTTPS_REMOTE_ORIGIN);
await verifyBeaconData(commit_event.eventType, commit_event.eventData,
get_host_info().HTTPS_REMOTE_ORIGIN);
}, 'Set and trigger an automatic beacon in a click handler for SharedStorage');
</script>

@ -34,7 +34,8 @@ promise_test(async(t) => {
.pointerDown()
.pointerUp()
.send();
await verifyBeaconData(beacon_event.eventType, beacon_event.eventData);
await verifyBeaconData(beacon_event.eventType, beacon_event.eventData,
get_host_info().HTTPS_REMOTE_ORIGIN);
// The second click should not have any associated automatic beacon info, so
// no beacon should be sent.
@ -44,8 +45,8 @@ promise_test(async(t) => {
.pointerDown()
.pointerUp()
.send();
await verifyBeaconData(beacon_event.eventType, beacon_event.eventData, false,
t);
await verifyBeaconData(beacon_event.eventType, beacon_event.eventData,
get_host_info().HTTPS_REMOTE_ORIGIN, false, t);
}, 'Set expiring automatic beacon but trigger two events in a click handler');
</script>

@ -33,7 +33,8 @@ promise_test(async(t) => {
.pointerDown()
.pointerUp()
.send();
await verifyBeaconData(beacon_event.eventType, beacon_event.eventData);
await verifyBeaconData(beacon_event.eventType, beacon_event.eventData,
get_host_info().HTTPS_REMOTE_ORIGIN);
// The second click should still have associated automatic beacon data, and a
// beacon should be sent.
@ -41,7 +42,8 @@ promise_test(async(t) => {
.pointerDown()
.pointerUp()
.send();
await verifyBeaconData(beacon_event.eventType, beacon_event.eventData);
await verifyBeaconData(beacon_event.eventType, beacon_event.eventData,
get_host_info().HTTPS_REMOTE_ORIGIN);
}, 'Set persisting automatic beacon but trigger two events in a click handler');
</script>

@ -43,8 +43,8 @@ promise_test(async(t) => {
}
window.fence.reportEvent(destination_url_event);
});
await verifyBeaconData("click", "enum", false, t);
await verifyBeaconData("url", "<No data>", false, t);
await verifyBeaconData("click", "enum", null, false, t);
await verifyBeaconData("url", "<No data>", null, false, t);
}, 'Cross-origin window.fence.reportEvent without embedder opt-in');
</script>
</body>

@ -39,8 +39,8 @@ promise_test(async(t) => {
});
// Check that both the destination enum and destination URL events were
// reported.
await verifyBeaconData("click", "enum", false, t);
await verifyBeaconData("url", "<No data>", false, t);
await verifyBeaconData("click", "enum", null, false, t);
await verifyBeaconData("url", "<No data>", null, false, t);
}, 'Cross-origin window.fence.reportEvent without subframe opt-in');
</script>
</body>

@ -43,8 +43,8 @@ promise_test(async(t) => {
}
window.fence.reportEvent(destination_url_event);
});
await verifyBeaconData("click", "enum", false, t);
await verifyBeaconData("url", "<No data>", false, t);
await verifyBeaconData("click", "enum", null, false, t);
await verifyBeaconData("url", "<No data>", null, false, t);
}, 'Cross-origin window.fence.reportEvent without embedder opt-in');
</script>
</body>

@ -39,8 +39,8 @@ promise_test(async(t) => {
});
// Check that both the destination enum and destination URL events were
// reported.
await verifyBeaconData("click", "enum", false, t);
await verifyBeaconData("url", "<No data>", false, t);
await verifyBeaconData("click", "enum", null, false, t);
await verifyBeaconData("url", "<No data>", null, false, t);
}, 'Cross-origin window.fence.reportEvent without subframe opt-in');
</script>
</body>

@ -16,11 +16,12 @@ promise_test(async(t) => {
headers: [[
'Allow-Cross-Origin-Event-Reporting', '?1'
]],
register_beacon: true
register_beacon: true,
origin: 'https://{{hosts[alt][]}}:{{ports[https][0]}}',
});
await fencedframe.execute(async () => {
const iframe = await attachIFrameContext({
origin: get_host_info().HTTPS_REMOTE_ORIGIN,
origin: 'https://{{hosts[alt][]}}:{{ports[https][1]}}'
});
await iframe.execute(() => {
const destination_url = new URL(BEACON_URL + "?type=url",
@ -39,8 +40,24 @@ promise_test(async(t) => {
});
// Check that both the destination enum and destination URL events were
// reported.
await nextBeacon("click", "enum");
await nextBeacon("url", "<No data>");
const [enum_origin, enum_referrer] = await nextBeacon("click", "enum")
.then(data => data.split(','));
const [url_origin, url_referrer] = await nextBeacon("url", "<No data>")
.then(data => data.split(','));
// Check the "Origin" headers are set to the origin that supplied the
// reporting URL. For destination enum events, that's the origin of the
// worklet. For destination URL events, that's the origin of the iframe.
assert_equals(enum_origin, get_host_info().HTTPS_ORIGIN,
'The enum origin should be correctly set.');
assert_equals(url_origin, 'https://{{hosts[alt][]}}:{{ports[https][1]}}',
'The url origin should be correctly set.');
assert_equals(enum_referrer,
'https://{{hosts[alt][]}}:{{ports[https][1]}}/',
'The enum referrer should be correctly set.');
assert_equals(url_referrer, 'https://{{hosts[alt][]}}:{{ports[https][1]}}/',
'The url referrer should be correctly set.');
}, 'window.fence.reportEvent from a cross-origin subframe');
</script>
</body>

@ -39,8 +39,8 @@ promise_test(async(t) => {
});
// Check that both the destination enum and destination URL events were
// reported.
await verifyBeaconData("click", "enum", false, t);
await verifyBeaconData("url", "<No data>", false, t);
await verifyBeaconData("click", "enum", null, false, t);
await verifyBeaconData("url", "<No data>", null, false, t);
}, 'window.fence.reportEvent should not work in a nested fenced frame');
</script>
</body>

@ -31,12 +31,22 @@
window.fence.reportEvent(destination_url_event);
});
let enum_data = await nextBeacon('click', 'enum');
assert_equals(enum_data, location.origin);
const [enum_origin, enum_referrer] = await nextBeacon("click", "enum")
.then(data => data.split(','));
const [url_origin, url_referrer] = await nextBeacon("url", "<No data>")
.then(data => data.split(','));
let url_data = await nextBeacon('url', '<No data>');
assert_equals(url_data, '<No data>');
assert_equals(enum_origin, location.origin,
'The enum origin should be correctly set.');
// GET requests do not set an 'Origin' header if same-origin to the request's
// destination.
assert_equals(url_origin, '<No data>',
'The url origin should be correctly set.');
assert_equals(enum_referrer, location.origin + "/",
'The enum referrer should be correctly set.');
assert_equals(url_referrer, location.origin + "/",
'The url referrer should be correctly set.');
}, 'Test that window.fence.reportEvent() succeeds in a fenced frame.');
</script>
</body>

@ -84,20 +84,31 @@ async function setupAutomaticBeacon(
// Checks if an automatic beacon of type `event_type` with contents `event_data`
// was sent out or not.
// event_type: The automatic beacon type to check.
// event_data: The automatic beacon data to check.
// expected_success: Whether we expect the automatic beacon to be sent.
// t: The WPT's test object. Only required if
// event_type: The automatic beacon type to check.
// event_data: The automatic beacon data to check.
// expected_referrer: The expected referrer header, if different from origin.
// expected_success: Whether we expect the automatic beacon to be sent.
// t: The WPT's test object. Only required if
// expected_success = false.
async function verifyBeaconData(
event_type, event_data, expected_success = true, t) {
event_type, event_data, expected_referrer = null, expected_success = true,
t) {
if (expected_success) {
const beacon_initiator_origin = await nextBeacon(event_type, event_data);
assert_equals(beacon_initiator_origin, get_host_info().HTTPS_ORIGIN);
const data = await nextBeacon(event_type, event_data);
const [beacon_initiator_origin, beacon_referrer] =
data.split(",");
assert_equals(beacon_initiator_origin, get_host_info().HTTPS_ORIGIN,
"The initiator origin should be set as expected.");
// The Referer header has a trailing '/' appended to the URL.
assert_equals(beacon_referrer,
(expected_referrer ? expected_referrer :
get_host_info().HTTPS_ORIGIN) + "/",
"The beacon referrer should be set as expected.");
} else {
const timeout = new Promise(r => t.step_timeout(r, 1000));
const result =
await Promise.race([nextBeacon(event_type, event_data), timeout]);
assert_true(typeof result === 'undefined');
assert_true(typeof result === 'undefined',
"The beacon should not have sent.");
}
}

@ -15,7 +15,8 @@
const beacon_data = "This is the beacon data!";
const beacon_initiator_origin = await nextBeacon(
"reserved.top_navigation_commit", beacon_data);
assert_equals(beacon_initiator_origin, get_host_info().HTTPS_ORIGIN);
assert_equals(beacon_initiator_origin, get_host_info().HTTPS_ORIGIN + "," +
get_host_info().HTTPS_REMOTE_ORIGIN + "/");
});
</script>
</body>

@ -43,14 +43,18 @@ def main(request, response):
# (either through reportEvent() or through an automatic beacon).
if request.method == "POST" and event_type:
request_body = request.body or NO_DATA_STRING
request_headers = request.headers.get("Origin") or NO_DATA_STRING
request_origin = request.headers.get("Origin") or NO_DATA_STRING
request_referrer = request.headers.get("Referer") or NO_DATA_STRING
stash.put(string_to_uuid(event_type + request_body),
request_headers)
(request_origin + b"," + request_referrer))
return (200, [], b"")
# GET requests without an 'expected_body' parameter imply they were sent
# as a destination URL reporting beacon.
if request.method == "GET" and event_type:
stash.put(string_to_uuid(event_type + NO_DATA_STRING), NO_DATA_STRING)
request_origin = request.headers.get("Origin") or NO_DATA_STRING
request_referrer = request.headers.get("Referer") or NO_DATA_STRING
stash.put(string_to_uuid(event_type + NO_DATA_STRING),
(request_origin + b"," + request_referrer))
return (200, [], b"")
return (400, [], u"")

@ -283,13 +283,13 @@ function attachContext(object_constructor, html, headers, origin) {
async function attachOpaqueContext(
generator_api, resolve_to_config, ad_with_size, requested_size,
register_beacon, object_constructor, html, headers, origin,
num_components) {
component_origin, num_components) {
const [uuid, url] = generateRemoteContextURL(headers, origin);
let components_list = [];
for (let i = 0; i < num_components; i++) {
let [component_uuid, component_url] =
generateRemoteContextURL(headers, origin);
generateRemoteContextURL(headers, component_origin);
// This field will be read by attachComponentFrameContext() in order to
// know what uuid to point to when building the remote context.
html += '<input type=\'hidden\' id=\'component_uuid_' + i + '\' value=\'' +
@ -309,13 +309,14 @@ async function attachOpaqueContext(
function attachPotentiallyOpaqueContext(
generator_api, resolve_to_config, ad_with_size, requested_size,
register_beacon, frame_constructor, html, headers, origin, num_components) {
register_beacon, frame_constructor, html, headers, origin,
component_origin, num_components) {
generator_api = generator_api.toLowerCase();
if (generator_api == 'fledge' || generator_api == 'sharedstorage') {
return attachOpaqueContext(
generator_api, resolve_to_config, ad_with_size, requested_size,
register_beacon, frame_constructor, html, headers, origin,
num_components);
component_origin, num_components);
} else {
return attachContext(frame_constructor, html, headers, origin);
}
@ -324,7 +325,7 @@ function attachPotentiallyOpaqueContext(
function attachFrameContext(
element_name, generator_api, resolve_to_config, ad_with_size,
requested_size, register_beacon, html, headers, attributes, origin,
num_components) {
component_origin, num_components) {
frame_constructor = (id) => {
frame = document.createElement(element_name);
attributes.forEach(attribute => {
@ -344,7 +345,7 @@ function attachFrameContext(
return attachPotentiallyOpaqueContext(
generator_api, resolve_to_config, ad_with_size, requested_size,
register_beacon, frame_constructor, html, headers, origin,
num_components);
component_origin, num_components);
}
// Performs a content-initiated navigation of a frame proxy. This navigated page
@ -420,12 +421,13 @@ function attachFencedFrameContext({
headers = [],
attributes = [],
origin = '',
component_origin = '',
num_components = 0
} = {}) {
return attachFrameContext(
'fencedframe', generator_api, resolve_to_config, ad_with_size,
requested_size, register_beacon, html, headers, attributes, origin,
num_components);
component_origin, num_components);
}
// Attach an iframe that waits for scripts to execute.
@ -437,12 +439,13 @@ function attachIFrameContext({
headers = [],
attributes = [],
origin = '',
component_origin = '',
num_components = 0
} = {}) {
return attachFrameContext(
'iframe', generator_api, resolve_to_config = false, ad_with_size = false,
requested_size = null, register_beacon, html, headers, attributes, origin,
num_components);
component_origin, num_components);
}
// Open a window that waits for scripts to execute.

@ -4,6 +4,7 @@ request headers: undefined
request data: dummy
requestExtraInfo has same requestId: true
requestExtraInfo has headers: true
requestExtraInfo referer: https://127.0.0.1:8443/
responseReceived has same requestId: true
responseReceived status: 200
loadingFinished has same requestId: true

@ -72,6 +72,8 @@
+ (request.requestId === requestExtraInfo.requestId));
testRunner.log('requestExtraInfo has headers: '
+ (Object.keys(requestExtraInfo.params.headers).length > 0));
testRunner.log('requestExtraInfo referer: '
+ requestExtraInfo.params.headers.Referer);
// The request should succeed with a 200 status code.
testRunner.log('responseReceived has same requestId: '

@ -19,14 +19,12 @@ import hashlib
NO_DATA_STRING = b"<No data>"
NOT_SET_STRING = b"<Not set>"
# The server stash requires a uuid to store data. Use a hash of the automatic
# beacon data as the uuid to store and retrieve the data.
def string_to_uuid(input):
hash_value = hashlib.md5(str(input).encode("UTF-8")).hexdigest()
return str(uuid.UUID(hex=hash_value))
def main(request, response):
stash = request.server.stash
event_type = request.GET.first(b"type", NO_DATA_STRING)
@ -46,15 +44,18 @@ def main(request, response):
# (either through reportEvent() or through an automatic beacon).
if request.method == "POST" and event_type:
request_body = request.body or NO_DATA_STRING
request_headers = request.headers.get("Origin") or NO_DATA_STRING
request_origin = request.headers.get("Origin") or NO_DATA_STRING
request_referrer = request.headers.get("Referer") or NO_DATA_STRING
stash.put(string_to_uuid(event_type + request_body),
request_headers)
(request_origin + b"," + request_referrer))
return (200, [], b"")
# GET requests without an 'expected_body' parameter imply they were sent
# as a destination URL reporting beacon.
if request.method == "GET" and event_type:
request_origin = request.headers.get("Origin") or NO_DATA_STRING
request_referrer = request.headers.get("Referer") or NO_DATA_STRING
stash.put(string_to_uuid(event_type + NO_DATA_STRING),
NO_DATA_STRING)
(request_origin + b"," + request_referrer))
return (200, [], b"")
return (400, [], u"")

@ -48,18 +48,22 @@ promise_test(async(t) => {
eventSender.mouseUp(0, [isMac ? 'metaKey' : 'ctrlKey']);
}, [new_url, beacon_data, beacon_type]);
const received_beacon_origin =
await nextBeacon(beacon_type, beacon_data);
const [received_beacon_origin, received_beacon_referrer] =
await nextBeacon(beacon_type, beacon_data).then(data => data.split(","));
assert_equals(received_beacon_origin, location.origin);
assert_equals(received_beacon_referrer, location.origin + "/");
// Also test automatic beacons with middle clicks
await fencedframe.execute(() => {
eventSender.mouseDown(1);
eventSender.mouseUp(1);
}, []);
const middle_click_nav_received_beacon_origin = await
nextBeacon(beacon_type, beacon_data);
const [middle_click_nav_received_beacon_origin,
middle_click_nav_received_beacon_referrer] = await
nextBeacon(beacon_type, beacon_data).then(data => data.split(","));
assert_equals(middle_click_nav_received_beacon_origin, location.origin);
assert_equals(middle_click_nav_received_beacon_referrer,
location.origin + "/");
}, 'Trigger an automatic beacon from ctrl+clicking a link.');
</script>

@ -19,14 +19,12 @@ import hashlib
NO_DATA_STRING = b"<No data>"
NOT_SET_STRING = b"<Not set>"
# The server stash requires a uuid to store data. Use a hash of the automatic
# beacon data as the uuid to store and retrieve the data.
def string_to_uuid(input):
hash_value = hashlib.md5(str(input).encode("UTF-8")).hexdigest()
return str(uuid.UUID(hex=hash_value))
def main(request, response):
stash = request.server.stash
event_type = request.GET.first(b"type", NO_DATA_STRING)
@ -46,15 +44,18 @@ def main(request, response):
# (either through reportEvent() or through an automatic beacon).
if request.method == "POST" and event_type:
request_body = request.body or NO_DATA_STRING
request_headers = request.headers.get("Origin") or NO_DATA_STRING
request_origin = request.headers.get("Origin") or NO_DATA_STRING
request_referrer = request.headers.get("Referer") or NO_DATA_STRING
stash.put(string_to_uuid(event_type + request_body),
request_headers)
(request_origin + b"," + request_referrer))
return (200, [], b"")
# GET requests without an 'expected_body' parameter imply they were sent
# as a destination URL reporting beacon.
if request.method == "GET" and event_type:
request_origin = request.headers.get("Origin") or NO_DATA_STRING
request_referrer = request.headers.get("Referer") or NO_DATA_STRING
stash.put(string_to_uuid(event_type + NO_DATA_STRING),
NO_DATA_STRING)
(request_origin + b"," + request_referrer))
return (200, [], b"")
return (400, [], u"")