背景: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 事件来间接实现的。