Skip to content

Commit e3e7200

Browse files
authored
fix(carbon-ads): keep the script id resolvable after unmount (#872)
1 parent a423e82 commit e3e7200

2 files changed

Lines changed: 39 additions & 2 deletions

File tree

‎packages/script/src/runtime/components/ScriptCarbonAds.vue‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ defineSlots<{
2626
}>()
2727
2828
const attrId =`_carbonads_js`
29-
const carbonadsEl =ref<HTMLElement|null>(import.meta.client?document.getElementById(attrId) :null)
29+
const carbonadsEl =ref<HTMLElement|null>(null)
3030
// syncs to useScript status
3131
const status =ref('awaitingLoad')
3232
let scriptEl:HTMLScriptElement|undefined
@@ -56,6 +56,8 @@ function loadCarbon() {
5656
emit('ready', script)
5757
}
5858
}
59+
// carbon.js resolves #_carbonads_js by id, so only one may exist at a time
60+
document.getElementById(attrId)?.remove()
5961
carbonadsEl.value.appendChild(script)
6062
}
6163
@@ -82,7 +84,9 @@ onBeforeUnmount(() => {
8284
if (scriptEl) {
8385
scriptEl.onload=null
8486
scriptEl.onerror=null
85-
scriptEl.remove()
87+
// a pending carbon.js ad callback reads #_carbonads_js.src unguarded, so park
88+
// the already-executed script in <head> rather than removing it
89+
document.head.appendChild(scriptEl)
8690
scriptEl=undefined
8791
}
8892
})

‎test/nuxt-runtime/script-component-lifecycle.nuxt.test.ts‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ describe('script component lifecycle', () => {
3333
afterEach(()=>{
3434
for(constwrapperofwrappers.splice(0))
3535
wrapper.unmount()
36+
for(constelofdocument.querySelectorAll('#_carbonads_js'))
37+
el.remove()
3638
if(lemonSqueezyDescriptor)
3739
Object.defineProperty(window,'LemonSqueezy',lemonSqueezyDescriptor)
3840
else
@@ -51,6 +53,37 @@ describe('script component lifecycle', () => {
5153
expect(removeRoot).not.toHaveBeenCalled()
5254
})
5355

56+
// carbon.js reads `document.getElementById('_carbonads_js').src` from its own
57+
// async ad callback, unguarded, so the id must outlive the component.
58+
it('keeps the carbon script resolvable by id after unmount',()=>{
59+
constwrapper=mount(ScriptCarbonAds,{
60+
attachTo: document.body,
61+
props: {serve: 'CW7DTKJL',placement: 'unlighthousedev',format: 'cover'},
62+
})
63+
wrappers.push(wrapper)
64+
65+
wrapper.unmount()
66+
67+
constreadServe=()=>newURL(document.getElementById('_carbonads_js')!.getAttribute('src')!,'https://x.test').searchParams.get('serve')
68+
expect(readServe()).toBe('CW7DTKJL')
69+
})
70+
71+
it('replaces the parked script when a new instance mounts',()=>{
72+
constfirst=mount(ScriptCarbonAds,{
73+
attachTo: document.body,
74+
props: {serve: 'CW7DTKJL',placement: 'first',format: 'cover'},
75+
})
76+
first.unmount()
77+
constsecond=mount(ScriptCarbonAds,{
78+
attachTo: document.body,
79+
props: {serve: 'CW7DTKJL',placement: 'second',format: 'cover'},
80+
})
81+
wrappers.push(second)
82+
83+
expect(document.querySelectorAll('#_carbonads_js')).toHaveLength(1)
84+
expect(document.getElementById('_carbonads_js')!.getAttribute('src')).toContain('placement=second')
85+
})
86+
5487
it('fans Lemon Squeezy events out to every live component',()=>{
5588
constfirstEvent=vi.fn()
5689
constsecondEvent=vi.fn()

0 commit comments

Comments
 (0)