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

Hello,
so this is quite a specific bug, but his impact may go far beyond this specific case, and so i’ve got a question about it.

(Also, i apologize for my english, i’m french, so not a native speaker at all…)

But first, let me explain the context.
I’ve been using Discourse for a while for a french forum about the raspberry pi (forum.raspberry-pi.fr). This forum use advertising management (themoneytizer). As you probably know, Europe compel us to implement RGPD for user’s privacy protection. The major actor (at least in France) for RGPD consent is Quantcast and their “Choice” solution.

So, i’ve been using Quantcast solution for quite a time, with no problems, until recently when i noticed that the “Accept all” button is not working properly anymore. On click, nothing happen, and looking in dev console i got this error : “Uncaught TypeError: can’t define property “status”: Function is not extensible”.

What happen (at least to the best of my understanding) :
Trust me when i say that it taked me a loooooonnng time to find the source of the problem. Apparently ember.js (which i’m very not familiar with) extend some Javascript native objects, such as Array, String and Function. And for some reason seems to also somehow prevent extension of thoses objects in a way (i’ve not yet fully understand this part).

Quantcast’s solution on his part try, probably on the function FunctionAcceptAll (explaining the bug only occur when clicking on accept all button and reject all button), to extend an object, i presume an array, which normal behavior have been modified by ember.js.

After a lot of research for understanding this bug, i’ve also found that it is possible to modify Ember.js behavior to not extend javascript prototypes, as explained in this page Disabling Prototype Extensions - Configuration - Ember Guides.

I’ve been making a few tests, and the bug do disappear if i add the line window.EmberENV.EXTEND_PROTOTYPES = {String: true, Array: false}; in file _ember_jquery-189e46ebcb33594b835e782fd1ce916ec750bc0cf980ebc4fb7796649161a18d.js after the line window.EmberENV.FORCE_JQUERY = true;

For thoses of you how would want to try it out, you can take a look at the /tst/index.html page on the forum (you may need an european ip in order to the script to start, i have no idea).
Now, i think you got all the informations i may give you.

So, now here is my question.
Even though this is quite a specific bug, RGPD is more and more present in europe right now, and it will not go easier.
Quantcast is in a quite monopolistic position, at least for actors that cannot afford to pay hundred of dollars for implemting RGPD. This bug prevent any usage of Quantcast, and so of advertising on Discourse in europe, which seem’s to me a big problem.
Also, even if I’ve only find the bug using Quantcast, this kind of bug may actually happen for a lot of third party scripts that we must embed for ads or other, that we have no control over at all, and that are relying on javascript “normal” behavior for Array, String and Function objects.

I do not know Discourse code enough, so i’m asking you, are the properties add by ember.js over Array, String and Function objects (see the link before) used by Discourse or not ? If not, maybe we should consider disabling prototype extensions from ember.js, in order to prevent side-effect like this one ?

I hope someone can give me info on this one,
thanks

3 Likes

This is not supported at the moment, we rely on the extensions. Perhaps in a future version we will not. @eviltrout can provide more context.

I am not sure what to do here, but I think we should have some workaround here. I am surprised this breaks RGPD, perhaps open a ticket with quantcast to discuss and link here?

3 Likes

How are you adding the scripts needed for Quantcast? Are you adding them via a script tag with a src (external) like so

<script src="foo"></script>

Or are you adding them inline like so?

<script>
  alert("Hello World!");
</script>

Hi,
thanks for your responses.

@sam I opened a ticket with quantcast the day i’ve created this topics and they responded to me today, apparently they are taking a look at it right know, hopefully they will find a fix for it. I do think this is a quite easy fix for them as the button “accept selection” is working find. Let’s hope they find Discourse and ember.js to be a solution used enough to deserve a dedicated fix.

@Johani I actually don’t add quantcast scripts myself, themoneytizer provide a script that do the adding for me (as well as looking for tcf2 events, etc.). You can take a look to this script here if you want.

This script seems to add a new element before the first script of the page. I would not be certain, but i do very strongly thinks i’ve seen quite this exact script on some Quantcast doc examples, so i presume it’s a fairly used script for integrating Quantcast Choice.

Thanks again for your time.

I can’t see us removing the array prototypes safely any time soon. They’ve been quite harmless over the years and add a lot of convenience.

Maybe you could reach out to Quantcast to get a version of the scripts that don’t rely on array prototypes?

1 Like

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 !

2 Likes