背景:iframe 限制
Google 广告内容是通过 iframe 展示和交互,利用 iframe 特性实现限制外部对广告内容的获取和交互。
iframe contentWindow 返回的 Window 受到浏览器同源策略规则约束,进一步获取 contentWindow.document 触发跨域安全错误,“Blocked a frame with origin "https://xx.com" from accessing a cross-origin frame.”,导致不能再进一步获取 contentWindow.document 子元素的广告内容。
// 例如 iframe 内嵌广告页来自 https://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-8126932081996221&output=html&h=90&slotname=2801015661&adk=4006315846&adf=1431886828&pi=t.ma~as.2801015661&w=391&abgtt=6&lmt=1735795401&rafmt=12&format=391x90&url=https%3A%2F%2Fwww.trytheapps.com%2Fpages%2Fresearch%2Findex.html%23%2Fb&wgl=1&uach=WyJBbmRyb2lkIiwiNi4wIiwiIiwiTmV4dXMgNSIsIjEzMS4wLjY3NzguMTQwIixudWxsLDEsbnVsbCwiNjQiLFtbIkdvb2dsZSBDaHJvbWUiLCIxMzEuMC42Nzc4LjE0MCJdLFsiQ2hyb21pdW0iLCIxMzEuMC42Nzc4LjE0MCJdLFsiTm90X0EgQnJhbmQiLCIyNC4wLjAuMCJdXSwwXQ..&dt=1735801468091&bpp=31&bdt=2256&idt=-M&shv=r20241212&mjsv=m202412090101&ptt=9&saldr=aa&abxe=1&cookie=ID%3D6eab13b4702cd034%3AT%3D1735798711%3ART%3D1735799215%3AS%3DALNI_MahObO0v1dvzdoveX-SAgWmt6MJOA&gpic=UID%3D00000fd0d6a44a06%3AT%3D1735798711%3ART%3D1735799215%3AS%3DALNI_MYQf4efES5JJzqJO8QSpXT1-S9CDA&eo_id_str=ID%3D8bfdd561462d51be%3AT%3D1735798711%3ART%3D1735799215%3AS%3DAA-AfjYOEBdpbBUsf4-IKn7bZout&prev_fmts=0x0&nras=1&correlator=8789770548456&frm=20&pv=1&u_tz=480&u_his=10&u_h=602&u_w=411&u_ah=602&u_aw=411&u_cd=30&u_sd=2&dmc=8&adx=10&ady=165&biw=411&bih=602&scr_x=0&scr_y=0&eid=31089329%2C31089340%2C95332590%2C95332923%2C95345966%2C95347432&oid=2&pvsid=188917818573070&tmod=1441886544&uas=0&nvt=2&fc=1920&brdim=0%2C0%2C0%2C0%2C411%2C0%2C411%2C602%2C411%2C602&vis=1&rsz=%7C%7CeE%7C&abl=CS&pfx=0&fu=256&bc=31&bz=1&td=1&tdf=2&psd=W251bGwsbnVsbCxudWxsLDNd&nt=1&ifi=2&uci=a!2&fsb=1&dtd=14
// 此时 contentWindow 返回的受限对象信息
{
0: global {window: global, self: global, location: Location, closed: false, frames: global, …},
1: global {window: global, self: global, location: Location, closed: false, frames: global, …},
2: global {window: global, self: global, location: Location, closed: false, frames: global, …},
blur: ƒ blur(),
close: ƒ close(),
closed: false,
focus: ƒ focus(),
frames: global {0: global, 1: global, 2: global, window: global, self: global, …},
length: 3,
location: Location {Symbol(Symbol.toStringTag): undefined, …},
opener: null,
parent: Window {0: global, 1: global, 2: global, 3: global, 4: global, 5: Window, …},
postMessage: ƒ postMessage(),
self: global {0: global, 1: global, 2: global, window: global, …},
then: [Exception: SecurityError: Blocked a frame with origin …,
top: Window {0: global, 1: global, 2: global, 3: global, …},
window: global {0: global, 1: global, 2: global, window: global, …}
Symbol(Symbol.hasInstance): undefined,
Symbol(Symbol.isConcatSpreadable): undefined,
Symbol(Symbol.toStringTag): undefined
}
一、广告填充
Google 通过 iframe postMessage 通信实现是否填充逻辑。
iframe 广告页
通信代码和数据由服务端直接渲染,在广告页 DOM 加载完时即触发消息发送,目标窗口无限制。
// 成功填充需满足2个条件:googMsgType = “adpnt”;返回的 iframe 源和注册监听时的 iframe 源是同一个
window.top.postMessage(
'{"key_value":[{"key":"qid","value":"***"}],"googMsgType":"adpnt"}',
'*'
);
// 其他消息还有2个
window.parent.postMessage(
'{"googMsgType":"pvt""token":"***"}',
'*'
);
window.top.postMessage(
'{
"msg_type":"adsense-labs",
"key_value":[{"key":"settings","value":"[\\\"ca-pub-8126932081996221\\\",[[1]],null,[[\\\"ID=***:T=1735878120:RT=1735885265:S=***\\\",1769574120,\\\"/\\\",\\\"trytheapps.com\\\",1],[\\\"UID=***:T=1735878120:RT=1735885265:S=***\\\",1769574120,\\\"/\\\",\\\"trytheapps.com\\\",2]],[\\\"ID=***:T=1735878120:RT=1735885265:S=***\\\",1751430120,\\\"/\\\",\\\"trytheapps.com\\\"]]"}],
"googMsgType":"sth"
}',
'*'
);
父级页
// 源码 https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8126932081996221
// 调用 google_sa_impl
// 源码 https://pagead2.googlesyndication.com/pagead/managed/js/adsense/m202412090101/show_ads_impl_fy2021.js
// 初始创建全局对象 google_sa_impl, 值为 $X 函数
// => aY 函数
// => eY 函数
// => lY 函数
// => zY 函数
// => tY 函数
// => oY 函数
// => $R 函数,注册 message 监听 iframe 广告页广播的消息,同时监听 iframe 广告页的 load 情况,
// 广告页 load 完成后,延迟 1s 检查广告位填充情况,如果此时广告位的广告状态不是 filled,则设置为 unfilled。
目前是通过监听 iframe 广告页父级 ins 容器上的 data-ad-status 属性是否 filled 来判断是否填充成功。监听 Google iframe postMessage 通信方式已在调研页测试过(通过 postMsg 自定义事件上报),测试正常。
二、广告点击
限制点击事件监听 -- 浏览器
点击事件同样受浏览器同源策略规则约束,导致不能直接监听 Google iframe 里广告素材点击。
点击事件校验 -- 浏览器 + Goolge
每个 iframe 广告页会加载一套通用的点击事件保护代码(目前版本是 https://tpc.googlesyndication.com/pagead/js/r20241212/r20110914/client/qs_click_protection_fy2021.js)
事件派发方式无效原因:保护代码会在捕获阶段对广告素材各层级注入点击事件监听,通过点击事件对象的 isTrusted 属性(浏览器限制,浏览器还会限制派发事件伪装点击坐标等信息),识别点击是来自事件派发还是来自用户点击行为,如果是事件派发则通过阻止点击事件传播,使各层级均屏蔽掉此次点击响应,并通过 navigator.sendBeacon 上报 Google 后台。
点击交互后续还有对事件对象的触发时间、缩放、坐标偏移等校验,透明度似乎没有做校验。
// 源码 https://tpc.googlesyndication.com/pagead/js/r20241212/r20110914/client/qs_click_protection_fy2021.js
P(R, "click", e => {
if (this.I) {
var g = {
cx: e.clientX,
cy: e.clientY,
et: Date.now(),
qid: this.H
};
var h = dc;
var k = "J";
h.J && h.hasOwnProperty(k) || (k = new h,
h.J = k);
h = [];
!g.eid && h.length && (g.eid = h.toString());
gc(g);
this.I = !1
}
if (e.isTrusted === !1 && K(this.data, 15))
e.preventDefault ? e.preventDefault() : e.returnValue = !1,
e.stopImmediatePropagation(),
Nb();
else {
g = -1;
h = [];
for (var l of this.s) {
k = l.B;
var m = k !== -1;
if (!(L(l.j, 3) <= g || l.u || m && h[k] === !1)) {
var w = !m || h[k] || this.i[k].contains(e.target);
m && w && (h[k] = !0);
if (k = w)
if (k = e,
m = l.j,
L(m, 2) > 0 && N(m, 5) > 0)
k = this.A[N(m, 5)],
k = k !== void 0 && Date.now() < k + L(m, 2);
else if (M(m)) {
{
const v = (l.B >= 0 ? this.i[l.B] : R.body).getBoundingClientRect()
, F = Number(oc(R.body, "zoom") || "1")
, [Ac,Bc] = [k.clientX, k.clientY]
, [ja,ka,Fa,Ga] = [Ac / F - v.left, Bc / F - v.top, v.width, v.height];
if (!(Fa > 0 && Ga > 0) || isNaN(ja) || isNaN(ka) || ja < 0 || ka < 0)
k = !1;
else {
m = pc(M(l.j));
w = !(ja >= m.left && Fa - ja > m.right && ka >= m.top && Ga - ka > m.bottom);
var I = K(l.j, 12);
if (this.g && (K(this.data, 12) || I) && k.timeStamp - this.g.timeStamp < 300) {
k = this.g.changedTouches[0];
const [la,ma] = [k.clientX / F - v.left, k.clientY / F - v.top];
!isNaN(la) && !isNaN(ma) && la >= 0 && ma >= 0 && (w = (w = K(this.data, 16) || I ? w : !1) || !(la >= m.left && Fa - la > m.right && ma >= m.top && Ga - ma > m.bottom))
}
k = w
}
}
} else
k = L(m, 11) > 0 ? uc(this, k, l) : !0;
if (k) {
var t = l;
g = L(l.j, 3)
}
}
}
if (t)
switch (l = t.j,
N(l, 4)) {
case 2:
case 3:
e.preventDefault ? e.preventDefault() : e.returnValue = !1;
g = Date.now();
g - t.F > 500 && (t.F = g,
++t.D);
g = t.j;
if (L(g, 8) && t.D >= L(g, 8))
if (t.u = !0,
this.h && L(g, 2) > 0)
tc(this);
else if (this.l.length > 0 && M(g))
for (var E of this.l)
Y(E, !1);
Nb();
E = ib(l);
for (const v of this.C)
v(e, E)
}
}
}
目前获取广告区点击情况是通过监听 iframe blur 事件来间接实现的。