Versão: presente em @decocms/blocks@7.51.0 e idêntico em 7.55.0 (mais recente).
Arquivo: src/sdk/analytics.ts, dentro de ANALYTICS_SCRIPT.
O bug
var viewObserver = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
var event = getEvent(entry.target);
if (event) dispatch(event);
viewObserver.unobserve(entry.target); // desobserva...
}
});
}, { threshold: 0.5 });
function observeAll() {
document.querySelectorAll("[data-event-trigger='view']").forEach(function(el) {
viewObserver.observe(el); // ...mas re-observa tudo de novo
});
}
observeAll();
var mo = new MutationObserver(observeAll);
mo.observe(document.body, { childList: true, subtree: true });
unobserve marca o elemento como já disparado, mas nada persiste esse estado. Qualquer mutação no body chama observeAll(), que re-observa todos os elementos — inclusive os que já dispararam. Se o elemento ainda está na viewport, o IntersectionObserver notifica de novo e o evento dispara de novo.
E o dispatch faz window.dataLayer.push(...), que faz o GTM disparar tags, que injetam nós no body, que geram mutação, que chama observeAll(). O ciclo se auto-alimenta.
Impacto medido
Loja VTEX real (deco-sites/farmrio-storefront), PLP, navegador parado, sem scroll nem clique:
|
GTM ativo |
GTM bloqueado |
| long tasks em 20s |
245 tarefas, 15887 ms bloqueados |
6 tarefas, 458 ms |
chamadas a observe() |
2340 |
108 |
| exceptions não tratadas |
133 |
1 |
| clique num card → PDP |
1146 ms |
209 ms |
79% da main thread consumida em loop, numa página parada. O view_item_list e o view_category dispararam 175 vezes cada em 30 segundos (deveriam disparar uma vez), cada um levando junto um TypeError: Cannot read properties of undefined (reading 'split') de dentro do gtm.js.
Além do custo do loop, o observeAll roda um querySelectorAll no documento inteiro a cada mutação — nesta PLP são 611 elementos com data-event-trigger, e 1181 callbacks de mutação em 20s.
Efeito colateral relevante: como o evento é re-enviado centenas de vezes, os relatórios de analytics ficam inflados — view_item_list reportado 175x por pageview.
Proposta
Persistir o "já disparou" no próprio elemento e excluí-lo do seletor:
if (entry.isIntersecting) {
var event = getEvent(entry.target);
if (event) dispatch(event);
entry.target.setAttribute('data-event-fired', '');
viewObserver.unobserve(entry.target);
}
...
function observeAll() {
document.querySelectorAll("[data-event-trigger='view']:not([data-event-fired])")
.forEach(function(el) { viewObserver.observe(el); });
}
Sugiro também agrupar o observeAll do MutationObserver num requestAnimationFrame/debounce — mesmo corrigido o loop, um querySelectorAll de documento inteiro por mutação é caro em página com muita tag de terceiro.
Feliz em mandar PR.
Versão: presente em
@decocms/blocks@7.51.0e idêntico em7.55.0(mais recente).Arquivo:
src/sdk/analytics.ts, dentro deANALYTICS_SCRIPT.O bug
unobservemarca o elemento como já disparado, mas nada persiste esse estado. Qualquer mutação nobodychamaobserveAll(), que re-observa todos os elementos — inclusive os que já dispararam. Se o elemento ainda está na viewport, o IntersectionObserver notifica de novo e o evento dispara de novo.E o
dispatchfazwindow.dataLayer.push(...), que faz o GTM disparar tags, que injetam nós nobody, que geram mutação, que chamaobserveAll(). O ciclo se auto-alimenta.Impacto medido
Loja VTEX real (
deco-sites/farmrio-storefront), PLP, navegador parado, sem scroll nem clique:observe()79% da main thread consumida em loop, numa página parada. O
view_item_liste oview_categorydispararam 175 vezes cada em 30 segundos (deveriam disparar uma vez), cada um levando junto umTypeError: Cannot read properties of undefined (reading 'split')de dentro dogtm.js.Além do custo do loop, o
observeAllroda umquerySelectorAllno documento inteiro a cada mutação — nesta PLP são 611 elementos comdata-event-trigger, e 1181 callbacks de mutação em 20s.Efeito colateral relevante: como o evento é re-enviado centenas de vezes, os relatórios de analytics ficam inflados —
view_item_listreportado 175x por pageview.Proposta
Persistir o "já disparou" no próprio elemento e excluí-lo do seletor:
Sugiro também agrupar o
observeAlldo MutationObserver numrequestAnimationFrame/debounce — mesmo corrigido o loop, umquerySelectorAllde documento inteiro por mutação é caro em página com muita tag de terceiro.Feliz em mandar PR.