Quantcast RGPD解决方案“choice”与ember.js的冲突

你好,

这是一个相当具体的 bug,但其影响可能远超这一特定情况,因此我想就此提一个问题。

(另外,我为我的英语道歉,我是法国人,完全不是母语者……)

但首先,让我解释一下背景。
我一直使用 Discourse 运营一个关于树莓派的法语论坛(forum.raspberry-pi.fr)。该论坛使用了广告管理工具(themoneytizer)。正如您可能所知,欧洲要求我们实施 RGPD(通用数据保护条例)以保护用户隐私。在法国,RGPD 同意管理的主要参与者是 Quantcast 及其"Choice"解决方案。

因此,我一直使用 Quantcast 的解决方案有一段时间了,之前没有任何问题,但最近我注意到“全部接受”按钮不再正常工作。点击后没有任何反应,在开发者控制台中我看到如下错误:“Uncaught TypeError: can’t define property “status”: Function is not extensible"。

发生了什么(至少根据我的理解):
请相信,我花了非常非常长的时间才找到问题的根源。显然,ember.js(我对它非常不熟悉)扩展了一些 JavaScript 原生对象,例如 Array、String 和 Function。出于某种原因,它似乎以某种方式阻止了这些对象的扩展(我尚未完全理解这部分)。

Quantcast 的解决方案在其部分尝试扩展一个对象(我推测是一个数组),这可能是 FunctionAcceptAll 函数(这也解释了为什么 bug 仅在点击“全部接受”和“全部拒绝”按钮时出现)。然而,该对象的正常行为已被 ember.js 修改。

在进行了大量研究以理解此 bug 后,我还发现可以修改 Ember.js 的行为,使其不扩展 JavaScript 原型,正如这个页面所解释的:https://guides.emberjs.com/release/configuring-ember/disabling-prototype-extensions/。

我进行了一些测试,如果我在文件 _ember_jquery-189e46ebcb33594b835e782fd1ce916ec750bc0cf980ebc4fb7796649161a18d.js 中的 window.EmberENV.FORCE_JQUERY = true; 行之后添加一行 window.EmberENV.EXTEND_PROTOTYPES = {String: true, Array: false};,bug 就会消失。

对于那些想要尝试的人,可以查看论坛上的 /tst/index.html 页面(您可能需要一个欧洲 IP 才能让脚本启动,我不确定具体原因)。
现在,我认为我已经提供了所有可能提供的信息。

那么,现在是我的问题。
尽管这是一个相当具体的 bug,但 RGPD 在欧洲越来越普遍,而且未来只会变得更加严格。
Quantcast 处于近乎垄断的地位,至少对于那些无法承担数百美元费用来实施 RGPD 的参与者而言。这个 bug 导致无法在 Discourse 上使用 Quantcast,进而导致在欧洲无法使用广告,这在我看来是一个大问题。
此外,即使我仅在 Quantcast 中发现了这个 bug,这种类型的 bug 实际上可能发生在许多第三方脚本中,这些脚本我们必须嵌入用于广告或其他用途,我们完全无法控制它们,而且它们依赖于 JavaScript 中 Array、String 和 Function 对象的“正常”行为。

我对 Discourse 的代码还不够熟悉,所以我想请教您:Ember.js 添加到 Array、String 和 Function 对象上的属性(参见上面的链接)是否被 Discourse 使用?如果没有,我们是否应该考虑禁用 Ember.js 的原型扩展,以避免出现此类副作用?

希望有人能就此提供信息,
谢谢

5 个赞

目前暂不支持此操作,我们依赖这些扩展。也许在将来的版本中我们会不再需要它们。@eviltrout 可以提供更多背景信息。

我不确定该如何处理,但认为我们应该提供某种变通方案。令我惊讶的是这会导致 RGPD 问题,或许可以联系 Quantcast 开一个工单进行讨论,并将链接贴到这里?

4 个赞

您是如何添加 Quantcast 所需的脚本的?是通过带有 src(外部)的 script 标签添加,如下所示:

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

还是以内联方式添加,如下所示?

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

你好,
感谢你们的回复。

@sam 我在创建这个主题的同一天向 Quantcast 提交了工单,他们今天回复了我。据称他们正在查看该问题,希望他们能找到解决方案。我认为这对他们来说应该是一个相对简单的修复,因为“接受选择”按钮目前工作正常。希望他们能认识到 Discourse 和 Ember.js 是足够广泛使用的技术,值得为此提供专门的修复。

@Johani 我本人并没有直接添加 Quantcast 脚本,TheMoneyTizer 提供了一个脚本,会自动为我添加(同时还会查找 TCF2 事件等)。如果你感兴趣,可以查看这个脚本:此处

该脚本似乎会在页面中第一个 <script> 标签之前插入一个新的 <script> 元素。虽然我无法完全确定,但我非常确信曾在某些 Quantcast 官方文档示例中看到过几乎完全相同的脚本,因此我推测这是一个用于集成 Quantcast Choice 的相当常用的脚本。

再次感谢你们的时间。

我们短期内不太可能安全地移除数组原型。多年来,它们一直非常无害,并且提供了极大的便利。

或许你可以联系 Quantcast,获取不依赖数组原型的脚本版本?

2 个赞

大家好,
我有一些新信息要告诉大家,并且已经找到了这个问题的解决方案。

今天我又深入研究了这个 bug,发现了一些更多信息。

通过查看 quantcast 的 cmp2ui-fr.js 文件,我找到了 bug 发生的位置,它在这个函数中(我们只有压缩版本):

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

如你所见,这个函数使用了 for..in 循环,而变量 t 是一个数组。我们之前已经解释过,ember.js 扩展了 JavaScript 的原生 Array 对象。其中一个修改就是添加了一个 _super 属性。

这个 _super 属性指向某个 ROOT() 函数,该函数似乎引用了 ember.js 中的 _utils.ROOT(也许这对你们中的一些人很熟悉,但对我来说并不熟悉 ^^)。这个 ROOT() 函数是不可扩展的。

显然,这个 _super 属性被认为是可枚举的,这意味着在对数组使用 for..in 循环时,_super 会被当作一个“普通”条目(不像 valuesbindvalueOf 等数组对象中的函数)。

我认为这是 ember.js 的一个 bug,而不是期望的行为。

因此,我已经能够让这个 bug 非常可复现。具体做法是:创建一个对象数组,如下所示:

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

看到这一点让我觉得这个 bug 很可能已经广泛存在,并且并不特定于 quantcast。基本上,任何使用 for..in 的人都可能遇到不一致的行为或严重 bug。

对我来说,这并不完全是 Discourse 或 quantcast 的问题,而显然是 ember.js 的问题。不过,在 ember.js 修复之前,我们仍然需要找到解决方法 ^^。

好消息是,我认为我已经找到了修复方法。

一种方法是在每个 for..in 循环(可能也包括 for eachfor of 等)中添加一行:if (!objs.hasOwnProperty(i)) {continue};。这不会改变 Array 的 _super 异常行为,但在本地阻止访问它。显然,这意味着这种方法不适用于我们无法控制的外部脚本,比如我的 quantcast 用例。

另一种方法,我认为这是正确的方向,是修改 JavaScript 的 Array 原型(也就是覆盖 ember.js 自己的覆盖 ^^),使 _super 不可枚举。为此,我们需要在 ember.js 被调用和评估之后运行以下 JavaScript 代码:

//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}); 行?

目前,我通过以下方式本地修复了这个 bug:在调用 quantcast 的 choice.js 脚本之前,添加以下两行:

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

我认为有人可以通过创建一个名为 ‘fix_ember.js’ 的文件(包含这两行),并通过反向代理(例如 Nginx)静态提供它,然后在主题页脚中使用自定义功能添加 <script src="/fix_emmber.js"></script> 行来本地修复这个 bug。由于 Discourse 的脚本提取机制(参见 https://meta.discourse.org/t/custom-javascript-in-head-disappear/144356),直接写入脚本而不是创建链接是行不通的。

我希望这个主题能帮助到其他人。我明天将在 ember.js 上提交一个工单,看看这到底是一个 bug 还是一种非常奇怪但有意为之的行为。

我会随时向大家汇报,看看我们是否需要在 Discourse 中包含修复方案。

PS:非常感谢 Angus Croll 在 Extending JavaScript Natives – JavaScript, JavaScript… 上的文章,他的帖子对我帮助很大!

3 个赞

非常感谢 @OsaAjani 关注此事。我们的 Discourse 论坛也遇到了完全相同的问题,目前只能停留在他们的第 12 版(这是最后一个不会报错的版本)。

最新版本已是第 23 版,我们非常希望能使用最新版。

期待尽快得知如何解决此问题。

你好 @Terrapop,很高兴这个主题能帮到你。目前,我在 ember.js 仓库中提出的问题(https://github.com/emberjs/ember.js/issues/19289)上完全没有收到任何反馈。Quantcast 方面也没有给予任何回应。

不过,好消息是,我已经在按照我之前消息中描述的修复方案操作:创建一个名为 fix_ember.js 的文件,内容如下:

Object.defineProperty(Array.prototype, '_super', {'enumerable': false});

然后在主题页脚(通过管理面板的自定义功能)添加以下代码:

<script defer src="/fix_emmber.js"></script>

该解决方案确实解决了问题,而且截至目前我尚未发现任何副作用。因此,我认为你可以放心地使用此修复方案,并迁移到 Quantcast 的 v23 版本。

2 个赞

我们在加载 choice.js 之前添加了该行代码,这似乎也能正常工作。

关于另一种解决方案:我们的 Discourse 前面没有反向代理,因此不确定如何正确创建 fix_ember.js 文件。在 Docker 环境中这是否可行?有什么建议吗?

嗯,如果你能在 choice.js 之前加载它,那就更好了!

1 个赞

是的,有效了。我们真不知该如何感谢您,为此我们苦思冥想了好几天,却始终找不到问题所在。我们与Quantcast来回沟通了20封邮件,但他们也未能定位到问题。

嗯,如果你和 Quantcast 有良好的沟通渠道,或许可以指引他们查看这篇帖子,以便他们进行修复。我确实尝试过,但由于我并非他们的客户,他们的支持团队并未给予太多回应。

好的,说得对。