【问题标题】:Failed to execute 'postMessage' on 'Window' GoogleTagManager无法在“窗口”GoogleTagManager 上执行“postMessage”
【发布时间】:2020-11-27 21:11:39
【问题描述】:

最近,我收到此 postMessage 无法克隆 错误。大多数最新的浏览器都会出现这种情况,例如 Chrome 68、Firefox 61.0、IE11、Edge。

在“窗口”上执行“postMessage”失败:function (a){if(qe.$a.hasOwnProperty(a))return qe.$a[a]} 无法克隆。

堆栈跟踪是:

错误:无法在“窗口”上执行“postMessage”:无法克隆function (a){if(qe.$a.hasOwnProperty(a))return qe.$a[a]}
在 _reportEvent(评估在 (:1:35637), :94:35)
在评估(评估在(:1:35637),:55:5)
在评估(评估在(:1:35637),:433:11)

在 DevTools 中搜索我的页面源代码显示 gtm.js 作为代码片段的源代码:

我的网页上有一个 Google 跟踪代码管理器跟踪代码。为什么会这样?

【问题讨论】:

  • 我刚刚开始在我们的应用程序中看到这个错误,从几天前开始,即使我们没有更改代码。它发生在所有主要浏览器(chrome 68、Firefox、Edge 17)中,但仅限于 Windows。
  • 我最近也开始看到这个错误 - 在 Linux+Chrome、iOS+Safari、Windows+Chrome 中。我们最近才将 Google 跟踪代码管理器添加到我们的应用程序中。堆栈跟踪的下一行是at _reportEvent
  • 我们说的第一次出现是 2018-08-30T17:13 UTC。似乎在所有浏览器/设备上,我们也追踪到了 GTM。
  • 如果它有助于找出原因:我的 GTM 中唯一有效的跟踪代码是 Facebook Pixel。还有一个 Pardot 跟踪代码,但是它在发生错误的主机名上不活动。我还编辑了问题以添加堆栈跟踪,这表明错误是一个匿名代码块。
  • 我已经编辑了问题以包含一个屏幕截图,显示 gtm.js 作为函数的来源。

标签: javascript error-handling google-tag-manager


【解决方案1】:

如果 structured clone algorithm 无法复制某些内容,这种情况总是会发生。 window.postMessage 使用此算法。如果我们从 window.postMessage 阅读第一个参数的文档:

消息
要发送到另一个窗口的数据。数据使用structured clone algorithm进行序列化。

然后打开结构化克隆算法的描述(见上面最后一个链接)然后我们可以阅读:

结构化克隆算法是由 HTML5 规范定义的用于复制复杂 JavaScript 对象的算法。它在通过postMessage() 与Workers 之间传输数据或使用IndexedDB 存储对象时在内部使用。它通过递归遍历输入对象来构建一个克隆,同时维护先前访问过的引用的映射,以避免无限遍历循环。

不能使用结构化克隆的东西

  • ErrorFunction对象不能被结构化克隆算法复制;试图这样做会抛出一个 DATA_CLONE_ERR 异常。

  • 尝试克隆 DOM 节点同样会引发 DATA_CLONE_ERR 异常。

  • 不保留对象的某些参数:

    • RegExp 对象的 lastIndex 字段未保留。
    • 属性描述符、setter 和 getter(以及类似的类似元数据的功能)不会重复。例如,如果一个对象 使用属性描述符将其标记为只读,它将是读写的 在副本中,因为这是默认条件。
    • 原型链不会遍历和复制。

支持的类型

我用一些对象对其进行了测试,当发生这种情况时,我可以向您展示以下示例...

自定义函数的错误示例

var obj = {something: function(){}};
window.postMessage(obj, '*'); // DataCloneError

本机函数的错误示例

var obj = {something: window.alert};
window.postMessage(obj, '*'); // DataCloneError

我们将在 BooleanDateStringRegExpNumberArray 等原生函数中看到同样的情况。

本机对象的错误示例

var obj = {something: document};
window.postMessage(obj, '*'); // DataCloneError

HTML 元素对象的错误示例

var obj = {something: document.createElement('b')};
window.postMessage(obj, '*'); // DataCloneError

如果我们阅读上面的结构化克隆算法的描述,我们可以写更多的例子,但我认为这里已经足够了。

我们可以做些什么来避免这个错误

在我们的代码中,我们只能在对象中使用支持的类型(参见上面的列表)。但在我们的代码中,我们必须从该代码中联系开发人员并编写他们必须如何更正他们的代码。如果是来自 Google 跟踪代码管理器的案例,您可以将其写至 the Official Google Tag Manager Forum,并说明他们必须如何更正代码。

某些浏览器的解决方法

在某些浏览器中,出于安全原因,您不能覆盖本机方法。例如IE 不允许覆盖window.postMessage。但其他浏览器(如 Chrome)允许重写此方法,如下所示:

var postMessageTemp = window.postMessage;
window.postMessage = function(message, targetOrigin, transfer)
{
    postMessageTemp(JSON.parse(JSON.stringify(message)), targetOrigin, transfer)
};

但请注意,window 是 JavaScript 上下文的全局对象,它不是从 prototype 创建的。换句话说:你不能用window.prototype.postMessage = ...覆盖它。

解决方法示例

var obj = {something: window};

var postMessageTemp = window.postMessage;
window.postMessage = function(message, targetOrigin, transfer)
{
    function cloneObject(obj)
    {
        var clone = {};
        for(var i in obj)
        {
            if(typeof(obj[i]) == 'object' && obj[i] != null)
            {
                if((''+obj[i]) == '[object Window]')
                {
                    delete obj[i];
                    continue;
                }

                clone[i] = cloneObject(obj[i]);
            }
            else
                clone[i] = obj[i];
        }
        return clone;
    }

    // to avoid weird error causing by window object by JSON.stringify() execution.
    var clone = cloneObject(message);

    postMessageTemp(JSON.parse(JSON.stringify(clone)), targetOrigin, transfer)
};

window.postMessage(obj, '*');

console.log('We do not have any errors.');

如何实施此解决方法

请将此覆盖的 window.postMessage 函数放在您的 HTML 页面的脚本部分中,然后放在 Google 跟踪代码管理器脚本之前。但您可以更好地帮助 Google Tag Manager 的开发人员了解并纠正此错误,您可以等待更正的 Google Tag Manager 脚本。

【讨论】:

  • 好像有人在 GTM 论坛上发过这个问题:productforums.google.com/forum/#!topic/tag-manager/…
  • @Yoshi,但它与 OP 的问题相同,没有人回答这个问题。这是在回答这个问题之前从我们的问题中复制的。可能有人想通过这种方式获得这笔赏金。
  • 嘿巴拉塔,感谢您的精彩回答。正如您所展示的它发生的原因很少,这是可以接受的,因为我已经尝试了这些。我面临的主要问题是在我测试它的任何设备中都不会发生错误。正如你所建议的,我可以尝试覆盖方法,但我只能假设它有效。希望它有效。
  • @cris 您可能在自己的设备上看不到它的原因是它来自 Facebook 的爬虫。请参阅我的附加答案。
  • 你是很棒的伙伴
【解决方案2】:

这些错误是由 Facebook 爬虫执行 JavaScript 代码引起的。

我在这些 IP(都在 Facebook IP 范围内)和用户代理中遇到了这个错误:

66.220.149.14 - Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0
  31.13.115.2 - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
 173.252.87.1 - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
69.171.251.11 - facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)

要获取 Facebook 爬虫 IP 的最新列表,请参阅https://developers.facebook.com/docs/sharing/webmasters/crawler/ 中的此命令:

whois -h whois.radb.net -- '-i origin AS32934' | grep ^route

您需要更新错误报告机制以过滤掉这些 IP 范围内的错误。

您可以在 JavaScript 中的客户端执行此操作,方法是在出现错误时确定用户的 IP 地址(请参阅How to get client's IP address using JavaScript?)。

或者您可以在服务器端执行此操作。以下是 ASP.NET MVC 的示例:

using System.Linq;
// Requires the IPAddressRange NuGet library:
// https://www.nuget.org/packages/IPAddressRange/
using NetTools;

public class FacebookClientDetector
{
    /// <summary>
    /// The list of CIDR ranges of facebook IPs that its crawlers use.
    /// To generate, run
    ///     whois -h whois.radb.net -- '-i origin AS32934' | grep ^route
    /// https://developers.facebook.com/docs/sharing/webmasters/crawler/
    /// </summary>
    static readonly string[] facebookIpRanges = new string[] {
        "204.15.20.0/22",
        "69.63.176.0/20",
        "66.220.144.0/20",
        "66.220.144.0/21",
        "69.63.184.0/21",
        "69.63.176.0/21",
        "74.119.76.0/22",
        "69.171.255.0/24",
        "173.252.64.0/18",
        "69.171.224.0/19",
        "69.171.224.0/20",
        "103.4.96.0/22",
        "69.63.176.0/24",
        "173.252.64.0/19",
        "173.252.70.0/24",
        "31.13.64.0/18",
        "31.13.24.0/21",
        "66.220.152.0/21",
        "66.220.159.0/24",
        "69.171.239.0/24",
        "69.171.240.0/20",
        "31.13.64.0/19",
        "31.13.64.0/24",
        "31.13.65.0/24",
        "31.13.67.0/24",
        "31.13.68.0/24",
        "31.13.69.0/24",
        "31.13.70.0/24",
        "31.13.71.0/24",
        "31.13.72.0/24",
        "31.13.73.0/24",
        "31.13.74.0/24",
        "31.13.75.0/24",
        "31.13.76.0/24",
        "31.13.77.0/24",
        "31.13.96.0/19",
        "31.13.66.0/24",
        "173.252.96.0/19",
        "69.63.178.0/24",
        "31.13.78.0/24",
        "31.13.79.0/24",
        "31.13.80.0/24",
        "31.13.82.0/24",
        "31.13.83.0/24",
        "31.13.84.0/24",
        "31.13.85.0/24",
        "31.13.86.0/24",
        "31.13.87.0/24",
        "31.13.88.0/24",
        "31.13.89.0/24",
        "31.13.90.0/24",
        "31.13.91.0/24",
        "31.13.92.0/24",
        "31.13.93.0/24",
        "31.13.94.0/24",
        "31.13.95.0/24",
        "69.171.253.0/24",
        "69.63.186.0/24",
        "31.13.81.0/24",
        "179.60.192.0/22",
        "179.60.192.0/24",
        "179.60.193.0/24",
        "179.60.194.0/24",
        "179.60.195.0/24",
        "185.60.216.0/22",
        "45.64.40.0/22",
        "185.60.216.0/24",
        "185.60.217.0/24",
        "185.60.218.0/24",
        "185.60.219.0/24",
        "129.134.0.0/16",
        "157.240.0.0/16",
        "157.240.8.0/24",
        "157.240.0.0/24",
        "157.240.1.0/24",
        "157.240.2.0/24",
        "157.240.3.0/24",
        "157.240.4.0/24",
        "157.240.5.0/24",
        "157.240.6.0/24",
        "157.240.7.0/24",
        "157.240.9.0/24",
        "157.240.10.0/24",
        "157.240.16.0/24",
        "157.240.19.0/24",
        "157.240.11.0/24",
        "157.240.12.0/24",
        "157.240.13.0/24",
        "157.240.14.0/24",
        "157.240.15.0/24",
        "157.240.17.0/24",
        "157.240.18.0/24",
        "157.240.20.0/24",
        "157.240.21.0/24",
        "157.240.22.0/24",
        "157.240.23.0/24",
        "129.134.0.0/17",
        "157.240.0.0/17",
        "69.171.250.0/24",
        "204.15.20.0/22",
        "69.63.176.0/20",
        "69.63.176.0/21",
        "69.63.184.0/21",
        "66.220.144.0/20",
        "69.63.176.0/20",
    };

    public static bool IsFacebookClient(string ip)
    {
        IPAddressRange parsedIp;
        if (!IPAddressRange.TryParse(ip, out parsedIp)) {
            return false;
        }

        return facebookIpRanges.Any(cidr => IPAddressRange.Parse(cidr).Contains(parsedIp));
    }
}

【讨论】:

  • 不知道 Facebook 是如何抓取网站的,但是如果它们都来自 Facebook 抓取工具,那么在多个浏览器/设备上发生错误是不是有点奇怪?
  • @RMenke 很好。这不是来自 Facebook 在 Instagram 和 Facebook 上加载的 iframe 的错误吗?
  • @RMenke 这很奇怪,但数据不会说谎 - 请参阅我编辑的 facebook IP 日志的答案,导致混合平台/浏览器上的错误
  • 您可以通过whois -h whois.radb.net -- '-i origin AS32934' | grep ^route developers.facebook.com/docs/sharing/webmasters/crawler获取始终最新的 Facebook 爬虫 ips 列表
  • @Jake 谢谢。我已更新我的答案以包含此命令和如何使用 CIDR 条目的代码示例。
【解决方案3】:

您可能是我在通过Workbox window 使用服务人员时遇到的困惑的受害者。该包使用其模块的两个不同实例。有一组静态模块。在主 Workbox 模块的保护伞下同样有另一组。这些在内部调用它们的静态对应项

var payload = {key: "value"},

{ Workbox, messageSW } = await import('workbox-window'), // these represent static modules without a contextual `this`

wb = new Workbox('/service-worker.js'); // calls to this works on a single instance that interfaces with the underlying static equivalents

这意味着虽然你需要这样做

messageSW(wb.getSW(), payload);

调用wb.messageSW(wb.getSW(), payload) 会导致 OP 的错误,因为这里的实际负载是循环服务工作者而不是我们的对象字面量负载。在这种情况下,实例化版本将适用于:

wb.messageSW( payload);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-07
    • 2019-08-26
    • 1970-01-01
    • 1970-01-01
    • 2021-10-25
    • 2014-04-29
    • 2014-06-15
    • 2018-05-29
    相关资源
    最近更新 更多