Recently, I get this postMessage couldn't be cloned error. It is happening on most of the latest browsers like Chrome 68, Firefox 61.0, IE11, Edge.
Failed to execute 'postMessage' on 'Window': function (a){if(qe.$a.hasOwnProperty(a))return qe.$a[a]}
could not be cloned.
The stack trace is:
Error: Failed to execute 'postMessage' on 'Window': function (a){if(qe.$a.hasOwnProperty(a))return qe.$a[a]}
could not be cloned.
at _reportEvent (eval at (:1:35637), :94:35)
at eval (eval at (:1:35637), :55:5)
at eval (eval at (:1:35637), :433:11)
Searching through the source of my page in DevTools shows gtm.js
as the source of the code fragment:
I have a Google Tag Manager tracking code on my page. Why is this happening?
This happens all the time, if something can not be duplicated by the structured clone algorithm. This algorithm is used by window.postMessage
. If we read the documentation from window.postMessage
for the first parameter:
message
Data to be sent to the other window. The data is serialized using the structured clone algorithm.
and then open the description from structured clone algorithm (see last link above) then we can read:
The structured clone algorithm is an algorithm defined by the HTML5 specification for copying complex JavaScript objects. It is used internally when transferring data to and from Workers via postMessage()
or when storing objects with IndexedDB
. It builds up a clone by recursing through the input object while maintaining a map of previously visited references in order to avoid infinitely traversing cycles.
Things that don't work with structured clone
Supported types
- All primitive types (Note: However not symbols)
Boolean
object
String
object
Date
RegExp
(Note: The lastIndex field is not preserved.)
Blob
File
FileList
ArrayBuffer
ArrayBufferView
(Note: This basically means all typed arrays like Int32Array etc.)
ImageData
Array
Object
(Note: This just includes plain objects (e.g. from object literals))
Map
Set
I tested it with some objects and I can show you following examples when this is happening...
Example with custom function
var obj = {something: function(){}};
window.postMessage(obj, '*'); // DataCloneError
Example with native function
var obj = {something: window.alert};
window.postMessage(obj, '*'); // DataCloneError
The same we will see with native functions like Boolean
, Date
, String
, RegExp
, Number
, Array
.
Example with native object
var obj = {something: document};
window.postMessage(obj, '*'); // DataCloneError
Example with HTML element object
var obj = {something: document.createElement('b')};
window.postMessage(obj, '*'); // DataCloneError
We could write more examples if we read the description from The structured clone algorithm above, but I think here it is enough.
What we could do to avoid this error
In our code we could use only supported types (see the list above) in our objects. But in not our code we have to contact the developers from this code and write them how they have to correct their code. In the case from the Google Tag Manager you could write it to the Official Google Tag Manager Forum with description how they have to correct their code.
Workaround for some browsers
In some browsers you can not override native methods for security reasons. For example IE does not allow to override window.postMessage
. But other browsers like Chrome allow to override this method like follows:
var postMessageTemp = window.postMessage;
window.postMessage = function(message, targetOrigin, transfer)
{
postMessageTemp(JSON.parse(JSON.stringify(message)), targetOrigin, transfer)
};
But note that window
is a global object of JavaScript context and it is not created from the prototype
. In other words: you can not override it with window.prototype.postMessage = ...
.
Example with workaround
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.');
How to implement this workaround
Please put this overriden window.postMessage
function in script part in your HTML page before Google Tag Manager script. But in better way you could help the developers from Google Tag Manager to understand and to correct this error and you can wait for corrected Google Tag Manager script.
These errors are caused by Facebook crawlers executing JavaScript code.
I have had occurrences of this error from these IPs (all in the Facebook IP ranges) and user agents:
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)
To get an up to date list of Facebook crawler IPs, see this command from https://developers.facebook.com/docs/sharing/webmasters/crawler/ :
whois -h whois.radb.net -- '-i origin AS32934' | grep ^route
You will need to update your error reporting mechanism to filter out errors from these IP ranges.
You could do this on the client side in JavaScript by determining the user's IP address upon an error (see How to get client's IP address using JavaScript?).
Or you could do this on the server side. Here is an example for 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));
}
}