blob: d2dc784b189d62f94206cbef61d5c56697d81abd [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_impl.h"
#include <memory>
#include <string>
#include <utility>
#include "base/containers/flat_map.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/public/graph/frame_node.h"
#include "components/performance_manager/public/mojom/web_memory.mojom.h"
#include "components/performance_manager/public/performance_manager.h"
#include "components/performance_manager/public/v8_memory/v8_detailed_memory.h"
#include "components/performance_manager/public/v8_memory/web_memory.h"
#include "components/performance_manager/v8_memory/v8_memory_test_helpers.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/test/test_renderer_host.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/tokens/tokens.h"
namespace performance_manager {
namespace v8_memory {
using WebMemoryImplPMTest = V8MemoryPerformanceManagerTestHarness;
class WebMemoryImplTest : public WebMemoryTestHarness {
protected:
void MeasureAndVerify(FrameNodeImpl* frame,
base::flat_map<std::string, Bytes> expected);
};
class FakeSecurityChecker : public WebMeasureMemorySecurityChecker {
public:
explicit FakeSecurityChecker(bool allowed) : allowed_(allowed) {}
void CheckMeasureMemoryIsAllowed(
const FrameNode* frame_node,
MeasureMemoryCallback measure_memory_callback,
mojo::ReportBadMessageCallback bad_message_callback) const override {
if (allowed_) {
std::move(measure_memory_callback)
.Run(FrameNodeImpl::FromNode(frame_node)->GetWeakPtr());
} else {
std::move(bad_message_callback).Run("disallowed");
}
}
private:
bool allowed_;
};
void WebMemoryImplTest::MeasureAndVerify(
FrameNodeImpl* frame,
base::flat_map<std::string, Bytes> expected) {
bool measurement_done = false;
WebMemoryMeasurer web_memory(
frame->frame_token(), V8DetailedMemoryRequest::MeasurementMode::kDefault,
base::BindLambdaForTesting([&measurement_done, &expected](
mojom::WebMemoryMeasurementPtr result) {
base::flat_map<std::string, Bytes> actual;
for (const auto& entry : result->breakdown) {
EXPECT_EQ(1u, entry->attribution.size());
if (mojom::WebMemoryAttribution::Scope::kWindow ==
entry->attribution[0]->scope) {
actual[*entry->attribution[0]->url] = Bytes{entry->bytes};
} else {
actual[*entry->attribution[0]->src] = Bytes{entry->bytes};
}
}
EXPECT_EQ(expected, actual);
measurement_done = true;
}));
V8DetailedMemoryProcessData process_data;
web_memory.MeasurementComplete(process_node(), &process_data);
EXPECT_TRUE(measurement_done);
}
TEST_F(WebMemoryImplTest, MeasurerIncludesSameOriginRelatedFrames) {
auto* main = AddFrameNode("http://foo.com/", Bytes{10u});
AddFrameNode("http://foo.com/iframe", Bytes{20}, main);
MeasureAndVerify(main, {
{"http://foo.com/", Bytes{10u}},
{"http://foo.com/iframe", Bytes{20u}},
});
}
TEST_F(WebMemoryImplTest, MeasurerIncludesCrossOriginFrames) {
auto* main = AddFrameNode("http://foo.com", Bytes{10u});
AddFrameNode("http://bar.com/iframe", Bytes{20}, main, "bar_id",
"http://bar.com/iframe_src");
MeasureAndVerify(main, {{"http://foo.com/", Bytes{10u}},
{
"http://bar.com/iframe_src",
Bytes{20},
}});
}
TEST_F(WebMemoryImplTest, MeasurerSkipsCrossBrowserContextGroupFrames) {
auto* main = AddFrameNode("http://foo.com", Bytes{10u});
AddCrossBrowsingInstanceFrameNode("http://foo.com/unrelated", Bytes{20});
MeasureAndVerify(main, {{"http://foo.com/", Bytes{10u}}});
}
TEST_F(WebMemoryImplPMTest, WebMeasureMemory) {
blink::LocalFrameToken frame_token =
blink::LocalFrameToken(main_frame()->GetFrameToken());
// Call WebMeasureMemory on the performance manager sequence and verify that
// the result matches the data provided by the mock reporter.
base::RunLoop run_loop;
auto measurement_callback =
base::BindLambdaForTesting([&](mojom::WebMemoryMeasurementPtr result) {
EXPECT_EQ(1u, result->breakdown.size());
const auto& entry = result->breakdown[0];
EXPECT_EQ(1u, entry->attribution.size());
EXPECT_EQ(kMainFrameUrl, *(entry->attribution[0]->url));
EXPECT_EQ(1001u, entry->bytes);
run_loop.Quit();
});
auto bad_message_callback =
base::BindLambdaForTesting([&](const std::string& error) {
ADD_FAILURE() << error;
run_loop.Quit();
});
base::WeakPtr<FrameNode> frame_node_wrapper =
PerformanceManager::GetFrameNodeForRenderFrameHost(main_frame());
PerformanceManager::CallOnGraph(
FROM_HERE, base::BindLambdaForTesting([&]() {
ASSERT_TRUE(frame_node_wrapper);
FrameNode* frame_node = frame_node_wrapper.get();
WebMeasureMemory(
frame_node, mojom::WebMemoryMeasurement::Mode::kDefault,
std::make_unique<FakeSecurityChecker>(true),
std::move(measurement_callback), std::move(bad_message_callback));
}));
// Set up and bind the mock reporter.
MockV8DetailedMemoryReporter mock_reporter;
{
auto data = NewPerProcessV8MemoryUsage(1);
AddIsolateMemoryUsage(frame_token, 1001u, data->isolates[0].get());
ExpectBindAndRespondToQuery(&mock_reporter, std::move(data),
main_process_id());
}
// Finally, run all tasks to verify that the memory measurement callback
// is actually invoked. The test will time out if not.
run_loop.Run();
}
TEST_F(WebMemoryImplPMTest, MeasurementInterrupted) {
CreateCrossProcessChildFrame();
blink::LocalFrameToken frame_token =
blink::LocalFrameToken(child_frame()->GetFrameToken());
// Call WebMeasureMemory on the performance manager sequence but delete the
// process being measured before the result arrives.
auto measurement_callback =
base::BindOnce([](mojom::WebMemoryMeasurementPtr result) {
FAIL() << "Measurement callback ran unexpectedly";
});
auto bad_message_callback =
base::BindOnce([](const std::string& error) { FAIL() << error; });
base::WeakPtr<FrameNode> frame_node_wrapper =
PerformanceManager::GetFrameNodeForRenderFrameHost(child_frame());
PerformanceManager::CallOnGraph(
FROM_HERE, base::BindLambdaForTesting([&]() {
ASSERT_TRUE(frame_node_wrapper);
FrameNode* frame_node = frame_node_wrapper.get();
WebMeasureMemory(
frame_node, mojom::WebMemoryMeasurement::Mode::kDefault,
std::make_unique<FakeSecurityChecker>(true),
std::move(measurement_callback), std::move(bad_message_callback));
}));
// Set up and bind the mock reporter.
MockV8DetailedMemoryReporter mock_reporter;
{
::testing::InSequence seq;
ExpectBindReceiver(&mock_reporter, child_process_id());
auto data = NewPerProcessV8MemoryUsage(1);
AddIsolateMemoryUsage(frame_token, 1001u, data->isolates[0].get());
ExpectQueryAndDelayReply(&mock_reporter, base::TimeDelta::FromSeconds(10),
std::move(data));
}
// Verify that requests are sent but reply is not yet received.
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(5));
::testing::Mock::VerifyAndClearExpectations(&mock_reporter);
// Remove the child frame, which will destroy the child process.
content::RenderFrameHostTester::For(child_frame())->Detach();
// Advance until the reply is expected to make sure nothing explodes.
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(5));
}
TEST_F(WebMemoryImplPMTest, MeasurementDisallowed) {
// Call WebMeasureMemory on the performance manager sequence but expect the
// mojo ReportBadMessage callback to be called.
base::RunLoop run_loop;
auto measurement_callback =
base::BindLambdaForTesting([&](mojom::WebMemoryMeasurementPtr result) {
ADD_FAILURE() << "Measurement callback ran unexpectedly.";
run_loop.Quit();
});
auto bad_message_callback =
base::BindLambdaForTesting([&](const std::string& error) {
SUCCEED() << error;
run_loop.Quit();
});
base::WeakPtr<FrameNode> frame_node_wrapper =
PerformanceManager::GetFrameNodeForRenderFrameHost(main_frame());
PerformanceManager::CallOnGraph(
FROM_HERE, base::BindLambdaForTesting([&]() {
ASSERT_TRUE(frame_node_wrapper);
FrameNode* frame_node = frame_node_wrapper.get();
WebMeasureMemory(
frame_node, mojom::WebMemoryMeasurement::Mode::kDefault,
std::make_unique<FakeSecurityChecker>(false),
std::move(measurement_callback), std::move(bad_message_callback));
}));
run_loop.Run();
}
} // namespace v8_memory
} // namespace performance_manager