Conflict with Quantcast RGPD solution "choice" and ember.js

Hey everybody,
i’ve got some new informations for you and i’ve find a solution to this problem.

So, today i’ve been diging again on this bug and i think i have found more infos.

By looking at the file cmp2ui-fr.js of quantcast, i’ve been able to find where the bug happen, it’s in this function (we only have minified version):

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

As you see, this function use a for..in, the t var is an array. We previously explained this, ember.js extend javascript native Array. It appear that one of the modification is the addition of a _super entry.

The _super entry point to some ROOT() function, which seems to refer to _utils.ROOT in ember.js, (maybe this sound familiar to some of you, but not to me ^^). This ROOT() function is not extensible.

Apparently this _super property is considered to be enumerable, which mean that when using a for..in loop on an array, the _super entry is considered a “normal” entry (unlike, for example, values, bind, valueOf, basically all the functions of an array object).

I do think this is a bug of ember.js, and not a desirable behaviour.

So, i’ve been able to make the bug very much reproductible. To do so, just create an array of objects, like this :

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

Then, create function using strict, iterating over an array and setting a new property for each entry :

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

Finally, just call this function on the array of objects :

tst_func(objs);

You should get an error Uncaught TypeError: can't define property "newproperty": Function is not extensible.

Looking to this make me think the bug is very probably already in the wild and really not specific to quantcast. Basicly anybody trying to use for..in expose himself to inconsistent behavior or critical bug.

For me it’s not really up to discourse or quantcast, but clearly to ember.js. Yet, it does not change fact that we have to find a fix for it as long as it’s not fixed in ember.js ^^.

Good news is, i think i’ve find a way to fix this.

One way to do is to use a line if (!objs.hasOwnProperty(i)) {continue}; in every for..in loop (probably also each, for of, etc.). This does not modify Array _super strange behavior, but locally prevent accessing it. That obviously mean this does not work for any external script we have no control over, like my particular Quantcast use case.

Another way, which i think is the road to take, is to modify javascript’s Array prototype (so we basically override ember.js own override ^^) to make _super not enumerable. To do so, we have to run this js line AFTER ember.js is call and evaluated :

//Make _super not enumerable to prevent bug between emberjs and for..in
Object.defineProperty(Array.prototype, '_super', {'enumerable': false});

The second way keep allowing direct usage of _super, as it should be intended, and prevent it to appear on loop. Though, i cannot garanty this strange behavior is not used by any ember.js internal function or external plugin.

Looking at the file _ember_jquery-189e46ebcb33594b835e782fd1ce916ec750bc0cf980ebc4fb7796649161a18d.js, i’ve seen some lines specific to Discourse, like :

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/",
  };

Maybe we could add our Object.defineProperty(Array.prototype, '_super', {'enumerable': false}); line here ?

For me i’ve currently fixed the bug by adding the lines :
//Make _super not enumerable to prevent bug between emberjs and for…in
Object.defineProperty(Array.prototype, ‘_super’, {‘enumerable’: false});

Before i call Quantcast choice.js script.

I think someone could fix the bug locally by creating a file ‘fix_ember.js’ with thoses two lines, serving it statically from the reverse proxy (for example Nginx), and adding a <script src="/fix_emmber.js"></script> line into theme footer using customization. Writing the script directly instead of creating a link does not work because of Discourse script extraction (see Custom javascript in <head> disappear).

I do hope this topic will help others. I will open a ticket at ember.js tomorrow, to see if this is a bug or a verry strange yet deliberate behavior.

I’ll keep you informed to see if we have to include a fix in Discourse.

PS : Many thanks to Angus Croll from Extending JavaScript Natives – JavaScript, JavaScript…, his post help me a lot !

3 Likes