From 2c600877bb636474ed4fb1e00517ec44b4f6e2de Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 5 Feb 2026 09:55:14 -0800 Subject: [PATCH] Add IntegrationHeadInjector trait for head script injection - Add `IntegrationHeadInjector` trait allowing integrations to inject HTML into
- Add `with_head_injector()` builder method to IntegrationRegistrationBuilder - Add `head_inserts()` method to IntegrationRegistry to collect all injector snippets - Update html_processor to inject integration head inserts after TSJS bundle This enables integrations like GAM to inject configuration scripts into the document head without modifying the main TSJS bundle. --- crates/common/src/html_processor.rs | 22 ++++++-- crates/common/src/integrations/registry.rs | 58 ++++++++++++++++++++++ 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/crates/common/src/html_processor.rs b/crates/common/src/html_processor.rs index e3d827fb..b354e8b3 100644 --- a/crates/common/src/html_processor.rs +++ b/crates/common/src/html_processor.rs @@ -1,4 +1,4 @@ -//! Simplified HTML processor that combines URL replacement and Prebid injection +//! Simplified HTML processor that combines URL replacement and integration injection //! //! This module provides a `StreamProcessor` implementation for HTML content. use std::cell::Cell; @@ -191,10 +191,26 @@ pub fn create_html_processor(config: HtmlProcessorConfig) -> impl StreamProcesso // Inject unified tsjs bundle once at the start of element!("head", { let injected_tsjs = injected_tsjs.clone(); + let integrations = integration_registry.clone(); + let patterns = patterns.clone(); + let document_state = document_state.clone(); move |el| { if !injected_tsjs.get() { - let loader = tsjs::unified_script_tag(); - el.prepend(&loader, ContentType::Html); + let mut snippet = String::new(); + let ctx = IntegrationHtmlContext { + request_host: &patterns.request_host, + request_scheme: &patterns.request_scheme, + origin_host: &patterns.origin_host, + document_state: &document_state, + }; + // First inject the unified TSJS bundle (defines tsjs.setConfig, etc.) + snippet.push_str(&tsjs::unified_script_tag()); + // Then add any integration-specific head inserts (e.g., mode config) + // These run after the bundle so tsjs API is available + for insert in integrations.head_inserts(&ctx) { + snippet.push_str(&insert); + } + el.prepend(&snippet, ContentType::Html); injected_tsjs.set(true); } Ok(()) diff --git a/crates/common/src/integrations/registry.rs b/crates/common/src/integrations/registry.rs index 7fe9e778..426b557a 100644 --- a/crates/common/src/integrations/registry.rs +++ b/crates/common/src/integrations/registry.rs @@ -376,6 +376,14 @@ pub trait IntegrationHtmlPostProcessor: Send + Sync { fn post_process(&self, html: &mut String, ctx: &IntegrationHtmlContext<'_>) -> bool; } +/// Trait for integration-provided HTML head injections. +pub trait IntegrationHeadInjector: Send + Sync { + /// Identifier for logging/diagnostics. + fn integration_id(&self) -> &'static str; + /// Return HTML snippets to insert at the start of ``. + fn head_inserts(&self, ctx: &IntegrationHtmlContext<'_>) -> Vec