Конфликт решения Quantcast RGPD "choice" и ember.js

Всем привет,
У меня есть для вас новая информация, и я нашел решение этой проблемы.

Сегодня я снова копался в этом баге и, думаю, нашел больше деталей.

Просматривая файл 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…, его пост очень помог мне!