// Anti-bot vendors fingerprint injected globals aggressively. On these hosts,
// abort before defining any Unite helper symbols.
{
    const __snPreflightHost = (typeof location !== 'undefined' && location.host) ? location.host.toLowerCase() : '';
    if (/(?:^|\.)(tripit\.com|concur\.com|delta\.com|united\.com|aa\.com|southwest\.com|alaskaair\.com|jetblue\.com|britishairways\.com|lufthansa\.com|airfrance\.us|klm\.com|ana\.co\.jp|jal\.co\.jp|marriott\.com|hilton\.com|hyatt\.com|ihg\.com|ticketmaster\.com|livenation\.com|stubhub\.com|expedia\.com|booking\.com|priceline\.com|kayak\.com|vrbo\.com|airbnb\.com|hotels\.com|tripadvisor\.com)$/.test(__snPreflightHost)) {
        throw '__unite_snapihelper_antibot_skip';
    }
}

var SNAPIHelper = {}
var snAPIHelper = {}

try {
snAPIHelper.common = new class {
    // SECURITY: relay a credential fill to SAME-ORIGIN child frames only.
    // targetOrigin is pinned to our own origin, so WebKit refuses to deliver the
    // message to any cross-origin iframe (ad/analytics/third-party widgets) — they
    // can neither read the credential nor spoof a matching origin. Never use "*"
    // here: that would broadcast the plaintext password to every embedded frame.
    fillInSameOriginFrames(username, password) {
        var frames = document.getElementsByTagName("iframe");
        var message = {name: "unite-autofill-fill", username: username, password: password};
        var origin = window.location.origin;
        // "null" origin (sandboxed/opaque frames) must never receive credentials.
        if (!origin || origin === "null") { return; }
        for (var index = 0; index < frames.length; index++) {
            try {
                frames[index].contentWindow.postMessage(message, origin);
            } catch (e) {}
        }
    }
}

// Perf short-circuit: on pure video/streaming hosts there are no user-facing
// forms and no native autofill consumer; we already skip the MutationObserver
// at the bottom of this file. Parsing+executing the remaining ~600 lines of
// Notification shim and AutoFill class at document-start on every frame is
// pure overhead that steals time from YouTube's / Netflix's bootstrap. Stub
// the surface area that native autofill calls into and abort the rest of
// this script with a top-level throw. (WKUserScript swallows the error and
// stops evaluating this source; subsequent WKUserScripts run normally.)
var __sn_host = (typeof location !== 'undefined' && location.host) ? location.host.toLowerCase() : '';
if (/(?:^|\.)(youtube\.com|youtube-nocookie\.com|youtu\.be|ytimg\.com|googlevideo\.com|netflix\.com|nflxvideo\.net|vimeo\.com|twitch\.tv|hulu\.com|disneyplus\.com|hbomax\.com|max\.com|primevideo\.com|tv\.apple\.com)$/.test(__sn_host)) {
    snAPIHelper.autoFill = {
        autoFill: function(){},
        domUpdated: function(){},
        domUpdatedThrottled: function(){}
    };
    // Native code (SNScriptMessageHandler.sendNotificationPermissionResult:)
    // unconditionally calls window.uniteSetNotificationPermission() via
    // evaluateJavaScript on any page that pokes the notificationPermission
    // message handler. If we skip the rest of this script, that symbol doesn't
    // exist and every call throws a noisy TypeError into the console AND
    // triggers an NSError completionHandler on the ObjC side. Stub it.
    window.uniteSetNotificationPermission = function(){};
    window._uniteNotificationPermission = 'default';
    window._uniteNotificationCallbacks = [];

    // Critical for YouTube performance: real Safari exposes window.safari and
    // window.safari.pushNotification as globals. YouTube's client fingerprint
    // combines UA with a probe for these — if the UA says Safari but the
    // globals are missing, YT flags us as a non-Safari engine and serves a
    // degraded feed (visible as videoplayback 403s + slow page-manager mount).
    // The original (now-skipped) body of this script set these up; we have to
    // re-create them here or the skip regresses YT detection.
    try {
        if (typeof window.safari === 'undefined') {
            function __UniteSafariRemoteNotification(){}
            __UniteSafariRemoteNotification.prototype.toString = function(){ return '[object SafariRemoteNotification]'; };
            __UniteSafariRemoteNotification.prototype.permission = function(siteURL, webPushID, callback) {
                // Safari returns an object with a `permission` field.
                return { permission: 'default' };
            };
            __UniteSafariRemoteNotification.prototype.requestPermission = function(url, id, options, callback) {
                var result = { permission: 'default', deviceToken: null };
                if (typeof callback === 'function') {
                    try { callback(result); } catch(_) {}
                }
                return result;
            };
            window.safari = {
                pushNotification: new __UniteSafariRemoteNotification()
            };
        }
    } catch(_) {}

    window.__uniteSNAPIHelperSkipped = true;
    throw '__unite_snapihelper_skip';
}

// Message listener for relaying callbacks/events to iFrames
// Perf: YouTube / Google Meet / Slack blast postMessage between frames at
// high frequency (player ↔ chrome, chat ↔ composer). A `console.log(e)` here
// forced a cross-process console IPC + serialization of the full message
// payload for every one of those — tens of thousands of wasted bytes on
// YouTube home alone. Bail fast if the message isn't ours.
window.addEventListener('message', function(e) {
    if (typeof e.data !== "object" || e.data === null) { return; }
    if (e.data.name === "unite-autofill-fill") {
        // SECURITY: only accept a credential fill from our OWN origin. The sender
        // pinned targetOrigin to our origin, but verify e.origin as defense in
        // depth so a compromised same-origin frame is the only thing trusted.
        if (e.origin !== window.location.origin) { return; }
        try {
            if (snAPIHelper && snAPIHelper.autoFill &&
                typeof e.data.username === "string" && typeof e.data.password === "string") {
                snAPIHelper.autoFill.autoFill(e.data.username, e.data.password);
            }
        } catch (err) {}
    }
}, false);


// MARK: -
var com_bzg_unite_del_key_disable = true;

SafariRemoteNotification = class {
    
}

// Unite Notification Permission System
window._uniteNotificationPermissionStorageKey = null;
window._uniteNotificationPermission = 'default';
try {
    if (window.location && window.location.origin) {
        window._uniteNotificationPermissionStorageKey = '__unite_notification_permission__' + window.location.origin;
        var storedPermission = localStorage.getItem(window._uniteNotificationPermissionStorageKey);
        if (storedPermission === 'granted' || storedPermission === 'denied') {
            window._uniteNotificationPermission = storedPermission;
        }
    }
} catch (e) {}
window._uniteNotificationCallbacks = [];
window._uniteNotificationPermissionStatusObservers = [];

window._uniteCreateNotificationPermissionStatus = function() {
    var listeners = [];
    var status = {
        state: window._uniteNotificationPermission,
        onchange: null,
        addEventListener: function(type, listener) {
            if (type === 'change' && typeof listener === 'function') {
                listeners.push(listener);
            }
        },
        removeEventListener: function(type, listener) {
            if (type !== 'change' || typeof listener !== 'function') {
                return;
            }
            listeners = listeners.filter(function(item) { return item !== listener; });
        },
        dispatchEvent: function(event) {
            if (!event || event.type !== 'change') {
                return true;
            }
            listeners.forEach(function(listener) {
                try { listener.call(status, event); } catch (_) {}
            });
            return true;
        }
    };
    window._uniteNotificationPermissionStatusObservers.push(status);
    return status;
};

// Function called from native code to update permission state
window.uniteSetNotificationPermission = function(permission) {
    window._uniteNotificationPermission = permission;
    try {
        if (window._uniteNotificationPermissionStorageKey && (permission === 'granted' || permission === 'denied')) {
            localStorage.setItem(window._uniteNotificationPermissionStorageKey, permission);
        } else if (window._uniteNotificationPermissionStorageKey && permission === 'default') {
            localStorage.removeItem(window._uniteNotificationPermissionStorageKey);
        }
    } catch (e) {}
    // Resolve any pending permission callbacks
    while (window._uniteNotificationCallbacks.length > 0) {
        var cb = window._uniteNotificationCallbacks.shift();
        if (typeof cb === 'function') {
            cb(permission);
        }
    }
    
    // Keep navigator.permissions() notification status objects in sync.
    for (var i = 0; i < window._uniteNotificationPermissionStatusObservers.length; i++) {
        var observer = window._uniteNotificationPermissionStatusObservers[i];
        if (!observer) continue;
        
        observer.state = permission;
        
        if (typeof observer.onchange === 'function') {
            try { observer.onchange.call(observer, { type: 'change', target: observer }); } catch (_) {}
        }
        
        if (typeof observer.dispatchEvent === 'function') {
            try { observer.dispatchEvent({ type: 'change', target: observer }); } catch (_) {}
        }
    }
};

// Origin-scoped setter used by native bridge so subframe permission sync
// cannot clobber unrelated origins in the same WKWebView process.
window.uniteSetNotificationPermissionForOrigin = function(permission, origin) {
    try {
        if (origin && window.location && window.location.origin && window.location.origin !== origin) {
            return;
        }
    } catch (e) {}
    window.uniteSetNotificationPermission(permission);
};

window.oldNotification = window.Notification;

window.Notification = function(title, options) {
    // Check if notifications are allowed before creating
    if (window._uniteNotificationPermission === 'denied') {
        console.log('[Unite] Notification blocked - permission denied');
        return {};
    }
    
    let webwhatsappWorkaroundURL = document.URL;
    if (webwhatsappWorkaroundURL.includes("https://web.whatsapp.com")) {
        //webwhatsappWorkaround
        window.webkit.messageHandlers.note.postMessage([JSON.stringify(title), JSON.stringify(options)]);
    } else {
        window.webkit.messageHandlers.notifications.postMessage([title, options]);
    }
    
    // Create a mock notification object with standard properties
    var notification = {
        title: title,
        body: options ? options.body : '',
        icon: options ? options.icon : '',
        tag: options ? options.tag : '',
        onclick: null,
        onclose: null,
        onerror: null,
        onshow: null,
        close: function() {}
    };
    return notification;
};

// Copy static properties from original Notification
window.Notification.prototype = window.oldNotification ? window.oldNotification.prototype : {};

Object.defineProperty(Notification, 'permission', {
    get() {
        return window._uniteNotificationPermission;
    }
});

Notification.requestPermission = function(callback) {
    // If already decided, return immediately
    if (window._uniteNotificationPermission !== 'default') {
        var perm = window._uniteNotificationPermission;
        if (typeof callback === 'function') {
            callback(perm);
            return undefined;
        } else {
            return Promise.resolve(perm);
        }
    }
    
    // Request permission from native code
    window.webkit.messageHandlers.notificationPermission.postMessage({
        type: 'requestPermission',
        origin: window.location.origin,
        hostname: window.location.hostname
    });
    
    // Handle callback vs promise API
    if (typeof callback === 'function') {
        window._uniteNotificationCallbacks.push(callback);
        return undefined;
    } else {
        return new Promise(function(resolve) {
            window._uniteNotificationCallbacks.push(resolve);
        });
    }
};

// Some apps (including Proton Mail) check desktop notification state via
// navigator.permissions instead of Notification.permission. Bridge both APIs.
(function() {
    var canQueryPermissions = !!(navigator && navigator.permissions);
    var originalQuery = canQueryPermissions && typeof navigator.permissions.query === 'function'
        ? navigator.permissions.query.bind(navigator.permissions)
        : null;
    
    function notificationStatusPromise() {
        return Promise.resolve(window._uniteCreateNotificationPermissionStatus());
    }
    
    if (!canQueryPermissions) {
        navigator.permissions = {
            query: function(descriptor) {
                if (descriptor && descriptor.name === 'notifications') {
                    return notificationStatusPromise();
                }
                return Promise.reject(new TypeError('Permission descriptor not supported'));
            }
        };
        return;
    }
    
    navigator.permissions.query = function(descriptor) {
        if (descriptor && descriptor.name === 'notifications') {
            return notificationStatusPromise();
        }
        
        if (originalQuery) {
            return originalQuery(descriptor);
        }
        
        return Promise.reject(new TypeError('Permission query unavailable'));
    };
})();

// Sync permission from native storage as early as possible so sites that check
// Notification.permission on startup get the persisted value.
try {
    if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.notificationPermission) {
        window.webkit.messageHandlers.notificationPermission.postMessage({
            type: 'getPermissionState',
            origin: window.location.origin,
            hostname: window.location.hostname
        });
    }
} catch (e) {}

window.safari = {}
window.safari.pushNotification = new SafariRemoteNotification();

// MARK: -

snAPIHelper.autoFill = new class {
	constructor() {
		window.addEventListener('DOMContentLoaded', function() {
			// Use textContent, not innerHTML: sites like YouTube enforce
			// `require-trusted-types-for 'script'`, and innerHTML assignment
			// throws a TypeError that cascades through their error reporting.
			var style = document.createElement('style');
			style.textContent = '.SNAutofill { background-color: #ffffdd !important; }';
			if (document.head) document.head.appendChild(style);
		}, false);
		
		this.KEYCODE_ARROW_DOWN = 40;
		this.lastDomUpdateTime = 0;
		this.domUpdatePending = false;
		this.THROTTLE_DELAY = 250; // ms
		
		// CRITICAL: Bind event handlers once to preserve 'this' context
		this.boundOnUsernameChange = this.onUsernameChange.bind(this);
		this.boundOnPasswordChange = this.onPasswordChange.bind(this);
		this.boundOnUsernameFocused = this.onUsernameFocused.bind(this);
		this.boundOnPasswordFocused = this.onPasswordFocused.bind(this);
		this.boundOnUsernameBlurred = this.onUsernameBlurred.bind(this);
		this.boundOnPasswordBlurred = this.onPasswordBlurred.bind(this);
		this.boundOnKeyUp = this.onKeyUp.bind(this);
		this.boundOnKeyDown = this.onKeyDown.bind(this);
		
		// Use event delegation for robustness - attach to document to catch all login fields
		this.setupEventDelegation();
	}
	
	setupEventDelegation() {
		// Helper to check if target is a login field (works with shadow DOM via composedPath)
		const getLoginFieldFromEvent = (event) => {
			if (!this.loginFields) return null;
			
			// Check composedPath for shadow DOM traversal
			const path = event.composedPath ? event.composedPath() : [event.target];
			
			for (let element of path) {
				if (element === this.loginFields["username"]) {
					return { field: 'username', element: element };
				}
				if (element === this.loginFields["password"]) {
					return { field: 'password', element: element };
				}
			}
			
			// Also check direct target
			if (event.target === this.loginFields["username"]) {
				return { field: 'username', element: event.target };
			}
			if (event.target === this.loginFields["password"]) {
				return { field: 'password', element: event.target };
			}
			
			return null;
		};
		
		// Focus events don't bubble, so use capture phase
		document.addEventListener('focus', (event) => {
			const field = getLoginFieldFromEvent(event);
			if (!field) return;
			
			if (field.field === 'username') {
				this.boundOnUsernameFocused(event);
			} else if (field.field === 'password') {
				this.boundOnPasswordFocused(event);
			}
		}, true); // useCapture = true for focus events
		
		// Input events
		document.addEventListener('input', (event) => {
			const field = getLoginFieldFromEvent(event);
			if (!field) return;
			
			if (field.field === 'username') {
				this.boundOnUsernameChange(event);
			} else if (field.field === 'password') {
				this.boundOnPasswordChange(event);
			}
		}, false);
		
		// Keydown events (for immediate response to typing)
		document.addEventListener('keydown', (event) => {
			const field = getLoginFieldFromEvent(event);
			if (field) {
				this.boundOnKeyDown(event);
			}
		}, false);
		
		// Keyup events
		document.addEventListener('keyup', (event) => {
			const field = getLoginFieldFromEvent(event);
			if (field) {
				this.boundOnKeyUp(event);
			}
		}, false);
		
		// Blur events
		document.addEventListener('blur', (event) => {
			const field = getLoginFieldFromEvent(event);
			if (!field) return;
			
			if (field.field === 'username') {
				this.boundOnUsernameBlurred(event);
			} else if (field.field === 'password') {
				this.boundOnPasswordBlurred(event);
			}
		}, true);
	}
	
	autoFill(username, password) {
		if (typeof this.loginFields !== "undefined") {
			if (this.loginFields["username"]) {
				// Mark field as being autofilled to prevent event handler interference
				this.loginFields["username"].setAttribute('data-autofilling', 'true');
				this.setInputValue(this.loginFields["username"], username);
				this.loginFields["username"].classList.add("SNAutofill");
				// Remove the flag after React has time to process
				setTimeout(() => {
					if (this.loginFields["username"]) {
						this.loginFields["username"].removeAttribute('data-autofilling');
					}
				}, 100);
			}

			if (this.loginFields["password"]) {
				// Mark field as being autofilled to prevent event handler interference
				this.loginFields["password"].setAttribute('data-autofilling', 'true');
				this.setInputValue(this.loginFields["password"], password);
				this.loginFields["password"].classList.add("SNAutofill");
				// Remove the flag after React has time to process
				setTimeout(() => {
					if (this.loginFields["password"]) {
						this.loginFields["password"].removeAttribute('data-autofilling');
					}
				}, 100);
			}
		}
		// Relay to same-origin child frames only (structured data, origin-pinned).
		snAPIHelper.common.fillInSameOriginFrames(username, password);
	}
	
	setInputValue(element, value) {
		// Set the value using multiple methods to ensure React/Vue/Angular detect the change
		
		// Method 1: Direct property assignment using native setter
		const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
		nativeInputValueSetter.call(element, value);
		
		// Method 2: Dispatch input event (required for React)
		const inputEvent = new Event('input', { bubbles: true, cancelable: true });
		element.dispatchEvent(inputEvent);
		
		// Method 3: Dispatch change event (for older frameworks)
		const changeEvent = new Event('change', { bubbles: true, cancelable: true });
		element.dispatchEvent(changeEvent);
		
		// Method 4: Keyboard events (for additional compatibility)
		this.dispatchKeyboardEvent(element, "keydown", this.KEYCODE_ARROW_DOWN);
		this.dispatchKeyboardEvent(element, "keyup", this.KEYCODE_ARROW_DOWN);
	}
	
	domUpdatedThrottled() {
		const now = Date.now();
		
		// Throttle updates to reduce CPU usage
		if (this.domUpdatePending || (now - this.lastDomUpdateTime) < this.THROTTLE_DELAY) {
			if (!this.domUpdatePending) {
				this.domUpdatePending = true;
				setTimeout(() => {
					this.domUpdatePending = false;
					this.lastDomUpdateTime = Date.now();
					this.domUpdated();
				}, this.THROTTLE_DELAY);
			}
			return;
		}
		
		this.lastDomUpdateTime = now;
		this.domUpdated();
	}
	
	domUpdated() {
		var loginFields = this.getLoginFields();
		
		if (typeof loginFields !== "undefined") {
			// Prevent re‑processing the same password element again.
			if (typeof this.loginFields !== "undefined") {
				var samePassword = (loginFields["password"] && this.loginFields["password"] && loginFields["password"] === this.loginFields["password"]);
				var sameUsername = (loginFields["username"] && this.loginFields["username"] && loginFields["username"] === this.loginFields["username"]);
				if (samePassword && sameUsername) {
					return;
				}
			}

			var usernameValue = (loginFields["username"] ? loginFields["username"].value : "");
			var passwordValue = (loginFields["password"] ? loginFields["password"].value : "");
			
			// Check if messageHandlers.autoFill exists before posting
			if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.autoFill) {
				window.webkit.messageHandlers.autoFill.postMessage(["onLoginFound", { "username": usernameValue, "password": passwordValue, "hasPasswordField": !!(loginFields["password"]) }]);
			}

			// Remove old event listeners first (if fields were already tracked)
			if (this.loginFields && this.loginFields["username"]) {
				this.loginFields["username"].removeEventListener('input', this.boundOnUsernameChange, false);
				this.loginFields["username"].removeEventListener("focus", this.boundOnUsernameFocused, false);
				this.loginFields["username"].removeEventListener("blur", this.boundOnUsernameBlurred, false);
				this.loginFields["username"].removeEventListener("keydown", this.boundOnKeyDown, false);
				this.loginFields["username"].removeEventListener("keyup", this.boundOnKeyUp, false);
			}
			if (this.loginFields && this.loginFields["password"]) {
				this.loginFields["password"].removeEventListener('input', this.boundOnPasswordChange, false);
				this.loginFields["password"].removeEventListener("focus", this.boundOnPasswordFocused, false);
				this.loginFields["password"].removeEventListener("blur", this.boundOnPasswordBlurred, false);
				this.loginFields["password"].removeEventListener("keydown", this.boundOnKeyDown, false);
				this.loginFields["password"].removeEventListener("keyup", this.boundOnKeyUp, false);
			}
			
			// Add new event listeners with bound methods
			if (loginFields["username"]) {
				loginFields["username"].addEventListener('input', this.boundOnUsernameChange, false);
				loginFields["username"].addEventListener("focus", this.boundOnUsernameFocused, false);
				loginFields["username"].addEventListener("blur", this.boundOnUsernameBlurred, false);
				loginFields["username"].addEventListener("keydown", this.boundOnKeyDown, false);
				loginFields["username"].addEventListener("keyup", this.boundOnKeyUp, false);
			}
			if (loginFields["password"]) {
				loginFields["password"].addEventListener('input', this.boundOnPasswordChange, false);
				loginFields["password"].addEventListener("focus", this.boundOnPasswordFocused, false);
				loginFields["password"].addEventListener("blur", this.boundOnPasswordBlurred, false);
				loginFields["password"].addEventListener("keydown", this.boundOnKeyDown, false);
				loginFields["password"].addEventListener("keyup", this.boundOnKeyUp, false);
			}
		}
		this.loginFields = loginFields;
	}
	
	// Helper to search in shadow DOMs
	searchInShadowDOM(root, selector) {
		let results = Array.from(root.querySelectorAll(selector));
		
		// Search recursively in shadow roots
		root.querySelectorAll('*').forEach(el => {
			if (el.shadowRoot) {
				results = results.concat(this.searchInShadowDOM(el.shadowRoot, selector));
			}
		});
		
		return results;
	}
	
	getLoginFields() {
		// First, try to locate conventional username‑password pairs.
		// Only consider VISIBLE password fields: identifier-first pages (Google,
		// Amazon) include a hidden password input on the email step, which would
		// otherwise win here and leave the visible email field untracked.
		var passwordFields = this.allPasswordFields().filter(function(f) {
			return f.offsetParent !== null;
		});
		var passwordLength = passwordFields.length;
		var loginGroups = [];
		while (passwordLength--) {
			var curPasswordField = passwordFields[passwordLength];
			var parentForm = this.parentForm(curPasswordField);

			var possibleUserField = null;
			if (parentForm) {
				possibleUserField = this.possibleUserField(parentForm, curPasswordField);
			} else {
				possibleUserField = this.possibleUserField(document, curPasswordField);
			}

			// It is possible that no explicit username field exists on the same page (e.g. multi‑step logins).
			// In that scenario, we still want to return the password field alone so that we can at least offer
			// password filling.
			if (possibleUserField) {
				loginGroups.push([possibleUserField, curPasswordField]);
			} else {
				loginGroups.push([null, curPasswordField]);
			}
		}

		if (loginGroups.length > 0) {
			// Prefer groups that have a visible username field; otherwise fall back to password‑only group.
			for (var i = 0; i < loginGroups.length; i++) {
				var u = loginGroups[i][0];
				var p = loginGroups[i][1];
				if (u && p && u.offsetParent !== null && p.offsetParent !== null) {
					return { "username": u, "password": p };
				}
			}
			// No visible username field – return the first password field we found.
			return { "username": null, "password": loginGroups[0][1] };
		}

                // Lastly, handle pages that only ask for username / email (first step of multi‑step flows).
                var inputs = Array.from(document.querySelectorAll('input[type="text"], input[type="email"], input[type="search"]'));
                
                // Search shadow DOMs for username fields
                var shadowInputs = this.searchInShadowDOM(document, 'input[type="text"], input[type="email"], input[type="search"]');
                inputs = inputs.concat(shadowInputs);
                
                if (inputs.length > 0) {
                        // Score visible candidates so genuine username/email fields beat
                        // arbitrary text inputs (identifier-first flows like Amazon/Google).
                        var bestField = null;
                        var bestScore = -1;
                        for (var i = 0; i < inputs.length; i++) {
                                if (inputs[i].offsetParent === null) continue;
                                var attrString = ((inputs[i].name || "") + (inputs[i].id || "") + (inputs[i].placeholder || "") + (inputs[i].getAttribute("aria-label") || "")).toLowerCase();
                                var autocomplete = (inputs[i].getAttribute("autocomplete") || "").toLowerCase();
                                var isSearch = inputs[i].type.toLowerCase() === "search" || attrString.indexOf("search") !== -1 || attrString.indexOf("query") !== -1;
                                if (isSearch) continue;

                                var score = 0;
                                if (autocomplete.indexOf("username") !== -1 || autocomplete.indexOf("email") !== -1) score += 8;
                                if (inputs[i].type.toLowerCase() === "email") score += 6;
                                if (/(email|user|login|account|phone|identifier)/.test(attrString)) score += 4;
                                if (score > bestScore) {
                                        bestScore = score;
                                        bestField = inputs[i];
                                }
                        }
                        if (bestField) {
                                return { "username": bestField, "password": null };
                        }
                }
	}
	
	possibleUserField(parent, passwordField) {
		var inputFieldsByCategory = {};
		var inputs = parent.getElementsByTagName('input');
		var passwordFieldIndex = [].slice.call(inputs).indexOf(passwordField);
		var inputsByCategory = {};
		for (var i = 0; i < inputs.length; i++) {
			if (inputs[i] === passwordField || (passwordFieldIndex - i) < 0) {
				continue;
			}
			
			var fieldType = (inputs[i].hasAttribute("type") ?
							 inputs[i].getAttribute("type").toLowerCase() :
							 inputs[i].type);
			
			if (fieldType == "text"  ||
				fieldType == "email" ||
				fieldType == "url"   ||
				fieldType == "tel"   ||
				fieldType == "number") {
				if (typeof inputsByCategory[fieldType] === "undefined") {
					inputsByCategory[fieldType] = [];
				}
				
				inputsByCategory[fieldType].push([inputs[i], passwordFieldIndex - i]);
			}
		}
		
		if (typeof inputsByCategory["email"] !== "undefined") {
			return inputsByCategory["email"].sort(this.compare)[0][0];
		} else if (typeof inputsByCategory["text"] !== "undefined") {
			return inputsByCategory["text"].sort(this.compare)[0][0];
		}
	}
	
	allPasswordFields() {
		// Search regular DOM
		var inputs = Array.from(document.getElementsByTagName('input'));
		
		// Search shadow DOMs
		var shadowInputs = this.searchInShadowDOM(document, 'input');
		inputs = inputs.concat(shadowInputs);
		
		var	fields = [];
		for (let input of inputs) {
			if (input.type === 'password') {
				fields.push(input);
			}
		}
		return fields;
	}
	
	parentForm(element) {
		while (element.parentNode) {
			if (element.parentNode.nodeName.toLowerCase() === 'form') {
				return element.parentNode;
			}
			element = element.parentNode;
		}
	}
	
	compare(inputA, inputB) {
	  return (inputA[1] > inputB[1]);
	}
	
	onUsernameFocused(event) {
		// If field was autofilled, allow immediate editing
		if (event.target && event.target.classList && event.target.classList.contains("SNAutofill")) {
			event.target.classList.remove("SNAutofill");
			
			// Select all text so user can immediately type to replace
			setTimeout(() => {
				event.target.select();
			}, 0);
			
			// Don't show dropdown if user is editing an autofilled field
			return;
		}
		
		if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.autoFill) {
			var viewportOffset = event.target.getBoundingClientRect();
			window.webkit.messageHandlers.autoFill.postMessage(["onUsernameFocused", {"top": viewportOffset.bottom, "left": viewportOffset.left}]);
		}
	}
	
	onUsernameBlurred(event) {
		if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.autoFill) {
			window.webkit.messageHandlers.autoFill.postMessage(["onUsernameBlurred"]);
		}
	}
	
	onPasswordFocused(event) {
		// If field was autofilled, allow immediate editing
		if (event.target && event.target.classList && event.target.classList.contains("SNAutofill")) {
			event.target.classList.remove("SNAutofill");
			
			// Select all text so user can immediately type to replace
			setTimeout(() => {
				event.target.select();
			}, 0);
			
			// Don't show dropdown if user is editing an autofilled field
			return;
		}
		
		if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.autoFill) {
			var viewportOffset = event.target.getBoundingClientRect();
			window.webkit.messageHandlers.autoFill.postMessage(["onPasswordFocused", {"top": viewportOffset.bottom, "left": viewportOffset.left}]);
		}
	}
	
	onPasswordBlurred(event) {
		if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.autoFill) {
			window.webkit.messageHandlers.autoFill.postMessage(["onPasswordBlurred"]);
		}
	}
	
	onUsernameChange(event) {
		// Skip if we're in the middle of autofilling to avoid interference
		if (event.target && event.target.getAttribute('data-autofilling') === 'true') {
			return;
		}
		
		var viewportOffset = event.target.getBoundingClientRect();
		// Remove SNAutofill styling when user modifies the field
		if (event.target && event.target.classList) {
			event.target.classList.remove("SNAutofill");
		}
		if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.autoFill) {
			window.webkit.messageHandlers.autoFill.postMessage(["onUsernameChange", event.target.value, {"top": viewportOffset.bottom, "left": viewportOffset.left}]);
		}
	}
	
	onPasswordChange(event) {
		// Skip if we're in the middle of autofilling to avoid interference
		if (event.target && event.target.getAttribute('data-autofilling') === 'true') {
			return;
		}
		
		var viewportOffset = event.target.getBoundingClientRect();
		// Remove SNAutofill styling when user modifies the field
		if (event.target && event.target.classList) {
			event.target.classList.remove("SNAutofill");
		}
		if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.autoFill) {
			window.webkit.messageHandlers.autoFill.postMessage(["onPasswordChange", event.target.value, {"top": viewportOffset.bottom, "left": viewportOffset.left}]);
		}
	}
	
	onKeyDown(event) {
		// Remove autofill styling as soon as user starts typing
		// But don't interfere with the actual typing - let React handle it naturally
		if (event.target && event.target.classList && event.target.classList.contains("SNAutofill")) {
			event.target.classList.remove("SNAutofill");
		}
	}
	
	onKeyUp(event) {
		if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.autoFill) {
			if (event.keyCode === 13) {
				window.webkit.messageHandlers.autoFill.postMessage(["onKeyUp", 13]);
				event.preventDefault();
			} else if (event.keyCode === 27) {
				window.webkit.messageHandlers.autoFill.postMessage(["onKeyUp", 27]);
			} else if (event.key == "ArrowUp") {
				window.webkit.messageHandlers.autoFill.postMessage(["onKeyUp", -1]);
			} else if (event.key == "ArrowDown") {
				window.webkit.messageHandlers.autoFill.postMessage(["onKeyUp", 1]);
			}
		}
	}
	
	dispatchKeyboardEvent(element, eventName, keyCode) {
	  var event = document.createEvent("KeyboardEvent");
	  event.initKeyboardEvent(eventName, true, true, window, 0, 0, 0, 0, 0, keyCode);
	  element.dispatchEvent(event);
	}
}

// Skip autofill DOM observation on hosts that never present login forms in
// the main content area but do churn the DOM aggressively (YouTube's Polymer
// element updates fire thousands of mutations on Home/Watch). Running the
// observer + event delegation there is pure overhead.
var __snAutofillHost = (typeof location !== 'undefined' && location.host) ? location.host.toLowerCase() : '';
var __snAutofillSkip = /(?:^|\.)(youtube\.com|youtube-nocookie\.com|youtu\.be|ytimg\.com|googlevideo\.com)$/.test(__snAutofillHost);
if (!__snAutofillSkip) {
// Optimized DOM observer - only watch for relevant changes
snAPIHelper.observer = new MutationObserver(function(mutationsList, observer) {
	let shouldUpdate = false;
	
	for (let mutation of mutationsList) {
		// Only care about added nodes and specific attribute changes
		if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
			for (let node of mutation.addedNodes) {
				if (node.nodeType === Node.ELEMENT_NODE) {
					// Check if added node contains form inputs
					if (node.tagName === 'INPUT' || node.tagName === 'FORM' || 
						node.querySelector('input[type="password"], input[type="text"], input[type="email"]')) {
						shouldUpdate = true;
						break;
					}
				}
			}
		} else if (mutation.type === 'attributes' && 
				   mutation.target.tagName === 'INPUT' && 
				   (mutation.attributeName === 'type' || mutation.attributeName === 'name')) {
			shouldUpdate = true;
		}
		
		if (shouldUpdate) break;
	}
	
	if (shouldUpdate) {
		snAPIHelper.autoFill.domUpdatedThrottled();
	}
});

// Only observe specific changes that matter for form detection
snAPIHelper.observer.observe(document, { 
	childList: true, 
	subtree: true,
	attributes: true,
	attributeFilter: ['type', 'name'] // Only watch for type/name changes on inputs
});

// Initial scan on page load to detect existing forms
if (document.readyState === 'loading') {
	document.addEventListener('DOMContentLoaded', function() {
		snAPIHelper.autoFill.domUpdated();
	});
} else {
	// DOM already loaded, scan immediately
	snAPIHelper.autoFill.domUpdated();
}

// Also scan after full page load (for SPAs and delayed forms)
window.addEventListener('load', function() {
	setTimeout(function() {
		snAPIHelper.autoFill.domUpdated();
	}, 500); // Small delay for dynamic content
});
} // end __snAutofillSkip guard
} catch (e) {
    // Intentional early-exit signal for video hosts.
    if (e !== '__unite_snapihelper_skip') {
        throw e;
    }
}
