blob: 89700963d1bef93f1fb8344127aeeaa4f86ebe2e [file] [log] [blame]
// Copyright 2018 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 "third_party/blink/renderer/core/frame/ad_tracker.h"
#include <memory>
#include "base/feature_list.h"
#include "third_party/blink/renderer/bindings/core/v8/source_location.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/core/core_probe_sink.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/casting.h"
namespace blink {
namespace {
bool IsKnownAdExecutionContext(ExecutionContext* execution_context) {
// TODO(jkarlin): Do the same check for worker contexts.
if (auto* document = DynamicTo<Document>(execution_context)) {
LocalFrame* frame = document->GetFrame();
if (frame && frame->IsAdSubframe())
return true;
}
return false;
}
} // namespace
namespace features {
// Controls whether the AdTracker will look across async stacks to determine if
// the currently running stack is ad related.
const base::Feature kAsyncStackAdTagging{"AsyncStackAdTagging",
base::FEATURE_DISABLED_BY_DEFAULT};
} // namespace features
// static
AdTracker* AdTracker::FromExecutionContext(
ExecutionContext* execution_context) {
if (!execution_context)
return nullptr;
if (auto* document = DynamicTo<Document>(execution_context)) {
LocalFrame* frame = document->GetFrame();
if (frame) {
return frame->GetAdTracker();
}
}
return nullptr;
}
AdTracker::AdTracker(LocalFrame* local_root)
: local_root_(local_root),
async_stack_enabled_(
base::FeatureList::IsEnabled(features::kAsyncStackAdTagging)) {
local_root_->GetProbeSink()->AddAdTracker(this);
}
AdTracker::~AdTracker() {
DCHECK(!local_root_);
}
void AdTracker::Shutdown() {
if (!local_root_)
return;
local_root_->GetProbeSink()->RemoveAdTracker(this);
local_root_ = nullptr;
}
String AdTracker::ScriptAtTopOfStack(ExecutionContext* execution_context) {
std::unique_ptr<blink::SourceLocation> current_stack_trace =
SourceLocation::Capture(execution_context);
// TODO(jkarlin): Url() sometimes returns String(), why?
return current_stack_trace ? current_stack_trace->Url() : "";
}
ExecutionContext* AdTracker::GetCurrentExecutionContext() {
// Determine the current ExecutionContext.
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
return context.IsEmpty() ? nullptr : ToExecutionContext(context);
}
void AdTracker::WillExecuteScript(ExecutionContext* execution_context,
const String& script_url) {
bool is_ad = script_url.IsEmpty()
? false
: IsKnownAdScript(execution_context, script_url);
stack_frame_is_ad_.push_back(is_ad);
if (is_ad)
num_ads_in_stack_ += 1;
}
void AdTracker::DidExecuteScript() {
if (stack_frame_is_ad_.back()) {
DCHECK_LT(0u, num_ads_in_stack_);
num_ads_in_stack_ -= 1;
}
stack_frame_is_ad_.pop_back();
}
void AdTracker::Will(const probe::ExecuteScript& probe) {
WillExecuteScript(probe.context, probe.script_url);
}
void AdTracker::Did(const probe::ExecuteScript& probe) {
DidExecuteScript();
}
void AdTracker::Will(const probe::CallFunction& probe) {
// Do not process nested microtasks as that might potentially lead to a
// slowdown of custom element callbacks.
if (probe.depth)
return;
v8::Local<v8::Value> resource_name =
probe.function->GetScriptOrigin().ResourceName();
String script_url;
if (!resource_name.IsEmpty()) {
script_url = ToCoreString(
resource_name->ToString(ToIsolate(local_root_)->GetCurrentContext())
.ToLocalChecked());
}
WillExecuteScript(probe.context, script_url);
}
void AdTracker::Did(const probe::CallFunction& probe) {
if (probe.depth)
return;
DidExecuteScript();
}
bool AdTracker::CalculateIfAdSubresource(ExecutionContext* execution_context,
const ResourceRequest& request,
ResourceType resource_type,
bool known_ad) {
// Check if the document loading the resource is an ad or if any executing
// script is an ad.
known_ad = known_ad || IsKnownAdExecutionContext(execution_context) ||
IsAdScriptInStack();
// If it is a script marked as an ad and it's not in an ad context, append it
// to the known ad script set. We don't need to keep track of ad scripts in ad
// contexts, because any script executed inside an ad context is considered an
// ad script by IsKnownAdScript.
if (resource_type == ResourceType::kScript && known_ad &&
!IsKnownAdExecutionContext(execution_context)) {
AppendToKnownAdScripts(*execution_context, request.Url().GetString());
}
return known_ad;
}
void AdTracker::DidCreateAsyncTask(probe::AsyncTaskId* task) {
DCHECK(task);
if (!async_stack_enabled_)
return;
if (IsAdScriptInStack())
task->SetAdTask();
}
void AdTracker::DidStartAsyncTask(probe::AsyncTaskId* task) {
DCHECK(task);
if (!async_stack_enabled_)
return;
if (task->IsAdTask())
running_ad_async_tasks_ += 1;
}
void AdTracker::DidFinishAsyncTask(probe::AsyncTaskId* task) {
DCHECK(task);
if (!async_stack_enabled_)
return;
if (task->IsAdTask())
running_ad_async_tasks_ -= 1;
}
bool AdTracker::IsAdScriptInStack() {
if (num_ads_in_stack_ > 0 || running_ad_async_tasks_ > 0)
return true;
ExecutionContext* execution_context = GetCurrentExecutionContext();
if (!execution_context)
return false;
// If we're in an ad context, then no matter what the executing script is it's
// considered an ad.
if (IsKnownAdExecutionContext(execution_context))
return true;
// The pseudo-stack contains entry points into the stack (e.g., when v8 is
// executed) but not the entire stack. It's cheap to retrieve the top of the
// stack so scan that as well.
String top_script = ScriptAtTopOfStack(execution_context);
if (!top_script.IsEmpty() && IsKnownAdScript(execution_context, top_script))
return true;
return false;
}
bool AdTracker::IsKnownAdScript(ExecutionContext* execution_context,
const String& url) {
if (!execution_context)
return false;
if (IsKnownAdExecutionContext(execution_context))
return true;
auto it = known_ad_scripts_.find(execution_context);
if (it == known_ad_scripts_.end())
return false;
return it->value.Contains(url);
}
// This is a separate function for testing purposes.
void AdTracker::AppendToKnownAdScripts(ExecutionContext& execution_context,
const String& url) {
auto add_result =
known_ad_scripts_.insert(&execution_context, HashSet<String>());
add_result.stored_value->value.insert(url);
}
void AdTracker::Trace(blink::Visitor* visitor) {
visitor->Trace(local_root_);
visitor->Trace(known_ad_scripts_);
}
} // namespace blink