blob: 0590a64051fe2c5dc2419bfe135144a57a943254 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/performance_manager/v8_memory/web_memory_aggregator.h"
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include "base/optional.h"
#include "base/trace_event/traced_value.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/public/v8_memory/web_memory.h"
#include "components/performance_manager/v8_memory/v8_memory_test_helpers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace performance_manager {
namespace v8_memory {
namespace {
using AttributionScope = mojom::WebMemoryAttribution::Scope;
using NodeAggregationType = WebMemoryAggregator::NodeAggregationType;
using WebMemoryAggregatorTest = WebMemoryTestHarness;
struct ExpectedMemoryBreakdown {
WebMemoryTestHarness::Bytes bytes;
AttributionScope scope = AttributionScope::kWindow;
base::Optional<std::string> url;
base::Optional<std::string> id;
base::Optional<std::string> src;
ExpectedMemoryBreakdown() = default;
ExpectedMemoryBreakdown(
WebMemoryTestHarness::Bytes expected_bytes,
AttributionScope expected_scope,
base::Optional<std::string> expected_url = base::nullopt,
base::Optional<std::string> expected_id = base::nullopt,
base::Optional<std::string> expected_src = base::nullopt)
: bytes(expected_bytes),
scope(expected_scope),
url(std::move(expected_url)),
id(std::move(expected_id)),
src(std::move(expected_src)) {}
ExpectedMemoryBreakdown(const ExpectedMemoryBreakdown& other) = default;
ExpectedMemoryBreakdown& operator=(const ExpectedMemoryBreakdown& other) =
default;
};
mojom::WebMemoryMeasurementPtr CreateExpectedMemoryMeasurement(
const std::vector<ExpectedMemoryBreakdown>& breakdowns) {
auto expected_measurement = mojom::WebMemoryMeasurement::New();
for (const auto& breakdown : breakdowns) {
auto expected_breakdown = mojom::WebMemoryBreakdownEntry::New();
if (breakdown.bytes) {
expected_breakdown->memory = mojom::WebMemoryUsage::New();
expected_breakdown->memory->bytes = breakdown.bytes.value();
}
auto attribution = mojom::WebMemoryAttribution::New();
attribution->scope = breakdown.scope;
attribution->url = breakdown.url;
attribution->id = breakdown.id;
attribution->src = breakdown.src;
expected_breakdown->attribution.push_back(std::move(attribution));
expected_measurement->breakdown.push_back(std::move(expected_breakdown));
}
return expected_measurement;
}
// Abuse Mojo's trace integration to serialize a measurement to sorted JSON for
// string comparison. This gives failure messages that include the full
// measurement in JSON format and is easier than comparing every field of
// nested Mojo messages individually.
std::string MeasurementToJSON(
const mojom::WebMemoryMeasurementPtr& measurement) {
// Sort all arrays.
auto canonical_measurement = measurement->Clone();
for (const auto& breakdown_entry : canonical_measurement->breakdown) {
std::sort(breakdown_entry->attribution.begin(),
breakdown_entry->attribution.end());
}
std::sort(canonical_measurement->breakdown.begin(),
canonical_measurement->breakdown.end());
// Convert to JSON string.
base::trace_event::TracedValueJSON json_value;
canonical_measurement->AsValueInto(&json_value);
return json_value.ToJSON();
}
TEST_F(WebMemoryAggregatorTest, CreateBreakdownEntry) {
auto measurement = mojom::WebMemoryMeasurement::New();
auto* breakdown_with_no_url =
internal::CreateBreakdownEntry(AttributionScope::kCrossOriginAggregated,
base::nullopt, measurement.get());
auto* breakdown_with_url = internal::CreateBreakdownEntry(
AttributionScope::kWindow, "https://example.com", measurement.get());
auto* breakdown_with_empty_url = internal::CreateBreakdownEntry(
AttributionScope::kWindow, "", measurement.get());
// Ensure breakdowns were added to measurement.
EXPECT_EQ(measurement->breakdown.size(), 3U);
EXPECT_EQ(measurement->breakdown[0].get(), breakdown_with_no_url);
EXPECT_EQ(measurement->breakdown[1].get(), breakdown_with_url);
EXPECT_EQ(measurement->breakdown[2].get(), breakdown_with_empty_url);
// Can't use an initializer list because nullopt_t and
// base::Optional<std::string> are different types.
std::vector<base::Optional<std::string>> attributes;
attributes.push_back(base::nullopt);
attributes.push_back(base::make_optional("example_attr"));
attributes.push_back(base::make_optional(""));
for (const auto& attribute : attributes) {
SCOPED_TRACE(attribute.value_or("nullopt"));
// V8ContextTracker needs a parent frame to store attributes.
FrameNodeImpl* parent_frame =
attribute ? AddFrameNode("https://example.com", Bytes{1}, nullptr)
: nullptr;
FrameNodeImpl* frame = AddFrameNode("https://example.com", Bytes{1},
parent_frame, attribute, attribute);
internal::SetBreakdownAttributionFromFrame(frame, breakdown_with_url);
internal::CopyBreakdownAttribution(breakdown_with_url,
breakdown_with_empty_url);
// All measurements should be created without measurement results.
auto expected_result = CreateExpectedMemoryMeasurement({
ExpectedMemoryBreakdown(/*bytes=*/base::nullopt,
AttributionScope::kCrossOriginAggregated,
/*expected_url=*/base::nullopt,
/*expected_id=*/base::nullopt,
/*expected_src=*/base::nullopt),
ExpectedMemoryBreakdown(/*bytes=*/base::nullopt,
AttributionScope::kWindow,
"https://example.com", attribute, attribute),
ExpectedMemoryBreakdown(/*bytes=*/base::nullopt,
AttributionScope::kWindow,
/*expected_url=*/"", attribute, attribute),
});
EXPECT_EQ(MeasurementToJSON(measurement),
MeasurementToJSON(expected_result));
}
}
TEST_F(WebMemoryAggregatorTest, AggregateSingleFrame) {
// Example 1 from http://wicg.github.io/performance-measure-memory/#examples
FrameNodeImpl* main_frame = AddFrameNode("https://example.com/", Bytes{10});
auto expected_result = CreateExpectedMemoryMeasurement({
ExpectedMemoryBreakdown(10, AttributionScope::kWindow,
"https://example.com/"),
});
EXPECT_EQ(internal::FindAggregationStartNode(main_frame), main_frame);
WebMemoryAggregator aggregator(main_frame);
auto result = aggregator.AggregateMeasureMemoryResult();
EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
}
TEST_F(WebMemoryAggregatorTest, AggregateSingleSiteMultiFrame) {
// Example 2 from http://wicg.github.io/performance-measure-memory/#examples
FrameNodeImpl* main_frame = AddFrameNode("https://example.com/", Bytes{10});
FrameNodeImpl* child_frame =
AddFrameNode("https://example.com/iframe.html", Bytes{5}, main_frame,
"example-id", "redirect.html?target=iframe.html");
EXPECT_EQ(internal::FindAggregationStartNode(main_frame), main_frame);
WebMemoryAggregator aggregator(main_frame);
// Test the relationships of each node in the graph.
EXPECT_EQ(aggregator.FindNodeAggregationType(main_frame),
NodeAggregationType::kSameOriginAggregationPoint);
EXPECT_EQ(aggregator.FindNodeAggregationType(child_frame),
NodeAggregationType::kSameOriginAggregationPoint);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
child_frame, aggregator.requesting_origin()),
main_frame);
auto expected_result = CreateExpectedMemoryMeasurement({
ExpectedMemoryBreakdown(10, AttributionScope::kWindow,
"https://example.com/"),
ExpectedMemoryBreakdown(5, AttributionScope::kWindow,
"https://example.com/iframe.html", "example-id",
"redirect.html?target=iframe.html"),
});
auto result = aggregator.AggregateMeasureMemoryResult();
EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
}
TEST_F(WebMemoryAggregatorTest, AggregateCrossOrigin) {
// Example 5 from http://wicg.github.io/performance-measure-memory/#examples
//
// example.com (10 bytes)
// |
// *--foo.com/iframe1 (5 bytes)
// |
// *--foo.com/iframe2 (2 bytes)
// |
// *--bar.com/iframe2 (3 bytes)
// |
// *--foo.com/worker.js (4 bytes)
FrameNodeImpl* main_frame = AddFrameNode("https://example.com/", Bytes{10});
FrameNodeImpl* child_frame =
AddFrameNode("https://foo.com/iframe1", Bytes{5}, main_frame,
"example-id", "https://foo.com/iframe1");
FrameNodeImpl* grandchild1 =
AddFrameNode("https://foo.com/iframe2", Bytes{2}, child_frame,
"example-id2", "https://foo.com/iframe2");
FrameNodeImpl* grandchild2 =
AddFrameNode("https://bar.com/iframe2", Bytes{3}, child_frame,
"example-id3", "https://bar.com/iframe2");
// TODO(crbug.com/1085129): In the spec this is a worker, but they're not
// supported yet.
FrameNodeImpl* grandchild3 =
AddFrameNode("https://foo.com/worker.js", Bytes{4}, child_frame);
EXPECT_EQ(internal::FindAggregationStartNode(main_frame), main_frame);
WebMemoryAggregator aggregator(main_frame);
// Test the relationships of each node in the graph.
EXPECT_EQ(aggregator.FindNodeAggregationType(main_frame),
NodeAggregationType::kSameOriginAggregationPoint);
EXPECT_EQ(aggregator.FindNodeAggregationType(child_frame),
NodeAggregationType::kCrossOriginAggregationPoint);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
child_frame, aggregator.requesting_origin()),
main_frame);
EXPECT_EQ(aggregator.FindNodeAggregationType(grandchild1),
NodeAggregationType::kCrossOriginAggregated);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
grandchild1, aggregator.requesting_origin()),
nullptr);
EXPECT_EQ(aggregator.FindNodeAggregationType(grandchild2),
NodeAggregationType::kCrossOriginAggregated);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
grandchild2, aggregator.requesting_origin()),
nullptr);
EXPECT_EQ(aggregator.FindNodeAggregationType(grandchild3),
NodeAggregationType::kCrossOriginAggregated);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
grandchild3, aggregator.requesting_origin()),
nullptr);
auto expected_result = CreateExpectedMemoryMeasurement({
ExpectedMemoryBreakdown(10, AttributionScope::kWindow,
"https://example.com/"),
ExpectedMemoryBreakdown(14, AttributionScope::kCrossOriginAggregated,
base::nullopt, "example-id",
"https://foo.com/iframe1"),
});
auto result = aggregator.AggregateMeasureMemoryResult();
EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
}
TEST_F(WebMemoryAggregatorTest, AggregateNestedCrossOrigin) {
// Based on example 6 from
// http://wicg.github.io/performance-measure-memory/#examples with some
// further nested frames added to test all combinations of same-origin &
// cross-origin children & parents.
//
// example.com (10 bytes)
// |
// *--foo.com/iframe1 (5 bytes) <-- opaque to requesting node
// |
// *--bar.com/iframe1 (4 bytes) <-- invisible to requesting node
// |
// *--example.com/iframe1 (3 bytes)
// |
// *--foo.com/iframe2 (2 bytes) <-- opaque to requesting node
// | |
// | *--example.com/iframe2 (1 byte)
// |
// *--example.com/iframe3 (6 bytes)
FrameNodeImpl* main_frame = AddFrameNode("https://example.com/", Bytes{10});
FrameNodeImpl* subframe =
AddFrameNode("https://foo.com/iframe1", Bytes{5}, main_frame,
"example-id", "https://foo.com/iframe1");
FrameNodeImpl* subframe2 =
AddFrameNode("https://bar.com/iframe1", Bytes{4}, subframe, "example-id2",
"https://bar.com/iframe1");
FrameNodeImpl* subframe3 =
AddFrameNode("https://example.com/iframe1", Bytes{3}, subframe2,
"example-id3", "https://example.com/iframe1");
FrameNodeImpl* subframe4 =
AddFrameNode("https://foo.com/iframe2", Bytes{2}, subframe3,
"example-id4", "https://foo.com/iframe2");
FrameNodeImpl* subframe5 =
AddFrameNode("https://example.com/iframe2", Bytes{1}, subframe4,
"example-id5", "https://example.com/iframe2");
FrameNodeImpl* subframe6 =
AddFrameNode("https://example.com/iframe3", Bytes{6}, subframe3,
"example-id6", "https://example.com/iframe3");
// To test aggregation all the frames above are in the same process, even
// though in production frames with different origins will be in different
// processes whenever possible. Frames in a different process from the
// requesting frame should all have 0 bytes reported.
FrameNodeImpl* cross_process_frame =
AddCrossProcessFrameNode("https://example.com/cross_process", Bytes{100},
subframe3, "cross-process-id1");
FrameNodeImpl* cross_process_frame2 =
AddCrossProcessFrameNode("https://foo.com/cross_process", Bytes{200},
subframe3, "cross-process-id2");
// A frame without a memory measurement (eg. a frame that's added to the frame
// tree during the measurement) should not have a memory entry in the result.
FrameNodeImpl* empty_frame =
AddFrameNode("https://example.com/empty_frame", base::nullopt, subframe3);
EXPECT_EQ(internal::FindAggregationStartNode(main_frame), main_frame);
WebMemoryAggregator aggregator(main_frame);
// Test the relationships of each node in the graph.
EXPECT_EQ(aggregator.FindNodeAggregationType(main_frame),
NodeAggregationType::kSameOriginAggregationPoint);
EXPECT_EQ(aggregator.FindNodeAggregationType(subframe),
NodeAggregationType::kCrossOriginAggregationPoint);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
subframe, aggregator.requesting_origin()),
main_frame);
EXPECT_EQ(aggregator.FindNodeAggregationType(subframe2),
NodeAggregationType::kCrossOriginAggregated);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
subframe2, aggregator.requesting_origin()),
nullptr);
EXPECT_EQ(aggregator.FindNodeAggregationType(subframe3),
NodeAggregationType::kSameOriginAggregationPoint);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
subframe3, aggregator.requesting_origin()),
nullptr);
EXPECT_EQ(aggregator.FindNodeAggregationType(subframe4),
NodeAggregationType::kCrossOriginAggregationPoint);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
subframe4, aggregator.requesting_origin()),
subframe3);
EXPECT_EQ(aggregator.FindNodeAggregationType(subframe5),
NodeAggregationType::kSameOriginAggregationPoint);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
subframe5, aggregator.requesting_origin()),
nullptr);
EXPECT_EQ(aggregator.FindNodeAggregationType(subframe6),
NodeAggregationType::kSameOriginAggregationPoint);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
subframe6, aggregator.requesting_origin()),
subframe3);
EXPECT_EQ(aggregator.FindNodeAggregationType(empty_frame),
NodeAggregationType::kSameOriginAggregationPoint);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
empty_frame, aggregator.requesting_origin()),
subframe3);
EXPECT_EQ(aggregator.FindNodeAggregationType(cross_process_frame),
NodeAggregationType::kSameOriginAggregationPoint);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
cross_process_frame, aggregator.requesting_origin()),
subframe3);
EXPECT_EQ(aggregator.FindNodeAggregationType(cross_process_frame2),
NodeAggregationType::kCrossOriginAggregationPoint);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
cross_process_frame2, aggregator.requesting_origin()),
subframe3);
auto expected_result = CreateExpectedMemoryMeasurement({
ExpectedMemoryBreakdown(10, AttributionScope::kWindow,
"https://example.com/"),
ExpectedMemoryBreakdown(9, AttributionScope::kCrossOriginAggregated,
base::nullopt, "example-id",
"https://foo.com/iframe1"),
ExpectedMemoryBreakdown(3, AttributionScope::kWindow,
"https://example.com/iframe1", "example-id",
"https://foo.com/iframe1"),
ExpectedMemoryBreakdown(2, AttributionScope::kCrossOriginAggregated,
base::nullopt, "example-id4",
"https://foo.com/iframe2"),
ExpectedMemoryBreakdown(1, AttributionScope::kWindow,
"https://example.com/iframe2", "example-id4",
"https://foo.com/iframe2"),
ExpectedMemoryBreakdown(6, AttributionScope::kWindow,
"https://example.com/iframe3", "example-id6",
"https://example.com/iframe3"),
ExpectedMemoryBreakdown(0, AttributionScope::kWindow,
"https://example.com/cross_process",
"cross-process-id1"),
ExpectedMemoryBreakdown(0, AttributionScope::kCrossOriginAggregated,
base::nullopt, "cross-process-id2"),
ExpectedMemoryBreakdown(base::nullopt, AttributionScope::kWindow,
"https://example.com/empty_frame"),
});
auto result = aggregator.AggregateMeasureMemoryResult();
EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
}
TEST_F(WebMemoryAggregatorTest, FindAggregationStartNode) {
FrameNodeImpl* main_frame = AddFrameNode("https://example.com/", Bytes{10});
FrameNodeImpl* cross_site_child = AddFrameNode(
"https://foo.com/iframe.html", Bytes{5}, main_frame, "example-id", "");
FrameNodeImpl* same_site_child =
AddFrameNode("https://foo.com/iframe2.html", Bytes{4}, cross_site_child,
"example-id2", "");
// FindAggregationStartNode should return the parent foo.com frame for either
// foo.com child. It should not return the main frame since it's cross-site
// from the requesting frames.
EXPECT_EQ(internal::FindAggregationStartNode(cross_site_child),
cross_site_child);
EXPECT_EQ(internal::FindAggregationStartNode(same_site_child),
cross_site_child);
// When aggregation starts at |cross_site_child| it should not include any
// memory from the main frame.
WebMemoryAggregator aggregator(cross_site_child);
auto expected_result = CreateExpectedMemoryMeasurement({
ExpectedMemoryBreakdown(5, AttributionScope::kWindow,
"https://foo.com/iframe.html"),
ExpectedMemoryBreakdown(4, AttributionScope::kWindow,
"https://foo.com/iframe2.html", "example-id2",
""),
});
auto result = aggregator.AggregateMeasureMemoryResult();
EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
// When the main frame requests a measurement of the same tree it should
// aggregate the children, which are cross-site from it.
EXPECT_EQ(internal::FindAggregationStartNode(main_frame), main_frame);
auto main_frame_expected_result = CreateExpectedMemoryMeasurement({
ExpectedMemoryBreakdown(10, AttributionScope::kWindow,
"https://example.com/"),
ExpectedMemoryBreakdown(9, AttributionScope::kCrossOriginAggregated,
base::nullopt, "example-id", ""),
});
WebMemoryAggregator main_frame_aggregator(main_frame);
auto main_frame_result = main_frame_aggregator.AggregateMeasureMemoryResult();
EXPECT_EQ(MeasurementToJSON(main_frame_result),
MeasurementToJSON(main_frame_expected_result));
}
TEST_F(WebMemoryAggregatorTest, FindCrossProcessAggregationStartNode) {
FrameNodeImpl* main_frame = AddFrameNode("https://example.com/", Bytes{1});
FrameNodeImpl* cross_process_child = AddCrossProcessFrameNode(
"https://example.com/cross_process.html", Bytes{2}, main_frame);
FrameNodeImpl* same_process_child = AddFrameNode(
"https://example.com/same_process.html", Bytes{3}, cross_process_child);
auto origin = url::Origin::Create(GURL("https://example.com"));
ASSERT_EQ(internal::GetSameOriginParentOrOpener(cross_process_child, origin),
main_frame);
ASSERT_EQ(internal::GetSameOriginParentOrOpener(same_process_child, origin),
cross_process_child);
// |cross_process_child| has no ancestor in the same process as it.
EXPECT_EQ(internal::FindAggregationStartNode(cross_process_child),
cross_process_child);
// The search starting from |same_process_child| should skip over
// |cross_process_child|, which is in a different process, and find
// |main_frame| which is in the same process.
EXPECT_EQ(internal::FindAggregationStartNode(same_process_child), main_frame);
}
TEST_F(WebMemoryAggregatorTest, AggregateWindowOpener) {
FrameNodeImpl* main_frame = AddFrameNode("https://example.com/", Bytes{10});
FrameNodeImpl* child_frame = AddFrameNode("https://example.com/iframe.html",
Bytes{5}, main_frame, "example-id");
FrameNodeImpl* opened_frame = AddFrameNodeFromOpener(
"https://example.com/window/", Bytes{4}, main_frame);
FrameNodeImpl* child_of_opened_frame =
AddFrameNode("https://example.com/window-iframe.html", Bytes{3},
opened_frame, "example-id2");
FrameNodeImpl* cross_site_child =
AddFrameNode("https://cross-site-example.com/window-iframe.html",
Bytes{2}, opened_frame, "example-id3");
// COOP+COEP forces cross-site windows to open in their own BrowsingInstance.
FrameNodeImpl* cross_site_popup = AddCrossBrowsingInstanceFrameNodeFromOpener(
"https://cross-site-example.com/", Bytes{2}, main_frame);
// FindAggregationStartNode whould return |main_frame| from any of the
// same-site frames.
for (auto* frame :
{main_frame, child_frame, opened_frame, child_of_opened_frame}) {
EXPECT_EQ(internal::FindAggregationStartNode(frame), main_frame)
<< frame->url();
}
WebMemoryAggregator aggregator(main_frame);
// Test the relationships of each node in the graph.
EXPECT_EQ(aggregator.FindNodeAggregationType(main_frame),
NodeAggregationType::kSameOriginAggregationPoint);
EXPECT_EQ(aggregator.FindNodeAggregationType(child_frame),
NodeAggregationType::kSameOriginAggregationPoint);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
child_frame, aggregator.requesting_origin()),
main_frame);
EXPECT_EQ(aggregator.FindNodeAggregationType(opened_frame),
NodeAggregationType::kSameOriginAggregationPoint);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
opened_frame, aggregator.requesting_origin()),
main_frame);
EXPECT_EQ(aggregator.FindNodeAggregationType(child_of_opened_frame),
NodeAggregationType::kSameOriginAggregationPoint);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
child_of_opened_frame, aggregator.requesting_origin()),
opened_frame);
EXPECT_EQ(aggregator.FindNodeAggregationType(cross_site_child),
NodeAggregationType::kCrossOriginAggregationPoint);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
cross_site_child, aggregator.requesting_origin()),
opened_frame);
EXPECT_EQ(aggregator.FindNodeAggregationType(cross_site_popup),
NodeAggregationType::kInvisible);
EXPECT_EQ(internal::GetSameOriginParentOrOpener(
cross_site_popup, aggregator.requesting_origin()),
main_frame);
auto expected_result = CreateExpectedMemoryMeasurement({
ExpectedMemoryBreakdown(10, AttributionScope::kWindow,
"https://example.com/"),
ExpectedMemoryBreakdown(5, AttributionScope::kWindow,
"https://example.com/iframe.html", "example-id"),
ExpectedMemoryBreakdown(4, AttributionScope::kWindow,
"https://example.com/window/"),
ExpectedMemoryBreakdown(3, AttributionScope::kWindow,
"https://example.com/window-iframe.html",
"example-id2"),
ExpectedMemoryBreakdown(2, AttributionScope::kCrossOriginAggregated,
base::nullopt, "example-id3"),
});
auto result = aggregator.AggregateMeasureMemoryResult();
EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
// The two cross-site frames should only be able to see themselves (and their
// own children, if they had any). They have the same |bytes| so their
// expectations only vary by url.
for (auto* frame : {cross_site_child, cross_site_popup}) {
const std::string url = frame->url().spec();
SCOPED_TRACE(url);
const FrameNode* start_node = internal::FindAggregationStartNode(frame);
EXPECT_EQ(start_node, frame);
WebMemoryAggregator aggregator(start_node);
// Only check the NodeAggregationType of the single node that's iterated
// over. Parents of the start node have an undefined aggregation type.
EXPECT_EQ(aggregator.FindNodeAggregationType(start_node),
NodeAggregationType::kSameOriginAggregationPoint);
auto expected_cross_site_result = CreateExpectedMemoryMeasurement({
ExpectedMemoryBreakdown(2, AttributionScope::kWindow, url,
base::nullopt, base::nullopt),
});
auto cross_site_result = aggregator.AggregateMeasureMemoryResult();
EXPECT_EQ(MeasurementToJSON(cross_site_result),
MeasurementToJSON(expected_cross_site_result));
}
}
TEST_F(WebMemoryAggregatorTest, AggregateProvisionalWindowOpener) {
FrameNodeImpl* main_frame = AddFrameNode("https://example.com/", Bytes{10});
// This creates an openee window with pending navigation which should be
// skipped because it may get its own browsing context group once the
// navigation completes.
FrameNodeImpl* pending_frame =
AddFrameNodeFromOpener(base::nullopt, Bytes{4}, main_frame);
WebMemoryAggregator aggregator(main_frame);
EXPECT_EQ(aggregator.FindNodeAggregationType(pending_frame),
NodeAggregationType::kInvisible);
auto expected_result = CreateExpectedMemoryMeasurement({
ExpectedMemoryBreakdown(10, AttributionScope::kWindow,
"https://example.com/"),
});
auto result = aggregator.AggregateMeasureMemoryResult();
EXPECT_EQ(MeasurementToJSON(result), MeasurementToJSON(expected_result));
}
} // namespace
} // namespace v8_memory
} // namespace performance_manager