You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Two independent TSJS defects that combine into one severe user-visible failure:
adInit() can refresh a publisher-owned GPT slot after the publisher has already auctioned or rendered that slot. GPT removes the working creative and starts a second impression request.
When that second request selects a Trusted Server APS bid through GAM and Prebid Universal Creative, the TS render bridge suppresses Prebid's handler but does not replace Prebid's outer-frame resize. The APS creative renders inside the 1x1 GAM shell and remains effectively invisible.
The common sequence is therefore: a publisher ad paints quickly, Trusted Server replaces it a few seconds later, and the replacement appears blank even though the APS creative loaded successfully.
flowchart LR
A[Publisher auction] --> B[728x90 publisher creative paints]
B --> C[Late TS adInit refreshes same slot]
C --> D[GAM selects TS APS line item]
D --> E[PUC sends Prebid Request]
E --> F[TS bridge intercepts and renders APS]
F --> G[Inner APS creative loads]
G --> H[Outer GAM iframe stays 1x1]
H --> I[Placement appears blank]
Loading
Both defects need fixes. Preventing the duplicate request protects publisher-rendered impressions, but legitimate TS-owned APS impressions would still be invisible. Restoring shell resize makes APS visible, but the unnecessary replacement would still discard a valid publisher creative.
evidence
Duplicate first-impression request
Instrumentation installed before page scripts observed this homepage sequence:
The publisher's Prebid wrapper auctioned the slot.
GPT painted a real 728x90 AdX creative.
Nine milliseconds after the publisher paint, tsjs-unified called pubads().refresh() on the same slots.
GPT replaced the visible creative about 1.7 seconds later.
Independent matched-arm browser runs reproduced the same overwrite with both diagnostics consoles disabled, with Google Publisher Console enabled, and with Trusted Server diagnostics enabled. The consoles are not the trigger.
SPA article navigation widens the delay. /_ts/page-bids must return, and the current slot resolver may wait up to two seconds for matching elements. The resulting overwrite commonly happens two to four seconds after first paint, matching the report.
Trusted Server diagnostics captured the inverse SPA ordering as:
request 1: prebid_refresh
request 2: trusted_server_direct
request 2: replacedRequestNumber: 1
Some sampled second requests had trustedServerOpportunity: no_candidate and creativeChanged: false. Even with no TS candidate, adInit() removed a valid creative and issued another GPT request.
Successful APS render trapped in one pixel
A separate capture followed a TS APS win through the complete render path:
Prebid emitted bidWon.
Amazon's prebid-creative.js loaded.
The real APS creative iframe was present.
Its document contained about 26.7 KB of content.
The outer google_ads_iframe remained width=1 and height=1 indefinitely.
The ad was not absent. It was rendered inside one pixel.
Publisher bids rendered through the publisher's own Prebid path resized and painted correctly. That is the control showing that the page and GAM placement support the winning dimensions and that the missing behavior is specific to the TS bridge.
The previous duplicate-slot work in #944 and #966 made Trusted Server reuse a publisher GPT slot instead of defining a competing container slot. That solved duplicate slot definitions, but it did not establish ownership of the first request.
In the current adInit() existing-slot path, every matching existing slot receives Trusted Server targeting and enters slotsToRefresh. The later refresh block calls pubads().refresh() without checking whether the current navigation's first impression is already owned or consumed.
There is no behavioral check for:
a publisher Prebid auction already in flight;
a publisher GPT request already issued;
a creative already rendered for this navigation; or
a no-candidate page-bids response arriving after delivery.
The Prebid integration has delivery-correlation state, but it creates the useful registration inside bidsBackHandler and keeps it private to the module. That is too late for the homepage race, where delayed adInit() can run while the publisher auction is still pending.
The SPA generation guard protects route identity. It does not protect impression ownership.
The fallback GPT bootstrap carries the same unconditional existing-slot behavior, so a bundle-only change would leave the fallback path broken.
Defect 2: the TS render bridge bypasses outer-frame resize
The bridge documents and implements this behavior:
Receive PUC's Prebid Request.
Resolve and validate the TS-owned bid.
Reply over the transferred port with Prebid Response.
Call stopImmediatePropagation() so the publisher's Prebid handler does not process the request.
Prebid's normal top-page handler owns resizeRemoteCreative(). Suppressing that handler also suppresses its resize of the outer GAM iframe and immediate parent.
The TS response includes the correct width and height. The current APS dynamic renderer applies those dimensions only to the nested renderer iframe. Neither installTsRenderBridge() nor APS_UNIVERSAL_CREATIVE_RENDERER resizes the authenticated outer google_ads_iframe. The generic TS display renderer has the same structural gap.
PR #998 was intended to fix PUC rendering and collapsed GAM shells. An earlier implementation included a guarded resizeCollapsedCreativeFrame() helper and real-PUC browser coverage, but neither rc/202608 nor current main contains that helper or an equivalent, the shell-resize guarantee is absent. This should be treated as an incomplete or regressed part of #998 rather than as an APS no-bid.
The newer publisher_native experiment follows a different render path. It does not repair the deployed Universal Creative bridge, and switching modes is not a safe hotfix without the security, reporting, CSP, and APS-account validation required by #999 and #1042.
Impact
Valid publisher creatives disappear after first paint.
GPT sends unnecessary requests for the same user-visible impression.
TS APS winners can load and bill while remaining visually 1x1.
Viewability, impression ownership, bidder accounting, and revenue attribution may disagree.
A no-candidate TS cycle can still destroy a filled publisher slot.
Diagnostics may show the replacement generation as pending while the page looks blank.
The failure is timing-sensitive and appears intermittent even though both code paths are deterministic once their ordering is known.
Proposed fix 1: first-claimant ownership per navigation
Adopt this immediate contract:
The first valid claimant owns the current navigation's first impression. Trusted Server may mark and request ts_initial=1 only while that claim is unconsumed. After a publisher auction, GPT request, or render claims the impression, late page-bids data is stale for that impression and must not mutate or refresh the slot.
Strict unconditional TS-first ownership would require holding publisher delivery while page-bids settles and choosing between two auction winners. That is a larger product and auction-arbitration change and should not be hidden inside adInit().
Shared ownership state
Add bounded, shared state usable by the GPT and Prebid bundles. Track at least:
navigation generation;
exact slot element identity or unambiguous publisher ad-unit code;
owner such as publisher_prebid or trusted_server_direct;
phase such as auctioning, delivery_pending, requested, or rendered;
a registration token; and
a bounded expiry.
Use registration tokens rather than booleans so nested or overlapping auctions cannot clear each other's claims.
Claim publisher slots at requestBids() entry
For real publisher auctions, register the claim before invoking native requestBids().
Move the same registration to delivery-pending in bidsBackHandler.
Attach returned ad IDs to that registration.
Consume it only when the correlated GPT delivery is forwarded.
Clear it on synchronous throw, navigation invalidation, removal, or bounded timeout.
Do not create publisher claims for synthetic TS refresh auctions or while adInitRefreshInProgress is set.
This closes the homepage window that callback-time delivery registration misses.
Observe GPT lifecycle independently of diagnostics
Install early listeners for slotRequested and slotRenderEnded. Store only slot identity and navigation generation needed for ownership.
Behavioral correctness must not depend on the optional diagnostics recorder.
Classify before changing targeting
Before clearing or applying any targeting, adInit() should classify each slot:
Slot state
Required behavior
New TS-owned slot
Preserve current define and display behavior
New TS-owned slot with initial load disabled
Preserve the one required explicit refresh
Existing untouched slot with no publisher claim
Atomically claim for TS, apply targeting, set ts_initial=1, and refresh once
Existing slot in a publisher auction
Do not change targeting, set ts_initial, map a winning creative, or refresh
Existing slot requested or rendered this generation
Treat page-bids data as stale for that impression
Clear prior-generation TS targeting at navigation transition and again before publisher targeting is applied. Late adInit() cleanup must not alter an already-rendered current-generation slot.
Publisher claims should be leases. If a publisher auction never reaches GPT, allow one per-slot TS fallback only after the lease expires and the code rechecks generation, DOM identity, and ownership. Do not rerun whole-page adInit() as a fallback.
Mirror the same ownership contract in gpt_bootstrap.js.
Why TS demand is preserved
The Prebid wrapper injects the trustedServer adapter into publisher ad units. When publisher Prebid claims first, server-side TS demand still participates in that auction.
A separate page-bids response arriving after GPT has requested the impression is no longer eligible for that impression. Rendering it creates a second impression rather than improving the first.
This depends on the TS Prebid wrapper installing before the publisher auction. Verify and monitor that ordering during rollout.
Mirror Prebid's outer-frame resize after the bridge has validated the exact message source, slot ownership, bid identity, and winning dimensions.
The resize helper should:
Resolve the exact iframe whose contentWindow equals MessageEvent.source under the already-resolved slot root.
Require a connected ordinary display iframe that is still collapsed to 1x1 in both attributes and computed geometry.
Reject invalid, zero, non-finite, or unbounded dimensions.
Exclude interstitials, anchors, fixed or sticky frames, and ambiguous source matches.
Set the outer iframe's width and height attributes and styles to the validated winner dimensions.
Resize the immediate shell parent only under the same guarded conditions, matching the minimum behavior of Prebid's resizeRemoteCreative().
Perform the resize only after port.postMessage() succeeds, with explicit rollback or no mutation on failed response delivery.
Apply equivalent handling to APS, inline adm, and PBS Cache bridge responses where the TS bridge suppresses native Prebid handling.
The earlier guarded helper from the #998 work is a useful starting point, but it must be reconciled with current source matching, APS capability consumption, refresh lifecycle, diagnostics, and the publisher_native branch.
Regression coverage
Request ownership
Add deterministic tests for:
Homepage: publisher auction starts, delayed scheduler runs, then publisher delivery occurs. GPT receives one first-impression request.
SPA: publisher refresh renders, page-bids returns, and no second request or iframe removal occurs.
Reverse race: TS claims an untouched slot first, and a later genuine publisher refresh is allowed only as the next impression.
Same-generation slotRequested and slotRenderEnded both suppress late adInit() targeting and refresh.
Prior-generation lifecycle state does not suppress a valid new navigation claim.
An abandoned publisher claim permits exactly one per-slot fallback after expiry.
A no-candidate response never replaces an already-rendered publisher slot.
New TS-owned and both legacy and modern initial-load-disabled paths still request exactly once.
Bundle and bootstrap behavior match.
Creative shell geometry
Use real Prebid Universal Creative in a browser fixture:
Start with an outer GAM iframe and shell parent at 1x1.
Deliver a TS APS winner through the bridge.
Confirm the bridge authenticates the exact source and the APS creative loads.
Assert that the outer iframe and required parent geometry become the winning dimensions, such as 728x90 or 300x250.
Repeat for inline adm and cache-backed responses.
Confirm wrong-source, replayed, ambiguous, interstitial, anchor, fixed, sticky, already-expanded, and invalid-size cases do not resize.
Verify refresh and navigation cleanup do not let a stale completion resize a newer creative.
Assert terminal renderer and diagnostics events, not merely runner-script load.
Acceptance criteria
A publisher first impression produces one GPT request and one committed creative.
Late initial-page or SPA adInit() work cannot overwrite a publisher-claimed or rendered slot.
ts_initial=1 appears only on a request that TS actually owns.
Skipped page-bids results do not alter hb_* targeting or creative mappings.
Legitimate TS-owned slots, including initial-load-disabled slots, still request exactly once.
A TS APS winner delivered through a 1x1 PUC shell becomes visible at its validated dimensions.
The inner APS creative, outer GAM iframe, and immediate shell parent all have expected geometry.
Publisher-native, direct /auction, interstitial, anchor, refresh, and navigation behavior do not regress.
Until both fixes are deployed, disable server-side creative opportunities for the affected cohort:
[creative_opportunities]
enabled = false
This stops initial HTML and SPA page-bids template delivery while preserving direct /auction behavior and the publisher's normal Prebid flow. A narrower mitigation can remove the overlapping slot rules or page patterns, or disable the APS provider while keeping the duplicate-request risk visible for non-APS candidates.
Do not switch to publisher_native as an emergency workaround without the validation required by #999 and #1042.
Keep the creative-opportunities switch available through rollout. Monitor GPT request counts per slot and navigation, replacement generations, outer iframe geometry, visible-fill rate, and TS APS render acknowledgements. Roll back through the same configuration gate if duplicate requests or collapsed shells reappear.
Summary
Two independent TSJS defects that combine into one severe user-visible failure:
adInit()can refresh a publisher-owned GPT slot after the publisher has already auctioned or rendered that slot. GPT removes the working creative and starts a second impression request.The common sequence is therefore: a publisher ad paints quickly, Trusted Server replaces it a few seconds later, and the replacement appears blank even though the APS creative loaded successfully.
flowchart LR A[Publisher auction] --> B[728x90 publisher creative paints] B --> C[Late TS adInit refreshes same slot] C --> D[GAM selects TS APS line item] D --> E[PUC sends Prebid Request] E --> F[TS bridge intercepts and renders APS] F --> G[Inner APS creative loads] G --> H[Outer GAM iframe stays 1x1] H --> I[Placement appears blank]Both defects need fixes. Preventing the duplicate request protects publisher-rendered impressions, but legitimate TS-owned APS impressions would still be invisible. Restoring shell resize makes APS visible, but the unnecessary replacement would still discard a valid publisher creative.
evidence
Duplicate first-impression request
Instrumentation installed before page scripts observed this homepage sequence:
tsjs-unifiedcalledpubads().refresh()on the same slots.Independent matched-arm browser runs reproduced the same overwrite with both diagnostics consoles disabled, with Google Publisher Console enabled, and with Trusted Server diagnostics enabled. The consoles are not the trigger.
SPA article navigation widens the delay.
/_ts/page-bidsmust return, and the current slot resolver may wait up to two seconds for matching elements. The resulting overwrite commonly happens two to four seconds after first paint, matching the report.Trusted Server diagnostics captured the inverse SPA ordering as:
prebid_refreshtrusted_server_directreplacedRequestNumber: 1Some sampled second requests had
trustedServerOpportunity: no_candidateandcreativeChanged: false. Even with no TS candidate,adInit()removed a valid creative and issued another GPT request.Successful APS render trapped in one pixel
A separate capture followed a TS APS win through the complete render path:
bidWon.prebid-creative.jsloaded.google_ads_iframeremainedwidth=1andheight=1indefinitely.The ad was not absent. It was rendered inside one pixel.
Publisher bids rendered through the publisher's own Prebid path resized and painted correctly. That is the control showing that the page and GAM placement support the winning dimensions and that the missing behavior is specific to the TS bridge.
Defect 1: existing-slot reuse lacks request ownership
The previous duplicate-slot work in #944 and #966 made Trusted Server reuse a publisher GPT slot instead of defining a competing container slot. That solved duplicate slot definitions, but it did not establish ownership of the first request.
In the current
adInit()existing-slot path, every matching existing slot receives Trusted Server targeting and entersslotsToRefresh. The later refresh block callspubads().refresh()without checking whether the current navigation's first impression is already owned or consumed.There is no behavioral check for:
The Prebid integration has delivery-correlation state, but it creates the useful registration inside
bidsBackHandlerand keeps it private to the module. That is too late for the homepage race, where delayedadInit()can run while the publisher auction is still pending.The SPA generation guard protects route identity. It does not protect impression ownership.
The fallback GPT bootstrap carries the same unconditional existing-slot behavior, so a bundle-only change would leave the fallback path broken.
Defect 2: the TS render bridge bypasses outer-frame resize
The bridge documents and implements this behavior:
Prebid Request.Prebid Response.stopImmediatePropagation()so the publisher's Prebid handler does not process the request.See the bridge contract and interception and the APS response path.
Prebid's normal top-page handler owns
resizeRemoteCreative(). Suppressing that handler also suppresses its resize of the outer GAM iframe and immediate parent.The TS response includes the correct width and height. The current APS dynamic renderer applies those dimensions only to the nested renderer iframe. Neither
installTsRenderBridge()norAPS_UNIVERSAL_CREATIVE_RENDERERresizes the authenticated outergoogle_ads_iframe. The generic TS display renderer has the same structural gap.PR #998 was intended to fix PUC rendering and collapsed GAM shells. An earlier implementation included a guarded
resizeCollapsedCreativeFrame()helper and real-PUC browser coverage, but neitherrc/202608nor currentmaincontains that helper or an equivalent, the shell-resize guarantee is absent. This should be treated as an incomplete or regressed part of #998 rather than as an APS no-bid.The newer
publisher_nativeexperiment follows a different render path. It does not repair the deployed Universal Creative bridge, and switching modes is not a safe hotfix without the security, reporting, CSP, and APS-account validation required by #999 and #1042.Impact
Proposed fix 1: first-claimant ownership per navigation
Adopt this immediate contract:
Strict unconditional TS-first ownership would require holding publisher delivery while page-bids settles and choosing between two auction winners. That is a larger product and auction-arbitration change and should not be hidden inside
adInit().Shared ownership state
Add bounded, shared state usable by the GPT and Prebid bundles. Track at least:
publisher_prebidortrusted_server_direct;auctioning,delivery_pending,requested, orrendered;Use registration tokens rather than booleans so nested or overlapping auctions cannot clear each other's claims.
Claim publisher slots at
requestBids()entryFor real publisher auctions, register the claim before invoking native
requestBids().bidsBackHandler.adInitRefreshInProgressis set.This closes the homepage window that callback-time delivery registration misses.
Observe GPT lifecycle independently of diagnostics
Install early listeners for
slotRequestedandslotRenderEnded. Store only slot identity and navigation generation needed for ownership.Behavioral correctness must not depend on the optional diagnostics recorder.
Classify before changing targeting
Before clearing or applying any targeting,
adInit()should classify each slot:ts_initial=1, and refresh oncets_initial, map a winning creative, or refreshClear prior-generation TS targeting at navigation transition and again before publisher targeting is applied. Late
adInit()cleanup must not alter an already-rendered current-generation slot.Publisher claims should be leases. If a publisher auction never reaches GPT, allow one per-slot TS fallback only after the lease expires and the code rechecks generation, DOM identity, and ownership. Do not rerun whole-page
adInit()as a fallback.Mirror the same ownership contract in
gpt_bootstrap.js.Why TS demand is preserved
The Prebid wrapper injects the
trustedServeradapter into publisher ad units. When publisher Prebid claims first, server-side TS demand still participates in that auction.A separate page-bids response arriving after GPT has requested the impression is no longer eligible for that impression. Rendering it creates a second impression rather than improving the first.
This depends on the TS Prebid wrapper installing before the publisher auction. Verify and monitor that ordering during rollout.
Proposed fix 2: restore authenticated collapsed-shell resizing
Mirror Prebid's outer-frame resize after the bridge has validated the exact message source, slot ownership, bid identity, and winning dimensions.
The resize helper should:
contentWindowequalsMessageEvent.sourceunder the already-resolved slot root.resizeRemoteCreative().port.postMessage()succeeds, with explicit rollback or no mutation on failed response delivery.adm, and PBS Cache bridge responses where the TS bridge suppresses native Prebid handling.The earlier guarded helper from the #998 work is a useful starting point, but it must be reconciled with current source matching, APS capability consumption, refresh lifecycle, diagnostics, and the
publisher_nativebranch.Regression coverage
Request ownership
Add deterministic tests for:
slotRequestedandslotRenderEndedboth suppress lateadInit()targeting and refresh.Creative shell geometry
Use real Prebid Universal Creative in a browser fixture:
admand cache-backed responses.Acceptance criteria
adInit()work cannot overwrite a publisher-claimed or rendered slot.ts_initial=1appears only on a request that TS actually owns.hb_*targeting or creative mappings./auction, interstitial, anchor, refresh, and navigation behavior do not regress.Immediate mitigation and rollback
Until both fixes are deployed, disable server-side creative opportunities for the affected cohort:
This stops initial HTML and SPA page-bids template delivery while preserving direct
/auctionbehavior and the publisher's normal Prebid flow. A narrower mitigation can remove the overlapping slot rules or page patterns, or disable the APS provider while keeping the duplicate-request risk visible for non-APS candidates.Do not switch to
publisher_nativeas an emergency workaround without the validation required by #999 and #1042.Keep the creative-opportunities switch available through rollout. Monitor GPT request counts per slot and navigation, replacement generations, outer iframe geometry, visible-fill rate, and TS APS render acknowledgements. Roll back through the same configuration gate if duplicate requests or collapsed shells reappear.
Related work
publisher_nativeexperiment and its security and reporting trade-offs.