Всем привет,
У меня есть для вас новая информация, и я нашел решение этой проблемы.
Сегодня я снова копался в этом баге и, думаю, нашел больше деталей.
Просматривая файл cmp2ui-fr.js от Quantcast, я смог определить место, где возникает ошибка — это функция (к сожалению, у нас есть только минифицированная версия):
function(t){for(var n in t){t[n].status=e;}}
Как видите, эта функция использует for..in, а переменная t является массивом. Мы уже упоминали ранее, что ember.js расширяет нативный массив JavaScript. Оказывается, одно из изменений — это добавление свойства _super.
Свойство _super указывает на функцию ROOT(), которая, похоже, ссылается на _utils.ROOT в ember.js (возможно, это кому-то из вас знакомо, но не мне ^^). Эта функция ROOT() не является расширяемой.
Похоже, что свойство _super считается enumerable (перебираемым), что означает: при использовании цикла for..in по массиву запись _super воспринимается как «обычная» запись (в отличие, например, от values, bind, valueOf — в общем, всех функций объекта массива).
Я считаю, что это баг в ember.js, а не желаемое поведение.
Мне удалось сделать баг легко воспроизводимым. Для этого просто создайте массив объектов, например:
var objs = [{'key':'val'},{'key':'val'}];
Затем создайте функцию в строгом режиме, которая перебирает массив и добавляет новое свойство для каждого элемента:
var tst_func = function (objs){'use strict';for(var i in objs){objs[i].newproperty = true }};
Наконец, вызовите эту функцию для массива объектов:
tst_func(objs);
Вы получите ошибку: Uncaught TypeError: can't define property "newproperty": Function is not extensible.
Это заставляет меня думать, что баг уже существует в дикой природе и не специфичен для Quantcast. В принципе, любой, кто использует for..in, подвергает себя риску некорректного поведения или критических ошибок.
На мой взгляд, проблема не в Discourse или Quantcast, а явно в ember.js. Тем не менее, пока это не исправлено в ember.js, нам нужно найти решение ^^.
Хорошая новость в том, что я, кажется, нашел способ исправить это.
Один из способов — добавить строку if (!objs.hasOwnProperty(i)) {continue}; в каждый цикл for..in (вероятно, также в forEach, for...of и т.д.). Это не изменит странное поведение _super в массиве, но локально предотвратит доступ к нему. Очевидно, что это не сработает для внешних скриптов, которыми мы не управляем, как в моём конкретном случае с Quantcast.
Другой способ, который я считаю правильным путём, — модифицировать прототип массива JavaScript (то есть фактически переопределить собственное переопределение ember.js ^^), чтобы сделать _super неперечисляемым. Для этого нужно выполнить следующую строку JS ПОСЛЕ того, как ember.js будет вызван и выполнен:
//Make _super not enumerable to prevent bug between emberjs and for..in
Object.defineProperty(Array.prototype, '_super', {'enumerable': false});
Второй способ сохраняет возможность прямого использования _super, как и задумано, но предотвращает его появление в циклах. Однако я не могу гарантировать, что это странное поведение не используется какой-либо внутренней функцией ember.js или внешним плагином.
Просматривая файл _ember_jquery-189e46ebcb33594b835e782fd1ce916ec750bc0cf980ebc4fb7796649161a18d.js, я заметил несколько строк, специфичных для Discourse, например:
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/",
};
Может быть, мы могли бы добавить нашу строку Object.defineProperty(Array.prototype, '_super', {'enumerable': false}); сюда?
На данный момент я исправил баг, добавив строки:
//Make _super not enumerable to prevent bug between emberjs and for..in
Object.defineProperty(Array.prototype, ‘_super’, {‘enumerable’: false});
перед вызовом скрипта choice.js от Quantcast.
Я думаю, что кто-то может исправить баг локально, создав файл fix_ember.js с этими двумя строками, разместив его статически через обратный прокси (например, Nginx) и добавив строку <script src="/fix_emmber.js"></script> в подвал темы через кастомизацию. Написание скрипта напрямую вместо создания ссылки не работает из-за извлечения скриптов в Discourse (см. Custom javascript in <head> disappear).
Надеюсь, эта тема поможет другим. Завтра я открою тикет в ember.js, чтобы выяснить, является ли это багом или очень странное, но намеренное поведение.
Я буду держать вас в курсе, нужно ли нам включить исправление в Discourse.
PS: Большое спасибо Энгу Кроллу с Extending JavaScript Natives – JavaScript, JavaScript…, его пост очень помог мне!