Commit 85ecd948 authored by Chris Hamilton's avatar Chris Hamilton Committed by Commit Bot

[PM] Implement V8ContextTracker business logic.

BUG=1080672

Change-Id: I10d0f001f00f1f7eb566843b9e9647297569abb2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2466152Reviewed-by: default avatarJoe Mason <joenotcharles@chromium.org>
Commit-Queue: Chris Hamilton <chrisha@chromium.org>
Cr-Commit-Position: refs/heads/master@{#817764}
parent f309e1d5
......@@ -234,6 +234,7 @@ source_set("unit_tests") {
"render_process_host_id_unittest.cc",
"v8_memory/v8_context_tracker_helpers_unittest.cc",
"v8_memory/v8_context_tracker_internal_unittest.cc",
"v8_memory/v8_context_tracker_unittest.cc",
"v8_memory/v8_detailed_memory_unittest.cc",
"web_contents_proxy_unittest.cc",
"worker_watcher_unittest.cc",
......
......@@ -59,6 +59,8 @@ class FrameNodeImpl
public TypedNodeBase<FrameNodeImpl, FrameNode, FrameNodeObserver>,
public mojom::DocumentCoordinationUnit {
public:
using PassKey = util::PassKey<FrameNodeImpl>;
static const char kDefaultPriorityReason[];
static constexpr NodeTypeEnum Type() { return NodeTypeEnum::kFrame; }
......@@ -161,6 +163,8 @@ class FrameNodeImpl
return &execution_context_;
}
static PassKey CreatePassKeyForTesting() { return PassKey(); }
private:
friend class ExecutionContextPriorityAccess;
friend class FrameNodeImplDescriber;
......
......@@ -14,6 +14,7 @@
#include "base/process/process.h"
#include "base/process/process_handle.h"
#include "base/time/time.h"
#include "base/util/type_safety/pass_key.h"
#include "components/performance_manager/graph/node_attached_data.h"
#include "components/performance_manager/graph/node_base.h"
#include "components/performance_manager/graph/properties.h"
......@@ -44,6 +45,8 @@ class ProcessNodeImpl
public TypedNodeBase<ProcessNodeImpl, ProcessNode, ProcessNodeObserver>,
public mojom::ProcessCoordinationUnit {
public:
using PassKey = util::PassKey<ProcessNodeImpl>;
static constexpr NodeTypeEnum Type() { return NodeTypeEnum::kProcess; }
ProcessNodeImpl(content::ProcessType process_type,
......@@ -118,6 +121,8 @@ class ProcessNodeImpl
return weak_factory_.GetWeakPtr();
}
static PassKey CreatePassKeyForTesting() { return PassKey(); }
protected:
void SetProcessImpl(base::Process process,
base::ProcessId process_id,
......
......@@ -4,15 +4,46 @@
#include "components/performance_manager/v8_memory/v8_context_tracker.h"
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
#include "base/task/task_traits.h"
#include "base/values.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/public/execution_context/execution_context_registry.h"
#include "components/performance_manager/public/graph/graph.h"
#include "components/performance_manager/public/graph/node_data_describer_registry.h"
#include "components/performance_manager/public/performance_manager.h"
#include "components/performance_manager/public/render_process_host_id.h"
#include "components/performance_manager/v8_memory/v8_context_tracker_helpers.h"
#include "components/performance_manager/v8_memory/v8_context_tracker_internal.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "mojo/public/cpp/bindings/message.h"
namespace performance_manager {
namespace v8_memory {
namespace {
using ExecutionContextData = internal::ExecutionContextData;
using ProcessData = internal::ProcessData;
using RemoteFrameData = internal::RemoteFrameData;
using V8ContextData = internal::V8ContextData;
// A function that can be bound to as a mojo::ReportBadMessage
// callback. Only used in testing.
void FakeReportBadMessageForTesting(const std::string& error) {
// This is used in DCHECK death tests, so must use a DCHECK.
DCHECK(false) << "Bad mojo message: " << error;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// V8ContextTracker::ExecutionContextState implementation:
......@@ -43,10 +74,278 @@ V8ContextTracker::V8ContextTracker()
V8ContextTracker::~V8ContextTracker() = default;
const V8ContextTracker::ExecutionContextState*
V8ContextTracker::GetExecutionContextState(
const blink::ExecutionContextToken& token) const {
return data_store_->Get(token);
}
const V8ContextTracker::V8ContextState* V8ContextTracker::GetV8ContextState(
const blink::V8ContextToken& token) const {
return data_store_->Get(token);
}
void V8ContextTracker::OnV8ContextCreated(
util::PassKey<ProcessNodeImpl> key,
ProcessNodeImpl* process_node,
const V8ContextDescription& description,
const base::Optional<IframeAttributionData>& iframe_attribution_data) {
DCHECK_ON_GRAPH_SEQUENCE(process_node->graph());
// Validate the |description|.
{
auto result = ValidateV8ContextDescription(description);
if (result != V8ContextDescriptionStatus::kValid) {
LOG(ERROR) << "V8ContextDescriptionStatus = " << static_cast<int>(result);
mojo::ReportBadMessage("invalid V8ContextDescription");
return;
}
}
// Validate the |iframe_attribution_data|.
{
base::Optional<bool> result =
ExpectIframeAttributionDataForV8ContextDescription(
description, process_node->graph());
if (result) {
bool expected = *result;
bool received = static_cast<bool>(iframe_attribution_data);
if (expected != received) {
LOG(ERROR) << "IframeAttributionData: expected = " << expected
<< ", received = " << received;
mojo::ReportBadMessage("invalid IframeAttributionData");
return;
}
}
}
// Ensure that the V8Context creation notification isn't repeated.
if (data_store_->Get(description.token)) {
mojo::ReportBadMessage("repeated OnV8ContextCreated notification");
return;
}
auto* process_data = ProcessData::GetOrCreate(process_node);
// Get or create an ExecutionContextData if necessary. If it doesn't get
// committed below it will safely tear itself down.
std::unique_ptr<ExecutionContextData> ec_data;
ExecutionContextData* raw_ec_data = nullptr;
if (description.execution_context_token) {
raw_ec_data = data_store_->Get(*description.execution_context_token);
if (!raw_ec_data) {
ec_data = std::make_unique<ExecutionContextData>(
process_data, *description.execution_context_token,
iframe_attribution_data);
raw_ec_data = ec_data.get();
}
}
if (raw_ec_data && raw_ec_data->process_data() != process_data) {
mojo::ReportBadMessage(
"OnV8ContextCreated refers to an out-of-process ExecutionContext");
return;
}
// Create the V8ContextData.
std::unique_ptr<V8ContextData> v8_data =
std::make_unique<V8ContextData>(process_data, description, raw_ec_data);
// Try to commit the objects.
if (!data_store_->Pass(std::move(v8_data))) {
mojo::ReportBadMessage("Multiple main worlds seen for an ExecutionContext");
return;
}
if (ec_data)
data_store_->Pass(std::move(ec_data));
}
void V8ContextTracker::OnV8ContextDetached(
util::PassKey<ProcessNodeImpl> key,
ProcessNodeImpl* process_node,
const blink::V8ContextToken& v8_context_token) {
DCHECK_ON_GRAPH_SEQUENCE(process_node->graph());
auto* process_data = ProcessData::Get(process_node);
auto* v8_data = data_store_->Get(v8_context_token);
if (!process_data || !v8_data) {
mojo::ReportBadMessage("unexpected OnV8ContextDetached");
return;
}
if (!data_store_->MarkDetached(v8_data)) {
mojo::ReportBadMessage("repeated OnV8ContextDetached");
return;
}
// Mark the context as detached. If this is the main context, then mark the
// parent ExecutionContext as destroyed as well.
if (v8_data->IsMainV8Context()) {
auto* ec_data = v8_data->GetExecutionContextData();
data_store_->MarkDestroyed(ec_data);
}
}
void V8ContextTracker::OnV8ContextDestroyed(
util::PassKey<ProcessNodeImpl> key,
ProcessNodeImpl* process_node,
const blink::V8ContextToken& v8_context_token) {
DCHECK_ON_GRAPH_SEQUENCE(process_node->graph());
auto* process_data = ProcessData::Get(process_node);
auto* v8_data = data_store_->Get(v8_context_token);
if (!process_data || !v8_data) {
mojo::ReportBadMessage("unexpected OnV8ContextDestroyed");
return;
}
data_store_->Destroy(v8_context_token);
}
void V8ContextTracker::OnRemoteIframeAttached(
util::PassKey<FrameNodeImpl> key,
FrameNodeImpl* parent_frame_node,
const blink::RemoteFrameToken& remote_frame_token,
const IframeAttributionData& iframe_attribution_data) {
DCHECK_ON_GRAPH_SEQUENCE(parent_frame_node->graph());
// RemoteFrameTokens are issued by the browser to a renderer, so if we receive
// an IPC from a renderer using that token, then the corresponding
// RenderFrameProxyHost is guaranteed to exist, and the token will resolve to
// a RenderFrameHost. Similarly, if the RenderFrameHost exists, then we will
// have a representation for it in the graph, as we learn about frames from
// the UI thread. The only case where this won't be true is if the frame has
// subsequently been torn down (the IPC races with frame death), in which
// case it doesn't matter as the corresponding graph nodes are in the process
// of being torn down.
// The data that bounces between threads, bundled up for convenience.
struct Data {
mojo::ReportBadMessageCallback bad_message_callback;
blink::RemoteFrameToken remote_frame_token;
IframeAttributionData iframe_attribution_data;
base::WeakPtr<FrameNode> frame_node;
};
std::unique_ptr<Data> data(new Data{mojo::GetBadMessageCallback(),
remote_frame_token,
iframe_attribution_data, nullptr});
auto on_pm_seq = base::BindOnce([](std::unique_ptr<Data> data, Graph* graph) {
DCHECK(data);
DCHECK(graph);
DCHECK_ON_GRAPH_SEQUENCE(graph);
if (data->frame_node) {
auto* frame_node = FrameNodeImpl::FromNode(data->frame_node.get());
if (auto* tracker = V8ContextTracker::GetFromGraph(graph)) {
tracker->OnRemoteIframeAttachedImpl(
std::move(data->bad_message_callback), frame_node,
data->remote_frame_token, data->iframe_attribution_data);
}
}
});
// Looks up a RFH on the UI sequence, and posts back to |on_pm_seq|.
auto on_ui_thread = base::BindOnce([](decltype(on_pm_seq) on_pm_seq,
std::unique_ptr<Data> data,
RenderProcessHostId rph_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(on_pm_seq);
DCHECK(data);
if (auto* rfh = content::RenderFrameHost::FromPlaceholderToken(
rph_id.value(), data->remote_frame_token.value())) {
data->frame_node =
PerformanceManager::GetFrameNodeForRenderFrameHost(rfh);
PerformanceManager::CallOnGraph(
FROM_HERE, base::BindOnce(std::move(on_pm_seq), std::move(data)));
}
});
// Posts |on_ui_thread| to the UI sequence.
auto rph_id = parent_frame_node->process_node()
->render_process_host_proxy()
.render_process_host_id();
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(on_ui_thread), std::move(on_pm_seq),
std::move(data), rph_id));
}
void V8ContextTracker::OnRemoteIframeDetached(
util::PassKey<FrameNodeImpl> key,
FrameNodeImpl* parent_frame_node,
const blink::RemoteFrameToken& remote_frame_token) {
DCHECK_ON_GRAPH_SEQUENCE(parent_frame_node->graph());
// The data that bounces between threads, bundled up for convenience.
struct Data {
base::WeakPtr<FrameNodeImpl> parent_frame_node;
blink::RemoteFrameToken remote_frame_token;
};
std::unique_ptr<Data> data(
new Data{parent_frame_node->GetWeakPtr(), remote_frame_token});
auto on_pm_seq = base::BindOnce(
[](std::unique_ptr<Data> data, Graph* graph) {
DCHECK(data);
DCHECK(graph);
DCHECK_ON_GRAPH_SEQUENCE(graph);
// Only dispatch if the tracker and the frame node both still exist.
// Our bounce to the UI thread means either or both of these could have
// disappeared in the meantime.
if (data->parent_frame_node) {
if (auto* tracker = V8ContextTracker::GetFromGraph(graph)) {
tracker->OnRemoteIframeDetachedImpl(data->parent_frame_node.get(),
data->remote_frame_token);
}
}
},
std::move(data));
auto on_ui_seq = base::BindOnce(
[](decltype(on_pm_seq) on_pm_seq) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(on_pm_seq);
PerformanceManager::CallOnGraph(FROM_HERE, std::move(on_pm_seq));
},
std::move(on_pm_seq));
content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, std::move(on_ui_seq));
}
void V8ContextTracker::OnRemoteIframeAttachedForTesting(
FrameNodeImpl* frame_node,
const blink::RemoteFrameToken& remote_frame_token,
const IframeAttributionData& iframe_attribution_data) {
OnRemoteIframeAttachedImpl(base::BindOnce(&FakeReportBadMessageForTesting),
frame_node, remote_frame_token,
iframe_attribution_data);
}
void V8ContextTracker::OnRemoteIframeDetachedForTesting(
FrameNodeImpl* parent_frame_node,
const blink::RemoteFrameToken& remote_frame_token) {
OnRemoteIframeDetachedImpl(parent_frame_node, remote_frame_token);
}
size_t V8ContextTracker::GetExecutionContextCountForTesting() const {
return data_store_->GetExecutionContextDataCount();
}
size_t V8ContextTracker::GetV8ContextCountForTesting() const {
return data_store_->GetV8ContextDataCount();
}
size_t V8ContextTracker::GetDestroyedExecutionContextCountForTesting() const {
return data_store_->GetDestroyedExecutionContextDataCount();
}
size_t V8ContextTracker::GetDetachedV8ContextCountForTesting() const {
return data_store_->GetDetachedV8ContextDataCount();
}
void V8ContextTracker::OnBeforeExecutionContextRemoved(
const execution_context::ExecutionContext* ec) {
DCHECK_ON_GRAPH_SEQUENCE(ec->GetGraph());
// TODO(chrisha): Implement me.
if (auto* ec_data = data_store_->Get(ec->GetToken()))
data_store_->MarkDestroyed(ec_data);
}
void V8ContextTracker::OnBeforeGraphDestroyed(Graph* graph) {
......@@ -133,6 +432,7 @@ base::Value V8ContextTracker::DescribeProcessNodeData(
base::Value V8ContextTracker::DescribeWorkerNodeData(
const WorkerNode* node) const {
DCHECK_ON_GRAPH_SEQUENCE(node->GetGraph());
size_t v8_context_count = 0;
const auto* ec_data =
data_store_->Get(ToExecutionContextToken(node->GetWorkerToken()));
......@@ -146,8 +446,71 @@ base::Value V8ContextTracker::DescribeWorkerNodeData(
void V8ContextTracker::OnBeforeProcessNodeRemoved(const ProcessNode* node) {
DCHECK_ON_GRAPH_SEQUENCE(node->GetGraph());
// TODO(chrisha): Implement me by deleting all knowledge of execution contexts
// and v8 contexts in this process!
auto* process_node = ProcessNodeImpl::FromNode(node);
auto* process_data = ProcessData::Get(process_node);
if (process_data)
process_data->TearDown();
}
void V8ContextTracker::OnRemoteIframeAttachedImpl(
mojo::ReportBadMessageCallback bad_message_callback,
FrameNodeImpl* frame_node,
const blink::RemoteFrameToken& remote_frame_token,
const IframeAttributionData& iframe_attribution_data) {
DCHECK(bad_message_callback);
DCHECK_ON_GRAPH_SEQUENCE(frame_node->graph());
if (data_store_->Get(remote_frame_token)) {
std::move(bad_message_callback).Run("repeated OnRemoteIframeAttached");
return;
}
// Get or create an ExecutionContextData if necessary. If it doesn't get
// committed below it will safely tear itself down.
auto* process_data = ProcessData::GetOrCreate(frame_node->process_node());
std::unique_ptr<ExecutionContextData> ec_data;
blink::ExecutionContextToken ec_token(frame_node->frame_token());
auto* raw_ec_data = data_store_->Get(ec_token);
if (!raw_ec_data) {
ec_data = std::make_unique<ExecutionContextData>(process_data, ec_token,
base::nullopt);
raw_ec_data = ec_data.get();
}
if (raw_ec_data->remote_frame_data() ||
raw_ec_data->iframe_attribution_data != base::nullopt) {
std::move(bad_message_callback).Run("unexpected OnRemoteIframeAttached");
return;
}
// Attach the iframe data to the ExecutionContextData.
raw_ec_data->iframe_attribution_data = iframe_attribution_data;
// Create the RemoteFrameData reference to this context.
auto* parent_process_data =
ProcessData::GetOrCreate(frame_node->parent_frame_node()->process_node());
std::unique_ptr<RemoteFrameData> rf_data = std::make_unique<RemoteFrameData>(
parent_process_data, remote_frame_token, raw_ec_data);
// Commit the objects.
data_store_->Pass(std::move(rf_data));
if (ec_data)
data_store_->Pass(std::move(ec_data));
}
void V8ContextTracker::OnRemoteIframeDetachedImpl(
FrameNodeImpl* parent_frame_node,
const blink::RemoteFrameToken& remote_frame_token) {
DCHECK_ON_GRAPH_SEQUENCE(parent_frame_node->graph());
// Look up the RemoteFrameData. This can fail because the notification to
// clean up RemoteFrameData can race with process death, so ignore the message
// if the data has already been cleaned up.
auto* rf_data = data_store_->Get(remote_frame_token);
if (!rf_data)
return;
data_store_->Destroy(remote_frame_token);
}
} // namespace v8_memory
......
......@@ -8,6 +8,7 @@
#include <memory>
#include "base/optional.h"
#include "base/util/type_safety/pass_key.h"
#include "components/performance_manager/public/execution_context/execution_context.h"
#include "components/performance_manager/public/graph/graph.h"
#include "components/performance_manager/public/graph/graph_registered.h"
......@@ -17,6 +18,10 @@
#include "third_party/blink/public/common/tokens/tokens.h"
namespace performance_manager {
class FrameNodeImpl;
class ProcessNodeImpl;
namespace v8_memory {
// Forward declaration.
......@@ -117,6 +122,87 @@ class V8ContextTracker
const V8ContextState* GetV8ContextState(
const blink::V8ContextToken& token) const;
//////////////////////////////////////////////////////////////////////////////
// The following functions handle inbound IPC, and are only meant to be
// called from ProcessNodeImpl and FrameNodeImpl (hence the use of PassKey).
// Notifies the context tracker of a V8Context being created in a renderer
// process. If the context is associated with an ExecutionContext (EC) then
// |description.execution_context_token| will be provided. If the EC is a
// frame, and the parent of that frame is also in the same process, then
// |iframe_attribution_data| will be provided, otherwise these will be empty.
// In the case where they are empty the iframe data will be provided by a
// separate call to OnIframeAttached() from the process hosting the
// parent frame. See the V8ContextWorldType enum for a description of the
// relationship between world types, world names and execution contexts.
void OnV8ContextCreated(
util::PassKey<ProcessNodeImpl> key,
ProcessNodeImpl* process_node,
const V8ContextDescription& description,
const base::Optional<IframeAttributionData>& iframe_attribution_data);
// Notifies the tracker that a V8Context is now detached from its associated
// ExecutionContext (if one was provided during OnV8ContextCreated). If the
// context stays detached for a long time this is indicative of a Javascript
// leak, with the context being kept alive by a stray reference from another
// context. All ExecutionContext-associated V8Contexts will have this method
// called before they are destroyed, and it will not be called for other
// V8Contexts (they are never considered detached).
void OnV8ContextDetached(util::PassKey<ProcessNodeImpl> key,
ProcessNodeImpl* process_node,
const blink::V8ContextToken& v8_context_token);
// Notifies the tracker that a V8Context has been garbage collected. This will
// only be called after OnV8ContextDetached if the OnV8ContextCreated had a
// non-empty |execution_context_token|.
void OnV8ContextDestroyed(util::PassKey<ProcessNodeImpl> key,
ProcessNodeImpl* process_node,
const blink::V8ContextToken& v8_context_token);
// Notifies the tracker that a RemoteFrame child with a LocalFrame parent was
// created in a renderer, providing the iframe.id and iframe.src from the
// parent point of view. This will decorate the ExecutionContextData of the
// appropriate child frame. We require the matching OnRemoteIframeDetached to
// be called for bookkeeping. This should only be called once for a given
// |remote_frame_token|.
void OnRemoteIframeAttached(
util::PassKey<FrameNodeImpl> key,
FrameNodeImpl* parent_frame_node,
const blink::RemoteFrameToken& remote_frame_token,
const IframeAttributionData& iframe_attribution_data);
// TODO(chrisha): Add OnRemoteIframeAttributesChanged support.
// Notifies the tracker that a RemoteFrame child with a LocalFrame parent was
// detached from an iframe element in a renderer. This is used to cleanup
// iframe data that is being tracked due to a previous call to
// OnIframeAttached, unless the data was adopted by a call to
// OnV8ContextCreated. Should only be called once for a given
// |remote_frame_token|, and only after a matching "OnRemoteIframeAttached"
// call.
void OnRemoteIframeDetached(
util::PassKey<FrameNodeImpl> key,
FrameNodeImpl* parent_frame_node,
const blink::RemoteFrameToken& remote_frame_token);
//////////////////////////////////////////////////////////////////////////////
// The following functions are for testing only.
void OnRemoteIframeAttachedForTesting(
FrameNodeImpl* frame_node,
const blink::RemoteFrameToken& remote_frame_token,
const IframeAttributionData& iframe_attribution_data);
void OnRemoteIframeDetachedForTesting(
FrameNodeImpl* parent_frame_node,
const blink::RemoteFrameToken& remote_frame_token);
// System wide metrics.
size_t GetExecutionContextCountForTesting() const;
size_t GetV8ContextCountForTesting() const;
size_t GetDestroyedExecutionContextCountForTesting() const;
size_t GetDetachedV8ContextCountForTesting() const;
private:
// Implementation of execution_context::ExecutionContextObserverDefaultImpl.
void OnBeforeExecutionContextRemoved(
......@@ -138,6 +224,21 @@ class V8ContextTracker
// Implementation of ProcessNode::ObserverDefaultImpl.
void OnBeforeProcessNodeRemoved(const ProcessNode* node) final;
// OnIframeAttached bounces over to the UI thread to
// lookup the RenderFrameHost* associated with a given RemoteFrameToken,
// landing here.
void OnRemoteIframeAttachedImpl(
mojo::ReportBadMessageCallback bad_message_callback,
FrameNodeImpl* frame_node,
const blink::RemoteFrameToken& remote_frame_token,
const IframeAttributionData& iframe_attribution_data);
// To maintain strict ordering with OnRemoteIframeAttached events, detached
// events also detour through the UI thread to arrive here.
void OnRemoteIframeDetachedImpl(
FrameNodeImpl* parent_frame_node,
const blink::RemoteFrameToken& remote_frame_token);
// Stores Chrome-wide data store used by the tracking.
std::unique_ptr<DataStore> data_store_;
};
......
......@@ -76,6 +76,12 @@ bool IsWorkletToken(const blink::ExecutionContextToken& token) {
token.Is<blink::PaintWorkletToken>();
}
bool IsWorkerToken(const blink::ExecutionContextToken& token) {
return token.Is<blink::DedicatedWorkerToken>() ||
token.Is<blink::ServiceWorkerToken>() ||
token.Is<blink::SharedWorkerToken>();
}
const execution_context::ExecutionContext* GetExecutionContext(
const blink::ExecutionContextToken& token,
Graph* graph) {
......
......@@ -43,6 +43,10 @@ bool IsValidExtensionId(const std::string& s) WARN_UNUSED_RESULT;
bool IsWorkletToken(const blink::ExecutionContextToken& token)
WARN_UNUSED_RESULT;
// Returns true if an ExecutionContextToken corresponds to a worklet.
bool IsWorkerToken(const blink::ExecutionContextToken& token)
WARN_UNUSED_RESULT;
// Looks up the execution context corresponding to the given token. Note that
// the ExecutionContextRegistry must be installed on the graph.
const execution_context::ExecutionContext* GetExecutionContext(
......
......@@ -5,6 +5,7 @@
#include "components/performance_manager/v8_memory/v8_context_tracker_internal.h"
#include "base/check.h"
#include "components/performance_manager/v8_memory/v8_context_tracker_helpers.h"
namespace performance_manager {
namespace v8_memory {
......@@ -16,7 +17,7 @@ namespace internal {
ExecutionContextData::ExecutionContextData(
ProcessData* process_data,
const blink::ExecutionContextToken& token,
const base::Optional<IframeAttributionData> iframe_attribution_data)
const base::Optional<IframeAttributionData>& iframe_attribution_data)
: ExecutionContextState(token, iframe_attribution_data),
process_data_(process_data) {}
......@@ -67,6 +68,14 @@ bool ExecutionContextData::MarkDestroyed(util::PassKey<ProcessData>) {
return true;
}
bool ExecutionContextData::MarkMainWorldSeen(
util::PassKey<V8ContextTrackerDataStore>) {
if (main_world_seen_)
return false;
main_world_seen_ = true;
return true;
}
////////////////////////////////////////////////////////////////////////////////
// RemoteFrameData implementation:
......@@ -108,7 +117,12 @@ V8ContextData::V8ContextData(ProcessData* process_data,
: V8ContextState(description, execution_context_data),
process_data_(process_data) {
DCHECK(process_data);
DCHECK_EQ(static_cast<bool>(execution_context_data),
static_cast<bool>(description.execution_context_token));
if (execution_context_data) {
DCHECK_EQ(execution_context_data->GetToken(),
description.execution_context_token.value());
// These must be same process.
DCHECK_EQ(process_data, execution_context_data->process_data());
execution_context_data->IncrementV8ContextCount(PassKey());
......@@ -142,6 +156,25 @@ bool V8ContextData::MarkDetached(util::PassKey<ProcessData>) {
return true;
}
bool V8ContextData::IsMainV8Context() const {
auto* ec_data = GetExecutionContextData();
if (!ec_data)
return false;
// ExecutionContexts hosting worklets have no main world (there can be many
// worklets sharing an ExecutionContext).
if (IsWorkletToken(ec_data->GetToken()))
return false;
// We've already checked sane combinations of ExecutionContextToken types and
// world types in ValidateV8ContextDescription, so don't need to be overly
// thorough here.
// Only main frames and workers can be "main" contexts.
auto world_type = description.world_type;
return world_type == V8ContextWorldType::kMain ||
world_type == V8ContextWorldType::kWorkerOrWorklet;
}
////////////////////////////////////////////////////////////////////////////////
// ProcessData implementation:
......@@ -284,11 +317,17 @@ void V8ContextTrackerDataStore::Pass(std::unique_ptr<RemoteFrameData> rf_data) {
DCHECK(result.second);
}
void V8ContextTrackerDataStore::Pass(std::unique_ptr<V8ContextData> v8_data) {
bool V8ContextTrackerDataStore::Pass(std::unique_ptr<V8ContextData> v8_data) {
DCHECK(v8_data.get());
auto* ec_data = v8_data->GetExecutionContextData();
if (ec_data && v8_data->IsMainV8Context()) {
if (!ec_data->MarkMainWorldSeen(PassKey()))
return false;
}
v8_data->process_data()->Add(PassKey(), v8_data.get());
auto result = global_v8_context_datas_.insert(std::move(v8_data));
DCHECK(result.second);
return true;
}
ExecutionContextData* V8ContextTrackerDataStore::Get(
......@@ -324,12 +363,14 @@ void V8ContextTrackerDataStore::MarkDestroyed(ExecutionContextData* ec_data) {
}
}
void V8ContextTrackerDataStore::MarkDetached(V8ContextData* v8_data) {
bool V8ContextTrackerDataStore::MarkDetached(V8ContextData* v8_data) {
DCHECK(v8_data);
if (v8_data->process_data()->MarkDetached(PassKey(), v8_data)) {
DCHECK_LT(detached_v8_context_count_, global_v8_context_datas_.size());
++detached_v8_context_count_;
return true;
}
return false;
}
void V8ContextTrackerDataStore::Destroy(
......
......@@ -36,6 +36,7 @@ class ExecutionContextData;
class ProcessData;
class RemoteFrameData;
class V8ContextData;
class V8ContextTrackerDataStore;
// A comparator for "Data" objects that compares by token.
template <typename DataType, typename TokenType>
......@@ -68,7 +69,7 @@ class ExecutionContextData : public base::LinkNode<ExecutionContextData>,
ExecutionContextData(
ProcessData* process_data,
const blink::ExecutionContextToken& token,
const base::Optional<IframeAttributionData> iframe_attribution_data);
const base::Optional<IframeAttributionData>& iframe_attribution_data);
ExecutionContextData& operator=(const ExecutionContextData&) = delete;
~ExecutionContextData() override;
......@@ -76,6 +77,7 @@ class ExecutionContextData : public base::LinkNode<ExecutionContextData>,
ProcessData* process_data() const { return process_data_; }
RemoteFrameData* remote_frame_data() { return remote_frame_data_; }
size_t v8_context_count() const { return v8_context_count_; }
bool main_world_seen() const { return main_world_seen_; }
// For consistency, all Data objects have a GetToken() function.
const blink::ExecutionContextToken& GetToken() const { return token; }
......@@ -105,6 +107,13 @@ class ExecutionContextData : public base::LinkNode<ExecutionContextData>,
// if it was already destroyed.
WARN_UNUSED_RESULT bool MarkDestroyed(util::PassKey<ProcessData>);
// Marks the main world as having been seen. Returns true if the state changed
// and false if this had already occurred. This is called when the
// V8ContextData is passed to the data store and can prevent it from
// succeeding.
WARN_UNUSED_RESULT bool MarkMainWorldSeen(
util::PassKey<V8ContextTrackerDataStore>);
private:
ProcessData* const process_data_;
......@@ -112,6 +121,10 @@ class ExecutionContextData : public base::LinkNode<ExecutionContextData>,
// The count of V8ContextDatas keeping this object alive.
size_t v8_context_count_ = 0;
// True if a main world V8Context has been seen for this EC. Can only ever
// toggle from false to true.
bool main_world_seen_ = false;
};
////////////////////////////////////////////////////////////////////////////////
......@@ -189,6 +202,12 @@ class V8ContextData : public base::LinkNode<V8ContextData>,
// if it was already detached.
WARN_UNUSED_RESULT bool MarkDetached(util::PassKey<ProcessData>);
// Returns true if this is the "main" V8Context for an ExecutionContext.
// This will return true if |GetExecutionContextData()| is a frame and
// |description.world_type| is kMain, or if |GetExecutionContextData()| is a
// worker and |description.world_type| is a kWorkerOrWorklet.
bool IsMainV8Context() const;
private:
ProcessData* const process_data_;
};
......@@ -290,16 +309,17 @@ class V8ContextTrackerDataStore {
// |ec_data| to the impl that "ShouldDestroy" should return false.
void Pass(std::unique_ptr<ExecutionContextData> ec_data);
void Pass(std::unique_ptr<RemoteFrameData> rf_data);
void Pass(std::unique_ptr<V8ContextData> v8_data);
WARN_UNUSED_RESULT bool Pass(std::unique_ptr<V8ContextData> v8_data);
// Looks up owned objects by token.
ExecutionContextData* Get(const blink::ExecutionContextToken& token);
RemoteFrameData* Get(const blink::RemoteFrameToken& token);
V8ContextData* Get(const blink::V8ContextToken& token);
// For marking objects as detached/destroyed.
// For marking objects as detached/destroyed. "MarkDetached" returns true if
// the object was not previously detached, false otherwise.
void MarkDestroyed(ExecutionContextData* ec_data);
void MarkDetached(V8ContextData* v8_data);
WARN_UNUSED_RESULT bool MarkDetached(V8ContextData* v8_data);
// Destroys objects by token. They must exist ("Get" should return non
// nullptr).
......
......@@ -42,6 +42,14 @@ class V8ContextTrackerInternalTest : public GraphTestHarness {
MockSinglePageWithMultipleProcessesGraph mock_graph_;
};
V8ContextDescription MakeMatchingV8ContextDescription(
ExecutionContextData* ec_data) {
DCHECK(ec_data);
V8ContextDescription v8_desc;
v8_desc.execution_context_token = ec_data->GetToken();
return v8_desc;
}
using V8ContextTrackerInternalDeathTest = V8ContextTrackerInternalTest;
} // namespace
......@@ -57,6 +65,35 @@ TEST_F(V8ContextTrackerInternalDeathTest,
EXPECT_DCHECK_DEATH(data_store()->Pass(std::move(ec_data)));
}
TEST_F(V8ContextTrackerInternalDeathTest,
MultipleMainWorldsForExecutionContextFails) {
auto* process_data = ProcessData::GetOrCreate(
static_cast<ProcessNodeImpl*>(mock_graph_.process.get()));
std::unique_ptr<ExecutionContextData> ec_data =
std::make_unique<ExecutionContextData>(
process_data, mock_graph_.frame->frame_token(), base::nullopt);
EXPECT_TRUE(ec_data->ShouldDestroy());
EXPECT_FALSE(ec_data->main_world_seen());
V8ContextDescription v8_desc;
v8_desc.world_type = V8ContextWorldType::kMain;
v8_desc.execution_context_token = ec_data->GetToken();
std::unique_ptr<V8ContextData> v8_data =
std::make_unique<V8ContextData>(process_data, v8_desc, ec_data.get());
EXPECT_TRUE(v8_data->IsMainV8Context());
EXPECT_TRUE(data_store()->Pass(std::move(v8_data)));
EXPECT_TRUE(ec_data->main_world_seen());
v8_desc.token = blink::V8ContextToken();
v8_data =
std::make_unique<V8ContextData>(process_data, v8_desc, ec_data.get());
EXPECT_TRUE(v8_data->IsMainV8Context());
EXPECT_FALSE(data_store()->Pass(std::move(v8_data)));
data_store()->Pass(std::move(ec_data));
}
TEST_F(V8ContextTrackerInternalDeathTest, SameProcessRemoteFrameDataExplodes) {
auto* process_data = ProcessData::GetOrCreate(
static_cast<ProcessNodeImpl*>(mock_graph_.process.get()));
......@@ -78,9 +115,10 @@ TEST_F(V8ContextTrackerInternalDeathTest, CrossProcessV8ContextDataExplodes) {
std::make_unique<ExecutionContextData>(
process_data, mock_graph_.frame->frame_token(), base::nullopt);
std::unique_ptr<V8ContextData> v8_data;
EXPECT_DCHECK_DEATH(
v8_data = std::make_unique<V8ContextData>(
other_process_data, V8ContextDescription(), ec_data.get()));
EXPECT_DCHECK_DEATH(v8_data = std::make_unique<V8ContextData>(
other_process_data,
MakeMatchingV8ContextDescription(ec_data.get()),
ec_data.get()));
}
TEST_F(V8ContextTrackerInternalTest, ExecutionContextDataShouldDestroy) {
......@@ -106,14 +144,16 @@ TEST_F(V8ContextTrackerInternalTest, ExecutionContextDataShouldDestroy) {
// Adding a V8ContextData should also keep the object alive.
std::unique_ptr<V8ContextData> v8_data1 = std::make_unique<V8ContextData>(
process_data, V8ContextDescription(), ec_data.get());
process_data, MakeMatchingV8ContextDescription(ec_data.get()),
ec_data.get());
EXPECT_TRUE(ec_data->remote_frame_data());
EXPECT_EQ(1u, ec_data->v8_context_count());
EXPECT_FALSE(ec_data->ShouldDestroy());
// Add another V8ContextData.
std::unique_ptr<V8ContextData> v8_data2 = std::make_unique<V8ContextData>(
process_data, V8ContextDescription(), ec_data.get());
process_data, MakeMatchingV8ContextDescription(ec_data.get()),
ec_data.get());
EXPECT_TRUE(ec_data->remote_frame_data());
EXPECT_EQ(2u, ec_data->v8_context_count());
EXPECT_FALSE(ec_data->ShouldDestroy());
......@@ -194,13 +234,14 @@ TEST_F(V8ContextTrackerInternalTest,
// Create a V8ContextData.
std::unique_ptr<V8ContextData> v8_data = std::make_unique<V8ContextData>(
process_data, V8ContextDescription(), ec_data.get());
process_data, MakeMatchingV8ContextDescription(ec_data.get()),
ec_data.get());
auto* raw_v8_data = v8_data.get();
EXPECT_FALSE(v8_data->IsTracked());
// Pass both of these to the Impl.
data_store()->Pass(std::move(ec_data));
data_store()->Pass(std::move(v8_data));
EXPECT_TRUE(data_store()->Pass(std::move(v8_data)));
EXPECT_TRUE(raw_ec_data->IsTracked());
EXPECT_TRUE(raw_v8_data->IsTracked());
EXPECT_EQ(1u, data_store()->GetExecutionContextDataCount());
......@@ -231,15 +272,17 @@ TEST_F(V8ContextTrackerInternalTest, ContextCounts) {
auto* raw_ec_data = ec_data.get();
std::unique_ptr<V8ContextData> v8_data1 = std::make_unique<V8ContextData>(
process_data, V8ContextDescription(), ec_data.get());
process_data, MakeMatchingV8ContextDescription(ec_data.get()),
ec_data.get());
auto* raw_v8_data1 = v8_data1.get();
std::unique_ptr<V8ContextData> v8_data2 = std::make_unique<V8ContextData>(
process_data, V8ContextDescription(), ec_data.get());
process_data, MakeMatchingV8ContextDescription(ec_data.get()),
ec_data.get());
data_store()->Pass(std::move(ec_data));
data_store()->Pass(std::move(v8_data1));
data_store()->Pass(std::move(v8_data2));
EXPECT_TRUE(data_store()->Pass(std::move(v8_data1)));
EXPECT_TRUE(data_store()->Pass(std::move(v8_data2)));
EXPECT_EQ(1u, data_store()->GetExecutionContextDataCount());
EXPECT_EQ(0u, data_store()->GetDestroyedExecutionContextDataCount());
......@@ -255,8 +298,9 @@ TEST_F(V8ContextTrackerInternalTest, ContextCounts) {
EXPECT_TRUE(raw_ec_data->destroyed);
EXPECT_FALSE(raw_v8_data1->detached);
data_store()->MarkDetached(raw_v8_data1);
EXPECT_TRUE(data_store()->MarkDetached(raw_v8_data1));
EXPECT_TRUE(raw_v8_data1->detached);
EXPECT_FALSE(data_store()->MarkDetached(raw_v8_data1));
EXPECT_EQ(1u, data_store()->GetExecutionContextDataCount());
EXPECT_EQ(1u, data_store()->GetDestroyedExecutionContextDataCount());
......@@ -326,11 +370,11 @@ class V8ContextTrackerInternalTearDownOrderTest
// Create a couple V8ContextDatas.
std::unique_ptr<V8ContextData> v8_data = std::make_unique<V8ContextData>(
process_data_, V8ContextDescription(), ec_data_);
data_store()->Pass(std::move(v8_data));
v8_data = std::make_unique<V8ContextData>(process_data_,
V8ContextDescription(), ec_data_);
data_store()->Pass(std::move(v8_data));
process_data_, MakeMatchingV8ContextDescription(ec_data_), ec_data_);
EXPECT_TRUE(data_store()->Pass(std::move(v8_data)));
v8_data = std::make_unique<V8ContextData>(
process_data_, MakeMatchingV8ContextDescription(ec_data_), ec_data_);
EXPECT_TRUE(data_store()->Pass(std::move(v8_data)));
EXPECT_EQ(1u, data_store()->GetExecutionContextDataCount());
EXPECT_EQ(1u, data_store()->GetRemoteFrameDataCount());
......
// 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/v8_context_tracker.h"
#include <memory>
#include <string>
#include <utility>
#include "base/optional.h"
#include "base/stl_util.h"
#include "base/test/gtest_util.h"
#include "components/performance_manager/execution_context/execution_context_registry_impl.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/test_support/graph_test_harness.h"
#include "components/performance_manager/test_support/mock_graphs.h"
#include "components/performance_manager/v8_memory/v8_context_tracker_types.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 {
namespace {
using ::testing::AllOf;
using ::testing::Eq;
using ::testing::Property;
// Fake iframe attributes.
const std::string kIframeId("iframe1");
const std::string kIframeSrc("http://www.fakesite.com/");
// Some tokens for identifying frames and contexts.
const blink::V8ContextToken kFrameMainWorld;
const blink::V8ContextToken kChildFrameMainWorld;
const blink::V8ContextToken kFrameIsolatedWorld;
const blink::V8ContextToken kChildFrameIsolatedWorld;
const blink::RemoteFrameToken kChildFrameRemoteToken;
// A fake extension ID.
const char kExtensionId[] = "hickenlcldoffnfidnljacmfeielknka";
// Helper function for creating an IframeAttributionData.
const IframeAttributionData& GetFakeIframeAttributionData() {
static const IframeAttributionData kData =
IframeAttributionData::Create(kIframeId, kIframeSrc);
return kData;
}
class V8ContextTrackerTest : public GraphTestHarness {
public:
// Small PassToGraph helper that returns a raw pointer to the object that
// became graph owned. Helps write tidy constructors.
template <typename DerivedType>
DerivedType* PassToGraph(std::unique_ptr<DerivedType> graph_owned) {
DerivedType* object = graph_owned.get();
graph()->PassToGraph(std::move(graph_owned));
return object;
}
V8ContextTrackerTest()
: registry(
PassToGraph(std::make_unique<
execution_context::ExecutionContextRegistryImpl>())),
tracker(PassToGraph(std::make_unique<V8ContextTracker>())),
mock_graph(graph()) {}
~V8ContextTrackerTest() override = default;
execution_context::ExecutionContextRegistry* const registry = nullptr;
V8ContextTracker* const tracker = nullptr;
MockSinglePageWithMultipleProcessesGraph mock_graph;
};
using V8ContextTrackerDeathTest = V8ContextTrackerTest;
auto CountsMatch(size_t v8_context_count, size_t execution_context_count) {
return AllOf(Property(&V8ContextTracker::GetV8ContextCountForTesting,
Eq(v8_context_count)),
Property(&V8ContextTracker::GetExecutionContextCountForTesting,
Eq(execution_context_count)));
}
auto DetachedCountsMatch(size_t detached_v8_context_count,
size_t destroyed_execution_context_count) {
return AllOf(
Property(&V8ContextTracker::GetDetachedV8ContextCountForTesting,
Eq(detached_v8_context_count)),
Property(&V8ContextTracker::GetDestroyedExecutionContextCountForTesting,
Eq(destroyed_execution_context_count)));
}
} // namespace
TEST_F(V8ContextTrackerDeathTest, MissingExecutionContextForMainFrameExplodes) {
// A main-frame should not have iframe data, and it must have an execution
// context token. So this should fail.
EXPECT_DCHECK_DEATH(tracker->OnV8ContextCreated(
ProcessNodeImpl::CreatePassKeyForTesting(), mock_graph.process.get(),
V8ContextDescription::Create(
/* token */ kFrameMainWorld,
/* world_type */ V8ContextWorldType::kMain,
/* world_name */ base::nullopt,
/* execution_context_token */ base::nullopt),
/* iframe_attribution_data */ base::nullopt));
}
TEST_F(V8ContextTrackerDeathTest, DoubleCreationExplodes) {
auto v8_desc = V8ContextDescription::Create(
/* token */ kFrameMainWorld,
/* world_type */ V8ContextWorldType::kMain,
/* world_name */ base::nullopt,
/* execution_context_token */ mock_graph.frame->frame_token());
tracker->OnV8ContextCreated(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.process.get(), v8_desc, base::nullopt);
// Trying to create the context a second time should explode.
EXPECT_DCHECK_DEATH(tracker->OnV8ContextCreated(
ProcessNodeImpl::CreatePassKeyForTesting(), mock_graph.process.get(),
v8_desc, base::nullopt));
}
TEST_F(V8ContextTrackerDeathTest, MissingContextExplodes) {
// There was no OnV8ContextCreated called first, so this should explode.
EXPECT_DCHECK_DEATH(
tracker->OnV8ContextDetached(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.process.get(), kFrameMainWorld));
// Similarly with a destroyed notification.
EXPECT_DCHECK_DEATH(
tracker->OnV8ContextDestroyed(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.process.get(), kFrameMainWorld));
}
TEST_F(V8ContextTrackerDeathTest, DoubleRemoteFrameCreatedExplodes) {
tracker->OnRemoteIframeAttachedForTesting(mock_graph.child_frame.get(),
kChildFrameRemoteToken,
GetFakeIframeAttributionData());
EXPECT_DCHECK_DEATH(tracker->OnRemoteIframeAttachedForTesting(
mock_graph.child_frame.get(), kChildFrameRemoteToken,
GetFakeIframeAttributionData()));
}
TEST_F(V8ContextTrackerDeathTest, IframeAttributionDataForMainFrameExplodes) {
EXPECT_DCHECK_DEATH(tracker->OnV8ContextCreated(
ProcessNodeImpl::CreatePassKeyForTesting(), mock_graph.process.get(),
V8ContextDescription::Create(
/* token */ kFrameMainWorld,
/* world_type */ V8ContextWorldType::kMain,
/* world_name */ base::nullopt,
/* execution_context_token */ mock_graph.frame->frame_token()),
GetFakeIframeAttributionData()));
}
TEST_F(V8ContextTrackerDeathTest, IframeAttributionDataForInProcessChildFrame) {
// Create a child of mock_graph.frame that is in the same process.
TestNodeWrapper<FrameNodeImpl> child2_frame(graph()->CreateFrameNodeAutoId(
mock_graph.process.get(), mock_graph.page.get(), mock_graph.frame.get(),
3));
// Trying to provide IFrameAttribution data via a RemoteFrameAttached
// notification should explode because |child2_frame| is in the same process
// as its parent.
EXPECT_DCHECK_DEATH(tracker->OnRemoteIframeAttachedForTesting(
child2_frame.get(), blink::RemoteFrameToken(),
GetFakeIframeAttributionData()));
// This should succeed because iframe data is provided.
tracker->OnV8ContextCreated(
ProcessNodeImpl::CreatePassKeyForTesting(), mock_graph.process.get(),
V8ContextDescription::Create(
/* token */ kChildFrameMainWorld,
/* world_type */ V8ContextWorldType::kMain,
/* world_name */ base::nullopt,
/* execution_context_token */ child2_frame->frame_token()),
GetFakeIframeAttributionData());
}
TEST_F(V8ContextTrackerDeathTest,
NoIframeAttributionDataForInProcessChildFrameExplodes) {
// Create a child of mock_graph.frame that is in the same process.
TestNodeWrapper<FrameNodeImpl> child2_frame(graph()->CreateFrameNodeAutoId(
mock_graph.process.get(), mock_graph.page.get(), mock_graph.frame.get(),
3));
// This should explode because synchronous iframe data is expected, but not
// provided.
EXPECT_DCHECK_DEATH(tracker->OnV8ContextCreated(
ProcessNodeImpl::CreatePassKeyForTesting(), mock_graph.process.get(),
V8ContextDescription::Create(
/* token */ kChildFrameMainWorld,
/* world_type */ V8ContextWorldType::kMain,
/* world_name */ base::nullopt,
/* execution_context_token */ child2_frame->frame_token()),
/* iframe_attribution_data */ base::nullopt));
}
TEST_F(V8ContextTrackerDeathTest, MultipleMainContextsForExecutionContext) {
// Create a main-frame with two main worlds.
tracker->OnV8ContextCreated(
ProcessNodeImpl::CreatePassKeyForTesting(), mock_graph.process.get(),
V8ContextDescription::Create(
/* token */ kFrameMainWorld,
/* world_type */ V8ContextWorldType::kMain,
/* world_name */ base::nullopt,
/* execution_context_token */ mock_graph.frame->frame_token()),
/* iframe_attribution_data */ base::nullopt);
EXPECT_DCHECK_DEATH(tracker->OnV8ContextCreated(
ProcessNodeImpl::CreatePassKeyForTesting(), mock_graph.process.get(),
V8ContextDescription::Create(
/* token */ blink::V8ContextToken(),
/* world_type */ V8ContextWorldType::kMain,
/* world_name */ base::nullopt,
/* execution_context_token */ mock_graph.frame->frame_token()),
/* iframe_attribution_data */ base::nullopt));
}
TEST_F(V8ContextTrackerTest, NormalV8ContextLifecycleWithExecutionContext) {
EXPECT_THAT(tracker, CountsMatch(0, 0));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
tracker->OnV8ContextCreated(
ProcessNodeImpl::CreatePassKeyForTesting(), mock_graph.process.get(),
V8ContextDescription::Create(
/* token */ kFrameMainWorld,
/* world_type */ V8ContextWorldType::kMain,
/* world_name */ base::nullopt,
/* execution_context_token */ mock_graph.frame->frame_token()),
/* iframe_attribution_data */ base::nullopt);
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
tracker->OnV8ContextDetached(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.process.get(), kFrameMainWorld);
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
tracker->OnV8ContextDestroyed(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.process.get(), kFrameMainWorld);
EXPECT_THAT(tracker, CountsMatch(0, 0));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
TEST_F(V8ContextTrackerTest, NormalV8ContextLifecycleNoExecutionContext) {
EXPECT_THAT(tracker, CountsMatch(0, 0));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
tracker->OnV8ContextCreated(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.process.get(),
V8ContextDescription::Create(
/* token */ kFrameMainWorld,
/* world_type */ V8ContextWorldType::kRegExp,
/* world_name */ base::nullopt,
/* execution_context_token */ base::nullopt),
/* iframe_attribution_data */ base::nullopt);
EXPECT_THAT(tracker, CountsMatch(1, 0));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
tracker->OnV8ContextDetached(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.process.get(), kFrameMainWorld);
EXPECT_THAT(tracker, CountsMatch(1, 0));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 0));
tracker->OnV8ContextDestroyed(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.process.get(), kFrameMainWorld);
EXPECT_THAT(tracker, CountsMatch(0, 0));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
TEST_F(V8ContextTrackerTest, MultipleV8ContextsForExecutionContext) {
EXPECT_THAT(tracker, CountsMatch(0, 0));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
// Create a main-frame main world context, and an isolated world.
{
SCOPED_TRACE("");
tracker->OnV8ContextCreated(
ProcessNodeImpl::CreatePassKeyForTesting(), mock_graph.process.get(),
V8ContextDescription::Create(
/* token */ kFrameMainWorld,
/* world_type */ V8ContextWorldType::kMain,
/* world_name */ base::nullopt,
/* execution_context_token */ mock_graph.frame->frame_token()),
/* iframe_attribution_data */ base::nullopt);
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
{
SCOPED_TRACE("");
tracker->OnV8ContextCreated(
ProcessNodeImpl::CreatePassKeyForTesting(), mock_graph.process.get(),
V8ContextDescription::Create(
/* token */ kFrameIsolatedWorld,
/* world_type */ V8ContextWorldType::kExtension,
/* world_name */ kExtensionId,
/* execution_context_token */ mock_graph.frame->frame_token()),
/* iframe_attribution_data */ base::nullopt);
EXPECT_THAT(tracker, CountsMatch(2, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
// Create a child-frame main world context, and an isolated world. This child
// is cross-process so expects no iframe data at creation.
{
SCOPED_TRACE("");
tracker->OnV8ContextCreated(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.other_process.get(),
V8ContextDescription::Create(
/* token */ kChildFrameMainWorld,
/* world_type */ V8ContextWorldType::kMain,
/* world_name */ base::nullopt,
/* execution_context_token */
mock_graph.child_frame->frame_token()),
/* iframe_attribution_data */ base::nullopt);
EXPECT_THAT(tracker, CountsMatch(3, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
{
SCOPED_TRACE("");
tracker->OnV8ContextCreated(
ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.other_process.get(),
V8ContextDescription::Create(
/* token */ kChildFrameIsolatedWorld,
/* world_type */ V8ContextWorldType::kExtension,
/* world_name */ kExtensionId,
/* execution_context_token */
mock_graph.child_frame->frame_token()),
/* iframe_attribution_data */ base::nullopt);
EXPECT_THAT(tracker, CountsMatch(4, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
// Provide iframe data for the child frame.
{
SCOPED_TRACE("");
tracker->OnRemoteIframeAttachedForTesting(mock_graph.child_frame.get(),
kChildFrameRemoteToken,
GetFakeIframeAttributionData());
EXPECT_THAT(tracker, CountsMatch(4, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
// Detach the child frame contexts. The associated ExecutionContext only
// becomes detached when the primary (main world) V8Context is detached, not
// when an isolated world detaches.
{
SCOPED_TRACE("");
tracker->OnV8ContextDetached(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.other_process.get(),
kChildFrameIsolatedWorld);
EXPECT_THAT(tracker, CountsMatch(4, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 0));
}
{
SCOPED_TRACE("");
tracker->OnV8ContextDetached(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.other_process.get(),
kChildFrameMainWorld);
EXPECT_THAT(tracker, CountsMatch(4, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(2, 1));
}
// Destroy the child frame main world context.
{
SCOPED_TRACE("");
tracker->OnV8ContextDestroyed(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.other_process.get(),
kChildFrameMainWorld);
EXPECT_THAT(tracker, CountsMatch(3, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
}
// Detach the main frame contexts, main and isolated.
{
SCOPED_TRACE("");
tracker->OnV8ContextDetached(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.process.get(), kFrameMainWorld);
EXPECT_THAT(tracker, CountsMatch(3, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(2, 2));
}
{
SCOPED_TRACE("");
tracker->OnV8ContextDetached(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.process.get(), kFrameIsolatedWorld);
EXPECT_THAT(tracker, CountsMatch(3, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(3, 2));
}
// Destroy the main frame isolated world.
{
SCOPED_TRACE("");
tracker->OnV8ContextDestroyed(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.process.get(),
kFrameIsolatedWorld);
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(2, 2));
}
// Destroy the child frame isolated world..
{
SCOPED_TRACE("");
tracker->OnV8ContextDestroyed(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.other_process.get(),
kChildFrameIsolatedWorld);
EXPECT_THAT(tracker, CountsMatch(1, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 2));
}
// Destroy the remote iframe reference to the child frame, which should
// finally tear down the ExecutionContext as well.
{
SCOPED_TRACE("");
tracker->OnRemoteIframeDetachedForTesting(mock_graph.frame.get(),
kChildFrameRemoteToken);
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
}
}
TEST_F(V8ContextTrackerTest, AllEventOrders) {
EXPECT_THAT(tracker, CountsMatch(0, 0));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
// Create a main frame. This exists for the duration of the test, and we
// repeatedly attach/detach child frames to it.
tracker->OnV8ContextCreated(
ProcessNodeImpl::CreatePassKeyForTesting(), mock_graph.process.get(),
V8ContextDescription::Create(
/* token */ kFrameMainWorld,
/* world_type */ V8ContextWorldType::kMain,
/* world_name */ base::nullopt,
/* execution_context_token */ mock_graph.frame->frame_token()),
/* iframe_attribution_data */ base::nullopt);
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
// Bind lambdas for all the events that can occur to a frame in its lifetime.
// This test will explore all possible valid combinations of these events.
// Creates a child frame V8Context.
auto v8create = [self = this]() {
SCOPED_TRACE("");
self->tracker->OnV8ContextCreated(
ProcessNodeImpl::CreatePassKeyForTesting(),
self->mock_graph.other_process.get(),
V8ContextDescription::Create(
/* token */ kChildFrameMainWorld,
/* world_type */ V8ContextWorldType::kMain,
/* world_name */ base::nullopt,
/* execution_context_token */
self->mock_graph.child_frame->frame_token()),
/* iframe_attribution_data */ base::nullopt);
};
// Detaches a child frame V8Context.
auto v8detach = [self = this]() {
SCOPED_TRACE("");
self->tracker->OnV8ContextDetached(
ProcessNodeImpl::CreatePassKeyForTesting(),
self->mock_graph.other_process.get(), kChildFrameMainWorld);
};
// Destroys a child frame V8Context.
auto v8destroy = [self = this]() {
SCOPED_TRACE("");
self->tracker->OnV8ContextDestroyed(
ProcessNodeImpl::CreatePassKeyForTesting(),
self->mock_graph.other_process.get(), kChildFrameMainWorld);
};
// Attaches a child iframe. This is after frame resolution has occurred so it
// is aimed at the frame that is represented by the remote frame token
// (hence mock_graph.child_frame). The actual "OnRemoteIframeAttached"
// message originally arrives over the parent frame interface.
auto iframeattach = [self = this]() {
SCOPED_TRACE("");
self->tracker->OnRemoteIframeAttachedForTesting(
self->mock_graph.child_frame.get(), kChildFrameRemoteToken,
GetFakeIframeAttributionData());
};
// Detaches a child iframe. This message is sent over the interface associated
// with the parent frame that hosts the child frame (hence mock_graph.frame).
auto iframedetach = [self = this]() {
SCOPED_TRACE("");
self->tracker->OnRemoteIframeDetachedForTesting(
self->mock_graph.frame.get(), kChildFrameRemoteToken);
};
// The following tests look at all 10 possible orderings of the 3 ordered
// V8Context events interleaved with the 2 ordered Iframe events.
{
SCOPED_TRACE("Case 1");
v8create();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
v8detach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
v8destroy();
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
iframeattach();
EXPECT_THAT(tracker, CountsMatch(1, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
iframedetach();
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
{
SCOPED_TRACE("Case 2");
v8create();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
v8detach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
iframeattach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
v8destroy();
EXPECT_THAT(tracker, CountsMatch(1, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 1));
iframedetach();
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
{
SCOPED_TRACE("Case 3");
v8create();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
v8detach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
iframeattach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
iframedetach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
v8destroy();
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
{
SCOPED_TRACE("Case 4");
v8create();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
iframeattach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
v8detach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
v8destroy();
EXPECT_THAT(tracker, CountsMatch(1, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 1));
iframedetach();
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
{
SCOPED_TRACE("Case 5");
v8create();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
iframeattach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
v8detach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
iframedetach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
v8destroy();
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
{
SCOPED_TRACE("Case 6");
v8create();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
iframeattach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
iframedetach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
v8detach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
v8destroy();
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
{
SCOPED_TRACE("Case 7");
iframeattach();
EXPECT_THAT(tracker, CountsMatch(1, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
v8create();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
v8detach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
v8destroy();
EXPECT_THAT(tracker, CountsMatch(1, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 1));
iframedetach();
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
{
SCOPED_TRACE("Case 8");
iframeattach();
EXPECT_THAT(tracker, CountsMatch(1, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
v8create();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
v8detach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
iframedetach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
v8destroy();
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
{
SCOPED_TRACE("Case 9");
iframeattach();
EXPECT_THAT(tracker, CountsMatch(1, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
v8create();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
iframedetach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
v8detach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
v8destroy();
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
{
SCOPED_TRACE("Case 10");
iframeattach();
EXPECT_THAT(tracker, CountsMatch(1, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
iframedetach();
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
v8create();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
v8detach();
EXPECT_THAT(tracker, CountsMatch(2, 2));
EXPECT_THAT(tracker, DetachedCountsMatch(1, 1));
v8destroy();
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
}
}
TEST_F(V8ContextTrackerTest, PublicApi) {
EXPECT_THAT(tracker, CountsMatch(0, 0));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
// Create a main frame.
EXPECT_FALSE(tracker->GetV8ContextState(kFrameMainWorld));
EXPECT_FALSE(
tracker->GetExecutionContextState(mock_graph.frame->frame_token()));
tracker->OnV8ContextCreated(
ProcessNodeImpl::CreatePassKeyForTesting(), mock_graph.process.get(),
V8ContextDescription::Create(
/* token */ kFrameMainWorld,
/* world_type */ V8ContextWorldType::kMain,
/* world_name */ base::nullopt,
/* execution_context_token */ mock_graph.frame->frame_token()),
/* iframe_attribution_data */ base::nullopt);
EXPECT_THAT(tracker, CountsMatch(1, 1));
EXPECT_THAT(tracker, DetachedCountsMatch(0, 0));
const auto* v8_state = tracker->GetV8ContextState(kFrameMainWorld);
ASSERT_TRUE(v8_state);
EXPECT_EQ(kFrameMainWorld, v8_state->description.token);
EXPECT_EQ(V8ContextWorldType::kMain, v8_state->description.world_type);
EXPECT_FALSE(v8_state->description.world_name);
ASSERT_TRUE(v8_state->description.execution_context_token);
EXPECT_EQ(blink::ExecutionContextToken(mock_graph.frame->frame_token()),
v8_state->description.execution_context_token.value());
const auto* ec_state =
tracker->GetExecutionContextState(mock_graph.frame->frame_token());
ASSERT_TRUE(ec_state);
EXPECT_EQ(blink::ExecutionContextToken(mock_graph.frame->frame_token()),
ec_state->token);
// Create a child frame.
ASSERT_FALSE(tracker->GetV8ContextState(kChildFrameMainWorld));
ASSERT_FALSE(
tracker->GetExecutionContextState(mock_graph.child_frame->frame_token()));
tracker->OnV8ContextCreated(ProcessNodeImpl::CreatePassKeyForTesting(),
mock_graph.other_process.get(),
V8ContextDescription::Create(
/* token */ kChildFrameMainWorld,
/* world_type */ V8ContextWorldType::kMain,
/* world_name */ base::nullopt,
/* execution_context_token */
mock_graph.child_frame->frame_token()),
/* iframe_attribution_data */ base::nullopt);
v8_state = tracker->GetV8ContextState(kChildFrameMainWorld);
ASSERT_TRUE(v8_state);
EXPECT_EQ(kChildFrameMainWorld, v8_state->description.token);
EXPECT_EQ(V8ContextWorldType::kMain, v8_state->description.world_type);
EXPECT_FALSE(v8_state->description.world_name);
ASSERT_TRUE(v8_state->description.execution_context_token);
EXPECT_EQ(blink::ExecutionContextToken(mock_graph.child_frame->frame_token()),
v8_state->description.execution_context_token.value());
ec_state =
tracker->GetExecutionContextState(mock_graph.child_frame->frame_token());
ASSERT_TRUE(ec_state);
EXPECT_EQ(blink::ExecutionContextToken(mock_graph.child_frame->frame_token()),
ec_state->token);
// Provide iframe data for the child frame.
ASSERT_FALSE(ec_state->iframe_attribution_data);
tracker->OnRemoteIframeAttachedForTesting(mock_graph.child_frame.get(),
kChildFrameRemoteToken,
GetFakeIframeAttributionData());
ASSERT_TRUE(ec_state->iframe_attribution_data);
const auto& iad = ec_state->iframe_attribution_data.value();
EXPECT_EQ(base::OptionalFromPtr(&kIframeId), iad.id);
EXPECT_EQ(base::OptionalFromPtr(&kIframeSrc), iad.src);
}
} // namespace v8_memory
} // namespace performance_manager
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment