Conflito com a solução RGPD da Quantcast "choice" e ember.js

Ei pessoal,
Tenho algumas novas informações para vocês e encontrei uma solução para esse problema.

Então, hoje estive investigando novamente esse bug e acho que descobri mais detalhes.

Ao examinar o arquivo cmp2ui-fr.js do Quantcast, consegui identificar onde o bug ocorre, que está nesta função (temos apenas a versão minificada):

function(t){for(var n in t){t[n].status=e;}}

Como vocês podem ver, essa função usa um for..in, e a variável t é um array. Já explicamos isso anteriormente: o Ember.js estende o Array nativo do JavaScript. Parece que uma das modificações é a adição de uma entrada _super.

A entrada _super aponta para uma função ROOT(), que parece referenciar _utils.ROOT no Ember.js (talvez isso soe familiar para alguns de vocês, mas não para mim ^^). Essa função ROOT() não é extensível.

Aparentemente, essa propriedade _super é considerada enumerable, o que significa que, ao usar um loop for..in em um array, a entrada _super é tratada como uma entrada “normal” (diferente, por exemplo, de values, bind, valueOf, basicamente todas as funções de um objeto Array).

Acho que isso é um bug do Ember.js e não um comportamento desejável.

Consegui tornar o bug altamente reprodutível. Para isso, basta criar um array de objetos, assim:

var objs = [{'key':'val'},{'key':'val'}];

Em seguida, crie uma função usando strict mode, iterando sobre um array e definindo uma nova propriedade para cada entrada:

var tst_func = function (objs){'use strict';for(var i in objs){objs[i].newproperty = true }};

Por fim, basta chamar essa função no array de objetos:

tst_func(objs);

Você deve receber um erro: Uncaught TypeError: can't define property "newproperty": Function is not extensible.

Analisando isso, acredito que o bug provavelmente já existe em produção e não é específico do Quantcast. Basicamente, qualquer pessoa que tente usar for..in se expõe a comportamentos inconsistentes ou bugs críticos.

Na minha opinião, isso não é responsabilidade direta do Discourse ou do Quantcast, mas claramente do Ember.js. No entanto, isso não muda o fato de que precisamos encontrar uma solução enquanto o bug não for corrigido no Ember.js ^^.

A boa notícia é que acho que encontrei uma maneira de corrigir isso.

Uma maneira é adicionar uma linha if (!objs.hasOwnProperty(i)) {continue}; em todos os loops for..in (provavelmente também em forEach, for...of, etc.). Isso não modifica o comportamento estranho do _super do Array, mas impede localmente o acesso a ele. Obviamente, isso significa que não funciona para qualquer script externo que não temos controle, como no meu caso específico do uso do Quantcast.

Outra maneira, que acredito ser o caminho a seguir, é modificar o protótipo do Array do JavaScript (então, basicamente, sobrescrevemos a própria sobrescrita do Ember.js ^^) para tornar _super não enumerável. Para isso, precisamos executar essa linha de JavaScript DEPOIS que o Ember.js for chamado e avaliado:

//Tornar _super não enumerável para evitar bugs entre Ember.js e for..in
Object.defineProperty(Array.prototype, '_super', {'enumerable': false});

A segunda opção mantém a possibilidade de uso direto de _super, conforme deveria ser, e impede que ele apareça nos loops. No entanto, não posso garantir que esse comportamento estranho não seja utilizado por nenhuma função interna do Ember.js ou plugin externo.

Examinando o arquivo _ember_jquery-189e46ebcb33594b835e782fd1ce916ec750bc0cf980ebc4fb7796649161a18d.js, vi algumas linhas específicas do Discourse, como:

var ALIASES = {
    "ember-addons/ember-computed-decorators":
      "discourse-common/utils/decorators",
    "discourse/lib/raw-templates": "discourse-common/lib/raw-templates",
    "preload-store": "discourse/lib/preload-store",
    "fixtures/user_fixtures": "discourse/tests/fixtures/user-fixtures",
  };
  var ALIAS_PREPEND = {
    fixtures: "discourse/tests/",
    helpers: "discourse/tests/",
  };

Talvez pudéssemos adicionar nossa linha Object.defineProperty(Array.prototype, '_super', {'enumerable': false}); aqui?

No momento, corrigi o bug adicionando as linhas:
//Tornar _super não enumerável para evitar bugs entre Ember.js e for..in
Object.defineProperty(Array.prototype, ‘_super’, {‘enumerable’: false});

Antes de chamar o script choice.js do Quantcast.

Acho que alguém pode corrigir o bug localmente criando um arquivo ‘fix_ember.js’ com essas duas linhas, servindo-o estáticamente pelo proxy reverso (por exemplo, Nginx), e adicionando uma linha <script src="/fix_emmber.js"></script> no rodapé do tema usando personalização. Escrever o script diretamente em vez de criar um link não funciona por causa da extração de scripts do Discourse (veja Custom javascript in <head> disappear).

Espero que este tópico ajude outros. Vou abrir um ticket no Ember.js amanhã para verificar se isso é um bug ou um comportamento muito estranho, mas intencional.

Vou mantê-los informados para ver se precisamos incluir uma correção no Discourse.

PS: Muitos agradecimentos a Angus Croll de Extending JavaScript Natives – JavaScript, JavaScript…, seu post me ajudou muito!