Commit 56819ee9 authored by Alex Turner's avatar Alex Turner Committed by Commit Bot

Track dynamically added scripts with no src in AdTracker

When the script is about to execute, we check whether it was added by ad
script. If it was, we tag that script as an ad. In order to track these
scripts, which have no URL, we assign them a fake url based on their
script id.

Bug: 1098530
Change-Id: Id7a3571a47fb9235379b68a3f041a18952aafa03
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2316326
Commit-Queue: Alex Turner <alexmt@chromium.org>
Reviewed-by: default avatarJosh Karlin <jkarlin@chromium.org>
Reviewed-by: default avatarKouhei Ueno <kouhei@chromium.org>
Cr-Commit-Position: refs/heads/master@{#800582}
parent b330a916
......@@ -137,7 +137,6 @@ v8::Local<v8::Module> ModuleRecord::Compile(
ScriptValue ModuleRecord::Instantiate(ScriptState* script_state,
v8::Local<v8::Module> record,
const KURL& source_url) {
v8::Isolate* isolate = script_state->GetIsolate();
v8::TryCatch try_catch(isolate);
......@@ -145,7 +144,14 @@ ScriptValue ModuleRecord::Instantiate(ScriptState* script_state,
DCHECK(!record.IsEmpty());
v8::Local<v8::Context> context = script_state->GetContext();
probe::ExecuteScript probe(ExecutionContext::From(script_state), source_url);
// Script IDs are not available on errored modules or on non-source text
// modules, so we give them a default value.
probe::ExecuteScript probe(ExecutionContext::From(script_state), source_url,
record->GetStatus() != v8::Module::kErrored &&
record->IsSourceTextModule()
? record->ScriptId()
: v8::UnboundScript::kNoScriptId);
bool success;
if (!record->InstantiateModule(context, &ResolveModuleCallback)
.To(&success) ||
......@@ -168,7 +174,14 @@ ModuleEvaluationResult ModuleRecord::Evaluate(ScriptState* script_state,
v8::TryCatch try_catch(isolate);
ExecutionContext* execution_context = ExecutionContext::From(script_state);
probe::ExecuteScript probe(execution_context, source_url);
// Script IDs are not available on errored modules or on non-source text
// modules, so we give them a default value.
probe::ExecuteScript probe(execution_context, source_url,
record->GetStatus() != v8::Module::kErrored &&
record->IsSourceTextModule()
? record->ScriptId()
: v8::UnboundScript::kNoScriptId);
v8::Local<v8::Value> result;
if (!V8ScriptRunner::EvaluateModule(isolate, execution_context, record,
......
......@@ -354,7 +354,8 @@ v8::MaybeLocal<v8::Value> V8ScriptRunner::RunCompiledScript(
// ToCoreString here should be zero copy due to externalized string
// unpacked.
probe::ExecuteScript probe(context, ToCoreString(script_url));
probe::ExecuteScript probe(context, ToCoreString(script_url),
script->GetUnboundScript()->GetId());
result = script->Run(isolate->GetCurrentContext());
}
......
......@@ -35,6 +35,15 @@ bool IsKnownAdExecutionContext(ExecutionContext* execution_context) {
return false;
}
String GenerateFakeUrlFromScriptId(int script_id) {
// Null string is used to represent scripts with neither a name nor an ID.
if (script_id == v8::Message::kNoScriptIdInfo)
return String();
// The prefix cannot appear in real URLs.
return String::Format("{ id %d }", script_id);
}
} // namespace
namespace features {
......@@ -91,7 +100,23 @@ void AdTracker::Shutdown() {
}
String AdTracker::ScriptAtTopOfStack() {
return GetCurrentScriptUrl(/*max_stack_depth=*/1);
// CurrentStackTrace is 10x faster than CaptureStackTrace if all that you need
// is the url of the script at the top of the stack. See crbug.com/1057211 for
// more detail.
v8::Isolate* isolate = v8::Isolate::GetCurrent();
DCHECK(isolate);
v8::Local<v8::StackTrace> stack_trace =
v8::StackTrace::CurrentStackTrace(isolate, /*frame_limit=*/1);
if (stack_trace.IsEmpty() || stack_trace->GetFrameCount() < 1)
return String();
v8::Local<v8::StackFrame> frame = stack_trace->GetFrame(isolate, 0);
v8::Local<v8::String> script_name = frame->GetScriptNameOrSourceURL();
if (script_name.IsEmpty() || !script_name->Length())
return GenerateFakeUrlFromScriptId(frame->GetScriptId());
return ToCoreString(script_name);
}
ExecutionContext* AdTracker::GetCurrentExecutionContext() {
......@@ -102,13 +127,34 @@ ExecutionContext* AdTracker::GetCurrentExecutionContext() {
}
void AdTracker::WillExecuteScript(ExecutionContext* execution_context,
const String& script_url) {
const String& script_url,
int script_id) {
bool is_ad = false;
// We track scripts with no URL (i.e. dynamically inserted scripts with no
// src) by IDs instead. We also check the stack as they are executed
// immediately and should be tagged based on the script inserting them.
bool should_track_with_id =
script_url.IsEmpty() && script_id != v8::Message::kNoScriptIdInfo;
if (should_track_with_id) {
// This primarily checks if |execution_context| is a known ad context as we
// don't need to keep track of scripts in ad contexts. However, two scripts
// with identical text content can be assigned the same ID.
String fake_url = GenerateFakeUrlFromScriptId(script_id);
if (IsKnownAdScript(execution_context, fake_url)) {
is_ad = true;
} else if (IsAdScriptInStack(StackType::kBottomAndTop)) {
AppendToKnownAdScripts(*execution_context, fake_url);
is_ad = true;
}
}
if (top_of_stack_only_)
return;
bool is_ad = script_url.IsEmpty()
? false
: IsKnownAdScript(execution_context, script_url);
if (!should_track_with_id)
is_ad = IsKnownAdScript(execution_context, script_url);
stack_frame_is_ad_.push_back(is_ad);
if (is_ad)
num_ads_in_stack_ += 1;
......@@ -126,7 +172,7 @@ void AdTracker::DidExecuteScript() {
}
void AdTracker::Will(const probe::ExecuteScript& probe) {
WillExecuteScript(probe.context, probe.script_url);
WillExecuteScript(probe.context, probe.script_url, probe.script_id);
}
void AdTracker::Did(const probe::ExecuteScript& probe) {
......@@ -150,7 +196,7 @@ void AdTracker::Will(const probe::CallFunction& probe) {
if (!resource_name_string.IsEmpty())
script_url = ToCoreString(resource_name_string.ToLocalChecked());
}
WillExecuteScript(probe.context, script_url);
WillExecuteScript(probe.context, script_url, probe.function->ScriptId());
}
void AdTracker::Did(const probe::CallFunction& probe) {
......@@ -240,12 +286,7 @@ bool AdTracker::IsAdScriptInStack(StackType stack_type) {
// (e.g., when v8 is executed) but not the entire stack. For a small cost we
// can also check the top of the stack (this is much cheaper than getting the
// full stack from v8).
String top_script = ScriptAtTopOfStack();
if (!top_script.IsEmpty() && IsKnownAdScript(execution_context, top_script))
return true;
return false;
return IsKnownAdScript(execution_context, ScriptAtTopOfStack());
}
bool AdTracker::IsKnownAdScript(ExecutionContext* execution_context,
......@@ -256,6 +297,9 @@ bool AdTracker::IsKnownAdScript(ExecutionContext* execution_context,
if (IsKnownAdExecutionContext(execution_context))
return true;
if (url.IsEmpty())
return false;
auto it = known_ad_scripts_.find(execution_context);
if (it == known_ad_scripts_.end())
return false;
......@@ -265,6 +309,7 @@ bool AdTracker::IsKnownAdScript(ExecutionContext* execution_context,
// This is a separate function for testing purposes.
void AdTracker::AppendToKnownAdScripts(ExecutionContext& execution_context,
const String& url) {
DCHECK(!url.IsEmpty());
auto add_result =
known_ad_scripts_.insert(&execution_context, HashSet<String>());
add_result.stored_value->value.insert(url);
......
......@@ -103,7 +103,12 @@ class CORE_EXPORT AdTracker : public GarbageCollected<AdTracker> {
friend class AdTrackerSimTest;
friend class AdTrackerTest;
void WillExecuteScript(ExecutionContext*, const String& script_name);
// |script_name| will be empty in the case of a dynamically added script with
// no src attribute set. |script_id| won't be set for module scripts in an
// errored state or for non-source text modules.
void WillExecuteScript(ExecutionContext*,
const String& script_name,
int script_id);
void DidExecuteScript();
bool IsKnownAdScript(ExecutionContext* execution_context, const String& url);
void AppendToKnownAdScripts(ExecutionContext&, const String& url);
......@@ -116,7 +121,8 @@ class CORE_EXPORT AdTracker : public GarbageCollected<AdTracker> {
uint32_t num_ads_in_stack_ = 0;
// The set of ad scripts detected outside of ad-frame contexts.
// The set of ad scripts detected outside of ad-frame contexts. Scripts with
// no name (i.e. URL) use a String created by GenerateFakeUrlFromScriptId().
HeapHashMap<WeakMember<ExecutionContext>, HashSet<String>> known_ad_scripts_;
// The number of ad-related async tasks currently running in the stack.
......
......@@ -215,17 +215,6 @@ class FrameFetchContextSubresourceFilterTest : public FrameFetchContextTest {
return reason;
}
void AppendExecutingScriptToAdTracker(const String& url) {
AdTracker* ad_tracker = document->GetFrame()->GetAdTracker();
ad_tracker->WillExecuteScript(document->GetExecutionContext(), url);
}
void AppendAdScriptToAdTracker(const KURL& ad_script_url) {
AdTracker* ad_tracker = document->GetFrame()->GetAdTracker();
ad_tracker->AppendToKnownAdScripts(*document->GetExecutionContext(),
ad_script_url.GetString());
}
private:
base::Optional<ResourceRequestBlockedReason> CanRequestInternal(
ReportingDisposition reporting_disposition,
......
......@@ -152,7 +152,7 @@ interface CoreProbes {
void BreakableLocation(ExecutionContext* context, const char* name);
RecalculateStyle(Document* document);
UpdateLayout(Document* document);
ExecuteScript([Keep] ExecutionContext* context, const String& script_url);
ExecuteScript([Keep] ExecutionContext* context, const String& script_url, int script_id);
CallFunction([Keep] ExecutionContext* context, v8::Local<v8::Function> function, int depth = 0);
UserCallback([Keep] ExecutionContext* context, const char* name, const AtomicString& atomic_name, bool recurring, EventTarget* event_target = nullptr);
V8Compile([Keep] ExecutionContext* context, const String& file_name, int line, int column);
......
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