PageSourceSearch

https://www.pymnts.com/wp-content/plugins/pymnts-ads/assets/js/dianomi-skeleton.js?ver=1.0.20

js pymnts.com collected 2026-09-24 06:33:34 UTC 6,960 bytes, 234 lines download raw bytes

1/**
2 * Removes a Dianomi slot's loading placeholder once the ad is on screen, or
3 * once the vendor answers with nothing.
4 *
5 * Enqueued by Pymnts_Ads_Frontend::skeleton_markup(), so every placement that
6 * prints a placeholder gets this watcher, whoever renders the slot.
7 *
8 * ES6, no jQuery.
9 */
10( () => {
11	'use strict';
12
13	// contextfeed.js sets data-dianomi-complete="1" as soon as it has an
14	// answer, which for a filled slot is still before the creative renders.
15	// Measured on an article: complete at ~100ms, creative at ~1000ms. On a tag
16	// archive the gap is longer, complete at 1400ms and the creative at 2600ms,
17	// and giving up early costs a collapse followed by the ad pushing back in.
18	const GRACE_MS = 2500;
19
20	// A height within this multiple of the slot's reserved height is the ad's
21	// real size and is shown at once. Anything beyond it is the vendor's first
22	// pass: measured 0 -> 1300px -> 275px over 800ms on an article, against a
23	// slot reserving 250px.
24	const SANE_FACTOR = 2;
25
26	// How long to sit on a height beyond that before believing it anyway, so a
27	// genuinely tall unit is not hidden forever. Must outlast the gap between
28	// the vendor's two resizes, measured at 800ms.
29	const STABLE_MS = 2000;
30
31	// Past contextfeed.js's normal response on a slow connection. Without it a
32	// no-fill the vendor never reports would shimmer forever.
33	const TIMEOUT_MS = 8000;
34
35	// That timeout only means anything once the vendor has been asked, which
36	// with dianomi-lazy.js is whenever a slot is approached rather than at page
37	// load. Slots wait here until it signals; where nothing lazy-loads, the
38	// request is already in flight by the load event and they arm then.
39	let asked = false;
40	const waiting = new Set();
41
42	const armAll = () => {
43		asked = true;
44		waiting.forEach( ( arm ) => arm() );
45		waiting.clear();
46	};
47
48	document.addEventListener( 'pymnts-dianomi-loading', armAll );
49	window.addEventListener( 'load', () => {
50		if ( ! window.pymntsDianomiLazy ) {
51			armAll();
52		}
53	}, { once: true } );
54
55	const watch = ( slot ) => {
56		const skeleton = slot.querySelector( ':scope > .pymnts-ad-skeleton' );
57		if ( ! skeleton || slot.dataset.pymntsSkeletonWatched === '1' || window.pymntsAdsHoldSkeleton ) {
58			return;
59		}
60		slot.dataset.pymntsSkeletonWatched = '1';
61
62		let done = false;
63		let detached = false;
64		let released = false;
65		let graceTimer = 0;
66		let stableTimer = 0;
67		let timer = 0;
68		let lastHeight = -1;
69		let reserved = 0;
70		let watchedFrame = null;
71
72		const frame = () => slot.querySelector( 'iframe' );
73
74		// Hold the space the placeholder is occupying. This runs before the
75		// vendor script does anything, because contextfeed.js puts its own
76		// height on the container: measured on a tag archive, height:0px at
77		// 1400ms, then auto and a 420px ad at 2600ms. Waiting for its iframe to
78		// appear means measuring a container it has already collapsed.
79		const pin = () => {
80			if ( reserved > 0 ) {
81				return;
82			}
83			reserved = Math.round( slot.getBoundingClientRect().height );
84			if ( reserved > 0 ) {
85				slot.style.minHeight = reserved + 'px';
86				slot.style.maxHeight = reserved + 'px';
87				slot.style.overflow = 'hidden';
88			}
89		};
90
91		// Once the vendor has an iframe in the slot, the placeholder stops
92		// taking space and covers it instead. In flow the two stack, so the
93		// container is briefly placeholder-plus-ad tall and everything below it
94		// moves down and then snaps back.
95		const detach = () => {
96			if ( detached ) {
97				return;
98			}
99			detached = true;
100			pin();
101			slot.classList.add( 'pymnts-ad--filling' );
102		};
103
104		// Let the slot take the ad's real height, or collapse if there is none.
105		// Watching stops here rather than when the placeholder goes, because the
106		// vendor's second resize lands well after the ad is first visible.
107		const release = () => {
108			if ( released ) {
109				return;
110			}
111			released = true;
112			window.clearTimeout( stableTimer );
113			obs.disconnect();
114			if ( resize ) {
115				resize.disconnect();
116			}
117			slot.style.minHeight = '';
118			slot.style.maxHeight = '';
119			slot.style.overflow = '';
120		};
121
122		// The vendor appends an empty iframe within milliseconds and fills it
123		// much later, so height is the only signal that anything is on screen.
124		const adVisible = () => {
125			const el = frame();
126			return !! el && el.getBoundingClientRect().height > 0;
127		};
128
129		const resize = 'ResizeObserver' in window ? new ResizeObserver( () => check() ) : null;
130		const obs = new MutationObserver( () => check() );
131
132		const finish = () => {
133			if ( done ) {
134				return;
135			}
136			done = true;
137			window.clearTimeout( graceTimer );
138			window.clearTimeout( timer );
139			skeleton.remove();
140			slot.classList.remove( 'pymnts-ad--filling' );
141		};
142
143		// Show the ad: lift the clamp and drop the placeholder together, so a
144		// half-sized ad is never on screen behind a clipped container.
145		const settle = () => {
146			finish();
147			release();
148		};
149
150		function check() {
151			const el = frame();
152
153			if ( el ) {
154				detach();
155				if ( resize && watchedFrame !== el ) {
156					watchedFrame = el;
157					resize.observe( el );
158				}
159
160				const height = Math.round( el.getBoundingClientRect().height );
161				if ( height !== lastHeight ) {
162					lastHeight = height;
163					window.clearTimeout( stableTimer );
164
165					if ( height > 0 ) {
166						if ( ! reserved || height <= reserved * SANE_FACTOR ) {
167							settle();
168							return;
169						}
170						// The vendor's first pass. Keep the placeholder over it
171						// until it corrects itself, or believe it if it does not.
172						stableTimer = window.setTimeout( settle, STABLE_MS );
173					}
174				}
175			}
176
177			if ( done ) {
178				return;
179			}
180
181			if ( '1' === slot.getAttribute( 'data-dianomi-complete' ) && ! graceTimer ) {
182				graceTimer = window.setTimeout( () => {
183					if ( adVisible() ) {
184						return;
185					}
186					// Nothing is coming: drop the placeholder and let the slot
187					// collapse.
188					settle();
189				}, GRACE_MS );
190			}
191		}
192
193		obs.observe( slot, {
194			childList: true,
195			attributes: true,
196			attributeFilter: [ 'data-dianomi-complete', 'data-dianomi-loading', 'style' ],
197		} );
198
199		const armTimeout = () => {
200			if ( timer || done ) {
201				return;
202			}
203			timer = window.setTimeout( () => {
204				finish();
205				release();
206			}, TIMEOUT_MS );
207		};
208
209		if ( asked ) {
210			armTimeout();
211		} else {
212			waiting.add( armTimeout );
213		}
214
215		// Claim the space first, then start from the slot's current state rather
216		// than waiting for a change: the vendor can act before this script runs.
217		pin();
218		check();
219	};
220
221	const scan = ( root ) => {
222		( root || document ).querySelectorAll( '.dianomi_context' ).forEach( watch );
223	};
224
225	// Loaders that inject slots after page load (News & Trends, the
226	// headlines-grid "Load more") call scan() on the container they filled.
227	window.pymntsDianomiSkeleton = { watch, scan };
228
229	if ( document.readyState === 'loading' ) {
230		document.addEventListener( 'DOMContentLoaded', () => scan() );
231	} else {
232		scan();
233	}
234} )();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.