[AdTracker] Introduce ScriptInitiationMonitor [chromium/src : main]

0 views
Skip to first unread message

Josh Karlin (Gerrit)

unread,
Jun 26, 2026, 2:09:21 PM (5 days ago) Jun 26
to Josh Karlin, Yao Xiao, Kentaro Hara, Raphael Kubo da Costa, android-bu...@system.gserviceaccount.com, chromium...@chromium.org, Nate Chapin, blink-revie...@chromium.org, blink-revi...@chromium.org, blink-...@chromium.org, gavinp...@chromium.org, loading...@chromium.org
Attention needed from Yao Xiao

Josh Karlin added 1 comment

Patchset-level comments
File-level comment, Patchset 2:
Josh Karlin . resolved

Yao, PTAL for a high-level pass. I will be out for a bit and even though this CL isn't perfect yet it'd be good to have you take a pass. Thanks!

Open in Gerrit

Related details

Attention is currently required from:
  • Yao Xiao
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement is not satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: I3c0eeba30b6f3b0e17933407cdffcb2a9c330f8a
Gerrit-Change-Number: 8010133
Gerrit-PatchSet: 2
Gerrit-Owner: Josh Karlin <jka...@chromium.org>
Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
Gerrit-CC: Kentaro Hara <har...@chromium.org>
Gerrit-CC: Nate Chapin <jap...@chromium.org>
Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
Gerrit-Attention: Yao Xiao <yao...@chromium.org>
Gerrit-Comment-Date: Fri, 26 Jun 2026 18:09:14 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
satisfied_requirement
unsatisfied_requirement
open
diffy

AI Code Reviewer (Gerrit)

unread,
Jun 26, 2026, 2:11:05 PM (5 days ago) Jun 26
to Josh Karlin, Chromium LUCI CQ, Yao Xiao, Kentaro Hara, Raphael Kubo da Costa, android-bu...@system.gserviceaccount.com, chromium...@chromium.org, Nate Chapin, blink-revie...@chromium.org, blink-revi...@chromium.org, blink-...@chromium.org, gavinp...@chromium.org, loading...@chromium.org
Attention needed from Yao Xiao

AI Code Reviewer added 1 comment

File third_party/blink/renderer/bindings/core/v8/js_event_handler_for_content_attribute.h
Line 42, Patchset 3: probe::AsyncTaskContext* async_task_context() { return &async_task_context_; }
AI Code Reviewer . unresolved

Blink Style Guide: Naming - Use 'CamelCase' for all function names. Please rename `async_task_context` to `AsyncTaskContext`.

To keep this interaction as brief and non-intrusive as possible, please consider responding with one of following options:
**Done** | **OK But Won't Fix**: reason | **Later**: b/<bug_id> | **Invalid:** reason


_This comment was generated by [Experimental Blink C++ Code Review Agent](http://go/blink-c++-code-review-agent)._
_AI reviews can sometimes be inaccurate; We appreciate your 🙏 feedback 🙏 to help us improve._
_[File a bug](http://go/blink-c++-code-review-agent-feedback) | [Provide feedback on chat](https://chat.google.com/room/AAQA0zhQHe0?cls=4) | [Opt-out](https://ganpati2.corp.google.com/group/peep-genai-blink-agent-optout.prod)_

Open in Gerrit

Related details

Attention is currently required from:
  • Yao Xiao
Submit Requirements:
    • requirement satisfiedCode-Coverage
    • requirement is not satisfiedCode-Owners
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: chromium/src
    Gerrit-Branch: main
    Gerrit-Change-Id: I3c0eeba30b6f3b0e17933407cdffcb2a9c330f8a
    Gerrit-Change-Number: 8010133
    Gerrit-PatchSet: 3
    Gerrit-Owner: Josh Karlin <jka...@chromium.org>
    Gerrit-Reviewer: Josh Karlin <jka...@chromium.org>
    Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
    Gerrit-CC: AI Code Reviewer <peep-gen...@system.gserviceaccount.com>
    Gerrit-CC: Kentaro Hara <har...@chromium.org>
    Gerrit-CC: Nate Chapin <jap...@chromium.org>
    Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
    Gerrit-Attention: Yao Xiao <yao...@chromium.org>
    Gerrit-Comment-Date: Fri, 26 Jun 2026 18:11:01 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    satisfied_requirement
    unsatisfied_requirement
    open
    diffy

    Yao Xiao (Gerrit)

    unread,
    Jun 30, 2026, 2:11:12 PM (18 hours ago) Jun 30
    to Josh Karlin, AI Code Reviewer, Chromium LUCI CQ, Kentaro Hara, Raphael Kubo da Costa, android-bu...@system.gserviceaccount.com, chromium...@chromium.org, Nate Chapin, blink-revie...@chromium.org, blink-revi...@chromium.org, blink-...@chromium.org, gavinp...@chromium.org, loading...@chromium.org
    Attention needed from Josh Karlin

    Yao Xiao added 15 comments

    Patchset-level comments
    File-level comment, Patchset 6 (Latest):
    Yao Xiao . resolved

    The overall approach looks good. Added some comments.

    File third_party/blink/renderer/bindings/core/v8/js_event_handler_for_content_attribute.h
    Line 21, Patchset 6 (Latest):class JSEventHandlerForContentAttribute final : public JSEventHandler {
    Yao Xiao . unresolved

    `JSEventHandlerForContentAttribute` is a GarbageCollected object, but `probe::AsyncTaskContext` has a non-trivial destructor. Should you use a pre-finalizer to explicitly cancel the task before the object is swept? e.g.,

    ```
    class JSEventHandlerForContentAttribute : public JSEventHandler {
    USING_PRE_FINALIZER(JSEventHandlerForContentAttribute, Dispose);
    public:
    void Dispose() { async_task_context_.reset(); }
    private:
    std::optional<probe::AsyncTaskContext> async_task_context_;
    }
    ```
    File third_party/blink/renderer/bindings/core/v8/js_event_handler_for_content_attribute.cc
    Line 245, Patchset 6 (Latest): if (LocalDOMWindow* dom_window =
    DynamicTo<LocalDOMWindow>(execution_context_of_event_target)) {
    if (LocalFrame* frame = dom_window->GetFrame()) {
    if (ScriptInitiationMonitor* monitor =
    frame->GetScriptInitiationMonitor()) {
    monitor->DidRegisterDynamicScript(
    v8_context_of_event_target,
    V8ScriptId(compiled_function->ScriptId()));
    }
    }
    }
    Yao Xiao . unresolved
    You can use the new static helper:
    ```
    if (auto* monitor = ScriptInitiationMonitor::FromExecutionContext(execution_context_of_event_target)) {
    monitor->DidRegisterDynamicScript(v8_context_of_event_target, V8ScriptId(compiled_function->ScriptId()));
    }
    ```
    File third_party/blink/renderer/core/ad_tracker/ad_tracker.cc
    Line 441, Patchset 6 (Latest):void AdTracker::DidRegisterDynamicScript(v8::Local<v8::Context> v8_context,
    Yao Xiao . unresolved

    Because `JSEventHandlerForContentAttribute` now checks ad-status via `DidRegisterDynamicScript` at execution time rather than creation time, this introduces a subtle regression: If a vanilla script synchronously triggers the ad-created event handler (e.g., document.getElementById('btn').click()), `IsAdScriptInStack` evaluates the synchronous V8 stack, sees the vanilla script, ignores the `async_script_stack_`, and the original ad-provenance is lost.

    I've attached an example regression test below that demonstrates this issue. It passes previously but fails with your patch.

    To restore the previous behavior, I think you can check `async_script_stack_` before checking `IsAdScriptInStack`. Or, if you decide this new behavior is more desirable (I'm not entirely sure yet), I suggest splitting it into a separate CL so this current CL remains a strict refactoring.

    Either way, adding the regression test seems valuable.

    Example regression test:
    ```
    TEST_F(AdTrackerSimTest, AdEventHandlerTriggeredByNonAdScript) {
    SimRequest ad_script_resource("https://example.com/ad_script.js", "application/javascript");
    SimRequest vanilla_script_resource("https://example.com/script.js", "application/javascript");
    SimRequest image_resource("https://example.com/foo.png", "image/png");
      main_resource_->Complete(R"HTML(
    <body>
    <script src="https://example.com/ad_script.js"></script>
    <script src="https://example.com/script.js"></script>
    </body>
    )HTML");
      // Ad script sets an inline event handler on a dynamically created element.
    ad_script_resource.Complete(R"JS(
    let btn = document.createElement("button");
    btn.id = "target";
    btn.setAttribute("onclick", "let img = document.createElement('img'); img.src = 'foo.png';");
    document.body.appendChild(btn);
    )JS");
      // Non-ad script triggers the event handler synchronously.
    vanilla_script_resource.Complete(R"JS(
    document.getElementById('target').click();
    )JS");
      base::RunLoop().RunUntilIdle();
      image_resource.Complete("data");
      // Verify that the subsequent image request is correctly tagged as an ad.
    EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd("https://example.com/foo.png"));
    }
    ```
    File third_party/blink/renderer/core/ad_tracker/lazy_stack_trace.h
    Line 55, Patchset 6 (Latest): Vector<v8::StackTrace::ScriptData> cached_stack_;
    Yao Xiao . unresolved

    Add inline capacity to avoid heap allocations on V8 hot paths? e.g.,
    ```
    Vector<v8::StackTrace::ScriptData, 5> cached_stack_;
    ```

    File third_party/blink/renderer/core/ad_tracker/lazy_stack_trace.cc
    Line 22, Patchset 6 (Latest): if (cached_stack_.size() >= limit) {
    return base::span(cached_stack_).first(limit);
    }
    Yao Xiao . unresolved

    If the real stack is shallower than the requested limit, then you may still do unnecessary V8 stack walks. Suggest adding a `bool fully_captured_ = false;` member (and move it in the move constructor). The logic here can then be:

    ```
    if (cached_stack_.size() >= limit || fully_captured_) {
    return base::span(cached_stack_).first(std::min(static_cast<wtf_size_t>(limit), cached_stack_.size()));
    }
      // ... walk stack ...
      if (stack.size() < limit) {
    cached_stack_.Shrink(static_cast<wtf_size_t>(stack.size()));
    fully_captured_ = true; // Prevents future redundant walks
    }
    ```

    lazy_stack_trace_test.cc should also be updated to expect `EXPECT_EQ(stack3.data(), stack1.data());`.

    File third_party/blink/renderer/core/ad_tracker/script_initiation_monitor.h
    Line 68, Patchset 6 (Latest): virtual void DidRegisterDynamicScript(v8::Local<v8::Context> v8_context,
    Yao Xiao . unresolved

    Should this take a `LazyStackTrace& stack_trace` parameter as well?

    File third_party/blink/renderer/core/ad_tracker/script_initiation_monitor.cc
    Line 59, Patchset 6 (Latest): KURL url(probe.script_url);
    Yao Xiao . unresolved

    Here you construct a `KURL` from a `WTF::String`, but inside `AdTracker::WillExecuteScript`, you turn it back into a `WTF::String`.

    Can we change the `Observer::WillExecuteScript` signature to take just `const String& script_url`?

    Line 82, Patchset 6 (Latest): v8::HandleScope handle_scope(isolate);
    Yao Xiao . unresolved

    This shouldn't be needed. Since `probe.function` is a `v8::Local<v8::Function>`, a handle scope must already be active on the call stack.

    Line 98, Patchset 6 (Latest): v8::HandleScope handle_scope(isolate);
    Yao Xiao . unresolved

    Same as above: this shouldn't be needed.

    File third_party/blink/renderer/core/loader/frame_fetch_context.cc
    Line 1409, Patchset 6 (Latest):std::optional<AdProvenance> FrameFetchContext::CalculateIfAdSubresource(
    Yao Xiao . unresolved
    Should we consider using more generic names here, such as `CalculateResourceAnnotations`, `scan_javascript_stack`, and a struct for the return type:
    ```
    struct ResourceAnnotations {
    std::optional<AdProvenance> ad_provenance;
    // Future mutators can add their own return states here.
    };
    ```
    ?

    (Though keeping the return type as `std::optional<AdProvenance>` also seems fine for now).

    This can be done in a future CL to keep the current scope small.

    WDYT?

    Line 1426, Patchset 6 (Latest): ScriptInitiationMonitor* monitor = GetFrame()->GetScriptInitiationMonitor();
    if (monitor) {
    Yao Xiao . unresolved

    nit: one-liner `if` initialization
    ```
    if (ScriptInitiationMonitor* monitor = GetFrame()->GetScriptInitiationMonitor())
    ```

    Line 1438, Patchset 6 (Latest): return GetFrame()->GetAdTracker()->CalculateIfAdSubresource(
    Yao Xiao . unresolved

    Relying on `PrepareRequest` and `CalculateIfAdSubresource` being called back-to-back seems fragile.

    How about making this stateless by instantiating the `LazyStackTrace` directly in `FrameFetchContext`, and pass it to both `monitor->PrepareRequest` and `ad_tracker->CalculateIfAdSubresource`? With this approach, `AdTracker::WillPrepareRequest` can just be a no-op.

    Even though this exposes `LazyStackTrace` slightly higher up the stack, it seems more robust.

    File third_party/blink/renderer/core/probe/async_task_context.cc
    Line 22, Patchset 6 (Latest): ScanForAds scan_for_ads) {
    Yao Xiao . unresolved

    Should we generalize this struct name as well (in a future CL)?

    File third_party/blink/renderer/core/probe/core_probes.h
    Line 110, Patchset 6 (Latest): Persistent<ScriptInitiationMonitor> script_initiation_tracker_;
    Yao Xiao . unresolved

    nit: `script_initiation_monitor_` to match the class name.

    Open in Gerrit

    Related details

    Attention is currently required from:
    • Josh Karlin
    Submit Requirements:
    • requirement satisfiedCode-Coverage
    • requirement is not satisfiedCode-Owners
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: chromium/src
    Gerrit-Branch: main
    Gerrit-Change-Id: I3c0eeba30b6f3b0e17933407cdffcb2a9c330f8a
    Gerrit-Change-Number: 8010133
    Gerrit-PatchSet: 6
    Gerrit-Owner: Josh Karlin <jka...@chromium.org>
    Gerrit-Reviewer: Josh Karlin <jka...@chromium.org>
    Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
    Gerrit-CC: AI Code Reviewer <peep-gen...@system.gserviceaccount.com>
    Gerrit-CC: Kentaro Hara <har...@chromium.org>
    Gerrit-CC: Nate Chapin <jap...@chromium.org>
    Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
    Gerrit-Attention: Josh Karlin <jka...@chromium.org>
    Gerrit-Comment-Date: Tue, 30 Jun 2026 18:11:03 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    satisfied_requirement
    unsatisfied_requirement
    open
    diffy
    Reply all
    Reply to author
    Forward
    0 new messages