decocms / decocms/blocks

analytics: observeAll re-observa elementos já disparados a cada mutação do DOM — loop infinito de eventos view

Open Beginner friendly
#516 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
5
Forks
2
Avg merge
20h 12m
Merged PRs (30d)
36

Description

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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/sdk/analytics.ts at ANALYTICS_SCRIPT, especially the IntersectionObserver callback and observeAll triggered by the MutationObserver. Persist the fired state on view elements and exclude them from later observation; verify that DOM mutations no longer redispatch an event for an element that has already intersected.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
analytics
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.