PageSourceSearch

https://www.allelementsinc.net/wp-content/plugins/gf-utm-tracker/assets/js/tracker.js?ver=1.7.0

js allelementsinc.net collected 2026-10-06 20:22:09 UTC 24,087 bytes, 627 lines download raw bytes

1(function () {
2	'use strict';
3
4	// Guard: prevent the script running twice if somehow enqueued or concatenated twice.
5	if (window._brandoUtmInit) { return; }
6	window._brandoUtmInit = true;
7
8	// Cookie lifetime comes from PHP settings (wp_localize_script). Fall back to 90 days.
9	// v1.5.0
10	var COOKIE_DAYS   = (window.brandoUtmTrackerSettings && window.brandoUtmTrackerSettings.cookieDays)
11		? parseInt(window.brandoUtmTrackerSettings.cookieDays, 10)
12		: 90;
13	var COOKIE_PREFIX = 'gf-utm-';
14
15	// Search engine hostnames. Key = substring to match in hostname, Value = display name.
16	var SEARCH_ENGINES = {
17		'google':     'Google',
18		'bing':       'Bing',
19		'yahoo':      'Yahoo',
20		'duckduckgo': 'DuckDuckGo',
21		'baidu':      'Baidu',
22		'yandex':     'Yandex',
23		'ecosia':     'Ecosia',
24		'ask.com':    'Ask',
25	};
26
27	// Social / referral hostnames. Key = exact hostname without www., Value = display name.
28	var SOCIAL_DOMAINS = {
29		'facebook.com':   'Facebook',
30		'fb.com':         'Facebook',
31		'instagram.com':  'Instagram',
32		'linkedin.com':   'LinkedIn',
33		'lnkd.in':        'LinkedIn',
34		'twitter.com':    'X (Twitter)',
35		'x.com':          'X (Twitter)',
36		't.co':           'X (Twitter)',
37		'youtube.com':    'YouTube',
38		'youtu.be':       'YouTube',
39		'tiktok.com':     'TikTok',
40		'vm.tiktok.com':  'TikTok',
41		'snapchat.com':   'Snapchat',
42		'reddit.com':     'Reddit',
43		'out.reddit.com': 'Reddit',
44		'pinterest.com':  'Pinterest',
45		'threads.net':    'Threads',
46	};
47
48	// -------------------------------------------------------------------------
49	// Cookie helpers
50	// -------------------------------------------------------------------------
51
52	function getCookie(name) {
53		return Cookies.get(COOKIE_PREFIX + name);
54	}
55
56	function setCookie(name, value) {
57		if (value !== null && value !== undefined && value !== '') {
58			Cookies.set(COOKIE_PREFIX + name, String(value), { expires: COOKIE_DAYS });
59		}
60	}
61
62	function getHostname(url) {
63		try {
64			return new URL(url).hostname.replace(/^www\./, '');
65		} catch (e) {
66			return '';
67		}
68	}
69
70	// -------------------------------------------------------------------------
71	// Source label builder
72	//
73	// Priority:
74	//   1. utm_source (with medium-aware labels for Google CPC, Facebook Paid, etc.)
75	//   2. Platform click IDs (gclid, fbclid, ttclid, msclkid, li_fat_id, twclid)
76	//   3. Referrer → search engine organic
77	//   4. Referrer → known social domain
78	//   5. Referrer → unknown domain  → "Referral: hostname"
79	//   6. Nothing                    → "Direct"
80	// -------------------------------------------------------------------------
81
82	function buildSource(params, referrer) {
83		if (params.utm_source) {
84			var src    = params.utm_source;
85			var srcL   = src.toLowerCase();
86			var medium = (params.utm_medium || '').toLowerCase();
87			var paidMedia   = ['cpc', 'ppc', 'paid', 'paidsocial', 'paid_social', 'social_paid'];
88			var organicMedia = ['organic'];
89			var socialMedia  = ['social', 'social-organic'];
90
91			var isPaid    = paidMedia.indexOf(medium) !== -1;
92			var isOrganic = organicMedia.indexOf(medium) !== -1;
93			var isSocial  = socialMedia.indexOf(medium) !== -1;
94
95			// Google-specific labels
96			if (srcL === 'google' || srcL === 'google_ads' || srcL === 'google ads') {
97				if (isPaid)    return 'Google CPC';
98				if (isOrganic) return 'Google Organic';
99				if (medium === 'display') return 'Google Display';
100				if (medium === 'video')   return 'Google Video';
101				if (medium)    return 'Google / ' + params.utm_medium;
102				return 'Google';
103			}
104
105			// Facebook / Meta-specific labels
106			if (srcL === 'facebook' || srcL === 'fb' || srcL === 'meta') {
107				if (isPaid || isSocial) return 'Facebook Paid';
108				if (isOrganic)          return 'Facebook Organic';
109				if (medium)             return 'Facebook / ' + params.utm_medium;
110				return 'Facebook';
111			}
112
113			// Instagram
114			if (srcL === 'instagram' || srcL === 'ig') {
115				if (isPaid || isSocial) return 'Instagram Paid';
116				if (isOrganic)          return 'Instagram Organic';
117				return 'Instagram';
118			}
119
120			// Generic fallback for any other utm_source
121			if (isPaid)    return src + ' Ads';
122			if (isOrganic) return src + ' Organic';
123			if (medium)    return src + ' / ' + params.utm_medium;
124			return src;
125		}
126
127		// Click ID inference (platform pixels strip referrer header on redirect)
128		if (params.gclid || params.gbraid) { return 'Google Ads'; }
129		if (params.fbclid)                  { return 'Facebook Ads'; }
130		if (params.ttclid)                  { return 'TikTok Ads'; }
131		if (params.msclkid)                 { return 'Microsoft Ads'; }
132		if (params.li_fat_id)               { return 'LinkedIn Ads'; }
133		if (params.twclid)                  { return 'X (Twitter) Ads'; }
134
135		// Referrer-based detection
136		if (referrer) {
137			var hostname    = getHostname(referrer);
138			var ownHostname = window.location.hostname.replace(/^www\./, '');
139
140			// Same-site navigation is not a referral.
141			if (hostname && hostname === ownHostname) {
142				return 'Direct';
143			}
144
145			if (hostname) {
146				// Search engines → Organic
147				for (var engine in SEARCH_ENGINES) {
148					if (hostname.indexOf(engine) !== -1) {
149						return SEARCH_ENGINES[engine] + ' Organic';
150					}
151				}
152				// Social networks
153				for (var domain in SOCIAL_DOMAINS) {
154					if (hostname === domain || hostname.slice(-(domain.length + 1)) === '.' + domain) {
155						return SOCIAL_DOMAINS[domain];
156					}
157				}
158				// Unknown referral
159				return 'Referral: ' + hostname;
160			}
161		}
162
163		return 'Direct';
164	}
165
166	// -------------------------------------------------------------------------
167	// Channel classifier — maps a detailed source label to a broad channel.
168	// Paid | Organic | Social | Direct | Referral | Other
169	// -------------------------------------------------------------------------
170
171	var SOCIAL_PLATFORMS = [
172		'Facebook', 'Instagram', 'LinkedIn', 'X (Twitter)', 'YouTube',
173		'TikTok', 'Snapchat', 'Reddit', 'Pinterest', 'Threads',
174	];
175
176	function buildChannel(source) {
177		if (isPaidCampaign(source))          { return 'Paid'; }
178		if (source.indexOf('Organic') !== -1) { return 'Organic'; }
179		for (var i = 0; i < SOCIAL_PLATFORMS.length; i++) {
180			if (source.indexOf(SOCIAL_PLATFORMS[i]) !== -1) { return 'Social'; }
181		}
182		if (source === 'Direct')              { return 'Direct'; }
183		if (source.indexOf('Referral:') === 0) { return 'Referral'; }
184		return 'Other';
185	}
186
187	// -------------------------------------------------------------------------
188	// Read Google Analytics client ID from the _ga cookie.
189	// Works for both GA4 and Universal Analytics — both use the same cookie
190	// format: GA1.X.XXXXXXXXXX.XXXXXXXXXX  (client ID = last two segments).
191	// Falls back to ga.getAll() for Universal Analytics tracker objects.
192	// -------------------------------------------------------------------------
193
194	function readGAClientId() {
195		var gaCookie = Cookies.get('_ga') || '';
196		if (gaCookie) {
197			var parts = gaCookie.split('.');
198			if (parts.length >= 4) { return parts.slice(-2).join('.'); }
199		}
200		try {
201			if (typeof ga === 'function' && typeof ga.getAll === 'function') {
202				var trackers = ga.getAll();
203				if (trackers.length > 0) { return trackers[0].get('clientId') || ''; }
204			}
205		} catch (e) {}
206		return '';
207	}
208
209	// -------------------------------------------------------------------------
210	// Device type detection — returns 'Mobile', 'Tablet', or 'Desktop'.
211	// -------------------------------------------------------------------------
212
213	function detectDeviceType() {
214		var ua = navigator.userAgent;
215		if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) { return 'Tablet'; }
216		if (/Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/i.test(ua)) { return 'Mobile'; }
217		return 'Desktop';
218	}
219
220	// -------------------------------------------------------------------------
221	// Is a source label a paid campaign (vs organic / direct / referral)?
222	// Used to decide whether to add the source to the Campaign Attribution field.
223	// -------------------------------------------------------------------------
224
225	var PAID_PLATFORMS = [
226		'Google CPC', 'Google Display', 'Google Video', 'Google Ads', 'Google PMax / Broad',
227		'Facebook Paid', 'Facebook Ads',
228		'Instagram Paid', 'Instagram Ads',
229		'TikTok Ads',
230		'Microsoft Ads',
231		'LinkedIn Ads',
232		'X (Twitter) Ads',
233		'Reddit Ads',
234		'Snapchat Ads',
235		'Pinterest Ads',
236	];
237
238	function isPaidCampaign(source) {
239		// Exact match against known paid labels first.
240		for (var i = 0; i < PAID_PLATFORMS.length; i++) {
241			if (source === PAID_PLATFORMS[i]) { return true; }
242		}
243		// Catch-all for "[anything] Ads", "[anything] CPC", "[anything] Paid".
244		return (/\s(Ads|CPC|Paid)$/).test(source);
245	}
246
247	// Append a paid source to the accumulated campaign-touches cookie if not already present.
248	function recordCampaignTouch(source) {
249		if (!isPaidCampaign(source)) { return; }
250		var existing  = getCookie('campaign-touches') || '';
251		var touchList = existing ? existing.split(', ') : [];
252		if (touchList.indexOf(source) === -1) {
253			touchList.push(source);
254			setCookie('campaign-touches', touchList.join(', '));
255		}
256	}
257
258	// -------------------------------------------------------------------------
259	// Does the current page load carry trackable signals?
260	// Returns true when there are UTM params, click IDs, or an external referrer.
261	// "Direct" navigation (no signals) should not overwrite a previously captured
262	// paid/organic source.
263	// -------------------------------------------------------------------------
264
265	function hasTrackingSignals(params, referrer) {
266		if (params.utm_source || params.utm_medium || params.utm_campaign ||
267		    params.utm_term   || params.utm_content || params.utm_id)       { return true; }
268		if (params.gclid || params.gbraid || params.fbclid || params.ttclid ||
269		    params.msclkid || params.li_fat_id || params.twclid)             { return true; }
270		if (referrer) {
271			var hostname    = getHostname(referrer);
272			var ownHostname = window.location.hostname.replace(/^www\./, '');
273			if (hostname && hostname !== ownHostname)                         { return true; }
274		}
275		return false;
276	}
277
278	// -------------------------------------------------------------------------
279	// Visit counter — called on every page load but only increments once per
280	// browser session. sessionStorage clears when the tab/browser closes, so
281	// browsing multiple pages in one sitting still counts as a single visit.
282	// -------------------------------------------------------------------------
283
284	function trackVisit() {
285		// sessionStorage persists for one browser session and clears on close.
286		// Wrap in try/catch — some privacy modes restrict storage APIs.
287		try {
288			if (sessionStorage.getItem('brando-utm-session')) {
289				return;
290			}
291			sessionStorage.setItem('brando-utm-session', '1');
292		} catch (e) {
293			// sessionStorage unavailable — fall back to a session cookie (no expiry = tab-scoped).
294			if (getCookie('_session')) { return; }
295			Cookies.set(COOKIE_PREFIX + '_session', '1'); // no expires = session cookie
296		}
297
298		var today    = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
299		var count    = parseInt(getCookie('visit-count') || '0', 10) + 1;
300		var days     = parseInt(getCookie('visit-days')  || '0', 10);
301		var lastDate = getCookie('last-visit-date');
302
303		if (!lastDate || lastDate !== today) {
304			days += 1;
305		}
306
307		setCookie('visit-count',     count);
308		setCookie('visit-days',      days);
309		setCookie('last-visit-date', today);
310	}
311
312	// -------------------------------------------------------------------------
313	// Main capture logic — runs on every page load
314	// -------------------------------------------------------------------------
315
316	function capture() {
317		var urlParams = new URLSearchParams(window.location.search);
318
319		var params = {
320			utm_source:   urlParams.get('utm_source'),
321			utm_medium:   urlParams.get('utm_medium'),
322			utm_campaign: urlParams.get('utm_campaign'),
323			utm_term:     urlParams.get('utm_term'),
324			utm_content:  urlParams.get('utm_content'),
325			utm_id:       urlParams.get('utm_id'),
326			gclid:        urlParams.get('gclid'),
327			gbraid:       urlParams.get('gbraid'),
328			fbclid:       urlParams.get('fbclid'),
329			ttclid:       urlParams.get('ttclid'),
330			msclkid:      urlParams.get('msclkid'),
331			li_fat_id:    urlParams.get('li_fat_id'),
332			twclid:       urlParams.get('twclid'),
333			// Google Ads ValueTrack parameters — add to your final URL suffix as
334			// &campaignid={campaignid}&adgroupid={adgroupid}
335			campaignid:   urlParams.get('campaignid'),
336			adgroupid:    urlParams.get('adgroupid'),
337		};
338
339		var referrer = document.referrer || '';
340
341		// If a click ID is present but referrer is missing (cross-domain redirect
342		// strips it), infer the origin platform so source detection still works.
343		if (!referrer) {
344			if (params.gclid || params.gbraid) { referrer = 'https://google.com/'; }
345			else if (params.fbclid)            { referrer = 'https://facebook.com/'; }
346			else if (params.ttclid)            { referrer = 'https://www.tiktok.com/'; }
347			else if (params.msclkid)           { referrer = 'https://bing.com/'; }
348			else if (params.li_fat_id)         { referrer = 'https://linkedin.com/'; }
349			else if (params.twclid)            { referrer = 'https://x.com/'; }
350		}
351
352		// Always count the visit.
353		trackVisit();
354
355		var source  = buildSource(params, referrer);
356
357		// PMax / broad-match detection: Google paid click + campaign ID present + no keyword.
358		// PMax campaigns never populate {keyword} even when it is in the URL suffix.
359		// Standard search campaigns almost always return a keyword, so an empty utm_term
360		// alongside a campaignid strongly indicates PMax (or an uncategorised broad match).
361		if ((source === 'Google CPC' || source === 'Google Ads') && params.campaignid && !params.utm_term) {
362			source = 'Google PMax / Broad';
363		}
364
365		var channel      = buildChannel(source);
366		var isFirstVisit = !getCookie('first-source');
367
368		// Always accumulate paid campaign touches regardless of first/last-touch logic.
369		recordCampaignTouch(source);
370
371		// ---- Identity / device cookies — set on every page load ----------------
372
373		// Unique visitor ID — generated once and never overwritten.
374		if (!getCookie('visitor-id')) {
375			var vid = Math.random().toString(36).substr(2, 9) + '-' + Date.now().toString(36);
376			setCookie('visitor-id', vid);
377		}
378
379		// First-seen date — set once on the very first visit (drives days-to-convert).
380		if (!getCookie('first-seen')) {
381			setCookie('first-seen', new Date().toISOString().slice(0, 10));
382		}
383
384		// Device type — always refreshed in case the visitor switches devices.
385		setCookie('device-type', detectDeviceType());
386
387		// User agent — always refresh (captures the current browser/device).
388		setCookie('user-agent', navigator.userAgent);
389
390		// Facebook Pixel cookies — copy if the Pixel has set them.
391		var fbcVal = Cookies.get('_fbc') || '';
392		var fbpVal = Cookies.get('_fbp') || '';
393		if (fbcVal) { setCookie('fbc', fbcVal); }
394		if (fbpVal) { setCookie('fbp', fbpVal); }
395
396		// GA client ID — set once (stable for the lifetime of the browser profile).
397		if (!getCookie('ga-client-id')) {
398			var gaCid = readGAClientId();
399			if (gaCid) { setCookie('ga-client-id', gaCid); }
400		}
401
402		// ---- Attribution cookies — first-touch / last-touch --------------------
403
404		if (isFirstVisit) {
405			// First touch — set once and never overwrite.
406			setCookie('first-source',       source);
407			setCookie('first-channel',      channel);
408			setCookie('first-utm_campaign', params.utm_campaign);
409			setCookie('first-utm_medium',   params.utm_medium);
410
411			// Also seed the last-touch cookies on first visit.
412			setCookie('source',       source);
413			setCookie('channel',      channel);
414			setCookie('referrer',     referrer || 'Direct');
415			setCookie('source-url',   window.location.href);
416			setCookie('utm_source',   params.utm_source);
417			setCookie('utm_medium',   params.utm_medium);
418			setCookie('utm_campaign', params.utm_campaign);
419			setCookie('utm_term',     params.utm_term);
420			setCookie('utm_content',  params.utm_content);
421			setCookie('utm_id',       params.utm_id);
422			setCookie('campaign-id',  params.campaignid);
423			setCookie('adgroup-id',   params.adgroupid);
424
425		} else if (hasTrackingSignals(params, referrer)) {
426			// Subsequent visit with a trackable signal — update last-touch only.
427			// "Direct" return visits do NOT overwrite a paid/organic attribution.
428			setCookie('source',       source);
429			setCookie('channel',      channel);
430			setCookie('referrer',     referrer);
431			setCookie('source-url',   window.location.href);
432			setCookie('utm_source',   params.utm_source);
433			setCookie('utm_medium',   params.utm_medium);
434			setCookie('utm_campaign', params.utm_campaign);
435			setCookie('utm_term',     params.utm_term);
436			setCookie('utm_content',  params.utm_content);
437			setCookie('utm_id',       params.utm_id);
438			setCookie('campaign-id',  params.campaignid);
439			setCookie('adgroup-id',   params.adgroupid);
440		}
441	}
442
443	// -------------------------------------------------------------------------
444	// Fill hidden GF form fields from cookies
445	// -------------------------------------------------------------------------
446
447	function fillFields() {
448		var fieldMap = {
449			'campaign-touches':  '.gf-utm-campaign-touches',
450			'source':            '.gf-utm-source',
451			'first-source':      '.gf-utm-first-source',
452			'channel':           '.gf-utm-channel',
453			'first-channel':     '.gf-utm-first-channel',
454			'visit-count':       '.gf-utm-visit-count',
455			'visit-days':        '.gf-utm-visit-days',
456			'referrer':          '.gf-utm-referrer',
457			'source-url':        '.gf-utm-source-url',
458			'utm_source':        '.gf-utm-utm_source',
459			'utm_medium':        '.gf-utm-utm_medium',
460			'utm_campaign':      '.gf-utm-utm_campaign',
461			'utm_term':          '.gf-utm-utm_term',
462			'utm_content':       '.gf-utm-utm_content',
463			'utm_id':            '.gf-utm-utm_id',
464			'user-agent':        '.gf-utm-user-agent',
465			'fbc':               '.gf-utm-fbc',
466			'fbp':               '.gf-utm-fbp',
467			'ga-client-id':      '.gf-utm-ga-client-id',
468			'visitor-id':        '.gf-utm-visitor-id',
469			'campaign-id':       '.gf-utm-campaign-id',
470			'adgroup-id':        '.gf-utm-adgroup-id',
471			// v1.7.0
472			'device-type':         '.gf-utm-device-type',
473			'first-utm_campaign':  '.gf-utm-first-utm_campaign',
474			'first-utm_medium':    '.gf-utm-first-utm_medium',
475		};
476
477		for (var key in fieldMap) {
478			if (!Object.prototype.hasOwnProperty.call(fieldMap, key)) { continue; }
479			var value = getCookie(key);
480			if (!value && (key === 'source' || key === 'first-source' || key === 'channel' || key === 'first-channel')) {
481				value = 'Direct';
482			}
483			if (!value) { continue; }
484			var selector = fieldMap[key];
485			document.querySelectorAll(selector + ' input[type="text"]').forEach(function (input) {
486				input.value = value;
487			});
488		}
489
490		// Days to convert — compute from first-seen date cookie.
491		var firstSeen = getCookie('first-seen');
492		if (firstSeen) {
493			try {
494				var msDay   = 86400000;
495				// Parse as UTC midnight to avoid timezone drift.
496				var seenMs  = new Date(firstSeen + 'T00:00:00Z').getTime();
497				var todayMs = new Date(new Date().toISOString().slice(0, 10) + 'T00:00:00Z').getTime();
498				var daysAgo = Math.max(0, Math.round((todayMs - seenMs) / msDay));
499				document.querySelectorAll('.gf-utm-days-to-convert input[type="text"]').forEach(function (input) {
500					input.value = String(daysAgo);
501				});
502			} catch (e) {}
503		}
504
505		// Converting page URL — the page where this form is being submitted.
506		document.querySelectorAll('.gf-utm-converting-page input[type="text"]').forEach(function (input) {
507			input.value = window.location.href;
508		});
509
510		// Form load time — filled once when the form first renders (spam detection fallback).
511		// Never overwritten so the original render time is preserved on multi-page forms.
512		document.querySelectorAll('.gf-utm-form-load-time input[type="text"]').forEach(function (input) {
513			if (!input.value) {
514				input.value = Date.now().toString();
515			}
516		});
517	}
518
519	// -------------------------------------------------------------------------
520	// Form view tracking — fires once per browser session per form as soon as
521	// the form is visible on the page.  Uses the same sessionStorage guard as
522	// form starts so page refreshes and multi-page AJAX advances don't inflate
523	// the count.
524	// -------------------------------------------------------------------------
525
526	function trackFormView(formId) {
527		var key = 'brando-fview-' + formId;
528		try {
529			if (sessionStorage.getItem(key)) { return; }
530			sessionStorage.setItem(key, '1');
531		} catch (e) {
532			if (getCookie('_fview_' + formId)) { return; }
533			Cookies.set(COOKIE_PREFIX + '_fview_' + formId, '1');
534		}
535		if (!window.brandoUtmTrackerSettings || !window.brandoUtmTrackerSettings.ajaxUrl || !window.brandoUtmTrackerSettings.viewNonce) { return; }
536		var body = new URLSearchParams({
537			action:  'brando_track_form_view',
538			nonce:   window.brandoUtmTrackerSettings.viewNonce,
539			form_id: formId,
540			source:  getCookie('source') || 'Direct',
541		});
542		fetch(window.brandoUtmTrackerSettings.ajaxUrl, { method: 'POST', credentials: 'same-origin', body: body })
543			.catch(function () {});
544	}
545
546	function bindFormViewTracking() {
547		document.querySelectorAll('.gform_wrapper form').forEach(function (form) {
548			var match = form.id ? form.id.match(/gform_(\d+)/) : null;
549			var formId = match ? match[1] : '0';
550			if (!formId || formId === '0') { return; }
551			trackFormView(formId);
552		});
553	}
554
555	// -------------------------------------------------------------------------
556	// Form start tracking — fires once per browser session per form on first
557	// focus inside any Gravity Forms form field.
558	// -------------------------------------------------------------------------
559
560	function trackFormStart(formId) {
561		var key = 'brando-fstart-' + formId;
562		try {
563			if (sessionStorage.getItem(key)) { return; }
564			sessionStorage.setItem(key, '1');
565		} catch (e) {
566			if (getCookie('_fstart_' + formId)) { return; }
567			Cookies.set(COOKIE_PREFIX + '_fstart_' + formId, '1');
568		}
569		if (!window.brandoUtmTrackerSettings || !window.brandoUtmTrackerSettings.ajaxUrl || !window.brandoUtmTrackerSettings.startNonce) { return; }
570		var body = new URLSearchParams({
571			action:  'brando_track_form_start',
572			nonce:   window.brandoUtmTrackerSettings.startNonce,
573			form_id: formId,
574			source:  getCookie('source') || 'Direct',
575		});
576		fetch(window.brandoUtmTrackerSettings.ajaxUrl, { method: 'POST', credentials: 'same-origin', body: body })
577			.catch(function () {});
578	}
579
580	function bindFormStartTracking() {
581		document.querySelectorAll('.gform_wrapper form').forEach(function (form) {
582			var match = form.id ? form.id.match(/gform_(\d+)/) : null;
583			var formId = match ? match[1] : '0';
584			if (!formId || formId === '0') { return; }
585			var inputs = form.querySelectorAll('input:not([type=hidden]):not([type=submit]):not([type=button]), textarea, select');
586			inputs.forEach(function (input) {
587				input.addEventListener('focus', function onFocus() {
588					input.removeEventListener('focus', onFocus);
589					trackFormStart(formId);
590				});
591			});
592		});
593	}
594
595	// -------------------------------------------------------------------------
596	// Bootstrap
597	// -------------------------------------------------------------------------
598
599	capture();
600
601	if (document.readyState === 'loading') {
602		document.addEventListener('DOMContentLoaded', function () {
603			fillFields();
604			bindFormViewTracking();
605			bindFormStartTracking();
606		});
607	} else {
608		fillFields();
609		bindFormViewTracking();
610		bindFormStartTracking();
611	}
612
613	// Re-run after GF renders or re-renders (AJAX / multi-page forms).
614	// bindFormViewTracking uses sessionStorage so it won't double-count.
615	document.addEventListener('gform/post_render', function () {
616		fillFields();
617		bindFormViewTracking();
618		bindFormStartTracking();
619	});
620	if (typeof jQuery !== 'undefined') {
621		jQuery(document).on('gform_post_render', function () {
622			fillFields();
623			bindFormViewTracking();
624			bindFormStartTracking();
625		});
626	}
627})();

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.