Ehi a tutti,
ho alcune nuove informazioni per voi e ho trovato una soluzione a questo problema.
Quindi, oggi ho scavato di nuovo in questo bug e penso di aver trovato ulteriori dettagli.
Analizzando il file cmp2ui-fr.js di Quantcast, sono riuscito a individuare dove si verifica il bug; si trova in questa funzione (abbiamo solo la versione minificata):
function(t){for(var n in t){t[n].status=e;}}
Come vedete, questa funzione utilizza un for..in, e la variabile t è un array. Come avevamo spiegato in precedenza, Ember.js estende l’Array nativo di JavaScript. Sembra che una delle modifiche apportate sia l’aggiunta di una voce _super.
La voce _super punta a una funzione ROOT(), che sembra riferirsi a _utils.ROOT in Ember.js (forse questo vi suona familiare, ma a me no ^^). Questa funzione ROOT() non è estendibile.
Apparentemente, questa proprietà _super viene considerata enumerabile, il che significa che quando si utilizza un ciclo for..in su un array, la voce _super viene trattata come una voce “normale” (a differenza, ad esempio, di values, bind, valueOf, ovvero praticamente tutte le funzioni di un oggetto array).
Penso che questo sia un bug di Ember.js e non un comportamento desiderabile.
Quindi, sono riuscito a rendere il bug molto riproducibile. Per farlo, basta creare un array di oggetti, come questo:
var objs = [{'key':'val'},{'key':'val'}];
Poi, creare una funzione in strict mode che itera su un array e imposta una nuova proprietà per ogni voce:
var tst_func = function (objs){'use strict';for(var i in objs){objs[i].newproperty = true }};
Infine, chiamate semplicemente questa funzione sull’array di oggetti:
tst_func(objs);
Dovreste ottenere un errore Uncaught TypeError: can't define property "newproperty": Function is not extensible.
Osservando questo, mi viene da pensare che il bug sia molto probabilmente già presente in ambienti reali e non sia affatto specifico di Quantcast. Fondamentalmente, chiunque provi a utilizzare for..in si espone a comportamenti incoerenti o a bug critici.
Per me, la responsabilità non ricade davvero su Discourse o Quantcast, ma chiaramente su Ember.js. Tuttavia, questo non cambia il fatto che dobbiamo trovare una soluzione finché non verrà corretto in Ember.js ^^.
La buona notizia è che penso di aver trovato un modo per risolvere il problema.
Un modo per farlo è inserire una riga if (!objs.hasOwnProperty(i)) {continue}; in ogni ciclo for..in (probabilmente anche in forEach, for...of, ecc.). Questo non modifica il comportamento strano dell’Array _super, ma previene localmente l’accesso ad esso. Ovviamente, ciò significa che non funziona per eventuali script esterni su cui non abbiamo controllo, come nel mio caso particolare con Quantcast.
Un altro metodo, che ritengo sia la strada da seguire, è modificare il prototipo dell’Array di JavaScript (quindi di fatto sovrascriviamo la sovrascrittura di Ember.js ^^) per rendere _super non enumerabile. Per farlo, dobbiamo eseguire questa riga di JavaScript DOPO che Ember.js è stato chiamato e valutato:
//Rendi _super non enumerabile per prevenire bug tra Ember.js e for..in
Object.defineProperty(Array.prototype, '_super', {'enumerable': false});
Il secondo metodo mantiene la possibilità di utilizzare direttamente _super, come dovrebbe essere previsto, e ne previene la comparsa nei cicli. Tuttavia, non posso garantire che questo comportamento strano non venga utilizzato da alcuna funzione interna di Ember.js o da plugin esterni.
Analizzando il file _ember_jquery-189e46ebcb33594b835e782fd1ce916ec750bc0cf980ebc4fb7796649161a18d.js, ho notato alcune righe specifiche di Discourse, come:
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/",
};
Forse potremmo aggiungere qui la nostra riga Object.defineProperty(Array.prototype, '_super', {'enumerable': false});?
Al momento ho risolto il bug aggiungendo le righe:
//Rendi _super non enumerabile per prevenire bug tra Ember.js e for..in
Object.defineProperty(Array.prototype, ‘_super’, {‘enumerable’: false});
Prima di chiamare lo script choice.js di Quantcast.
Penso che qualcuno possa risolvere il problema localmente creando un file ‘fix_ember.js’ con queste due righe, servendolo in modo statico tramite un reverse proxy (ad esempio Nginx) e aggiungendo una riga <script src="/fix_emmber.js"></script> nel footer del tema tramite personalizzazione. Scrivere direttamente lo script invece di creare un link non funziona a causa dell’estrazione degli script di Discourse (vedi Custom javascript in <head> disappear).
Spero che questo argomento sia d’aiuto ad altri. Domani aprirò un ticket su Ember.js per verificare se si tratta di un bug o di un comportamento molto strano ma deliberato.
Vi terrò aggiornati per vedere se dobbiamo includere una correzione in Discourse.
PS: Molti ringraziamenti ad Angus Croll da Extending JavaScript Natives – JavaScript, JavaScript…, il suo post mi è stato di grande aiuto!