(function () {
'use strict';
if (window.__lingxiaBrowserContextMenu) return;
window.__lingxiaBrowserContextMenu = true;
var EXT_TO_MIME = {
jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png',
gif: 'image/gif', webp: 'image/webp', svg: 'image/svg+xml',
avif: 'image/avif', bmp: 'image/bmp', tiff: 'image/tiff', ico: 'image/x-icon',
mp4: 'video/mp4', webm: 'video/webm', ogv: 'video/ogg', mov: 'video/quicktime',
mp3: 'audio/mpeg', m4a: 'audio/mp4', ogg: 'audio/ogg', wav: 'audio/wav',
pdf: 'application/pdf',
};
var MIME_TO_EXT = {
'image/jpeg': 'jpg', 'image/png': 'png', 'image/gif': 'gif',
'image/webp': 'webp', 'image/svg+xml': 'svg', 'image/avif': 'avif',
'image/bmp': 'bmp', 'image/tiff': 'tiff', 'image/x-icon': 'ico',
'video/mp4': 'mp4', 'video/webm': 'webm', 'video/ogg': 'ogv',
'video/quicktime': 'mov', 'audio/mpeg': 'mp3', 'audio/mp4': 'm4a',
'audio/ogg': 'ogg', 'audio/wav': 'wav', 'application/pdf': 'pdf',
};
var FORMAT_QUERY_KEYS = ['format', 'fmt', 'type', 'ext', 'fm', 'f', 'filetype', 'img_type'];
function normalizeUrl(raw) {
if (typeof raw !== 'string') return null;
var trimmed = raw.trim();
if (!trimmed) return null;
try {
var resolved = new URL(trimmed, document.baseURI).href;
return /^https?:\/\//i.test(resolved) ? resolved : null;
} catch (_) {
return null;
}
}
function extFromPathname(pathname) {
var last = (pathname || '').split('/').pop() || '';
var dot = last.lastIndexOf('.');
if (dot < 0) return null;
return last.slice(dot + 1).toLowerCase().split(/[?#]/)[0] || null;
}
function mimeFromUrl(url) {
try {
var parsed = new URL(url);
for (var i = 0; i < FORMAT_QUERY_KEYS.length; i++) {
var val = (parsed.searchParams.get(FORMAT_QUERY_KEYS[i]) || '').toLowerCase();
if (!val) continue;
if (EXT_TO_MIME[val]) return EXT_TO_MIME[val]; if (MIME_TO_EXT[val]) return val; }
var ext = extFromPathname(parsed.pathname);
if (ext && EXT_TO_MIME[ext]) return EXT_TO_MIME[ext];
} catch (_) {}
return null;
}
function mimeFromElement(node) {
if (!node) return null;
var typeAttr = node.getAttribute && node.getAttribute('type');
if (typeAttr) {
var t = typeAttr.toLowerCase();
if (MIME_TO_EXT[t]) return t;
}
return null;
}
function withExtension(filename, mime) {
if (!filename || !mime) return filename;
var expectedExt = MIME_TO_EXT[mime];
if (!expectedExt) return filename;
var dot = filename.lastIndexOf('.');
if (dot >= 0) {
var currentExt = filename.slice(dot + 1).toLowerCase();
var normalised = currentExt === 'jpeg' ? 'jpg' : currentExt;
if (normalised === expectedExt) return filename;
return filename.slice(0, dot) + '.' + expectedExt;
}
return filename + '.' + expectedExt;
}
function inferFilename(url, mime, fallback) {
var base = null;
try {
var last = new URL(url).pathname.split('/').pop();
if (last) base = decodeURIComponent(last).split(/[?#]/)[0] || null;
} catch (_) {}
var name = fallback || base || null;
if (!name || !mime) return name;
return withExtension(name, mime);
}
function titleFor(url, kind) {
if (/\.pdf(?:$|[?#])/i.test(url)) return 'Download PDF';
switch (kind) {
case 'image': return 'Download Image';
case 'media': return 'Download Media';
case 'link': return 'Download Linked File';
default: return 'Download File';
}
}
function candidate(url, kind, fallbackName, mimeOverride) {
if (!url) return null;
var mime = mimeOverride || mimeFromUrl(url) || null;
return {
url: url,
suggestedFilename: inferFilename(url, mime, fallbackName || null),
menuTitle: titleFor(url, kind),
};
}
function backgroundImageUrl(node) {
try {
var style = window.getComputedStyle(node);
var raw = style && typeof style.backgroundImage === 'string' ? style.backgroundImage : '';
var match = /url\((['"]?)(.*?)\1\)/i.exec(raw);
return match ? normalizeUrl(match[2]) : null;
} catch (_) {
return null;
}
}
function datasetUrl(node) {
if (!node || !node.dataset) return null;
var entries = Object.entries(node.dataset);
for (var i = 0; i < entries.length; i++) {
var key = entries[i][0], value = entries[i][1];
if (!value || typeof value !== 'string') continue;
if (/(url|src|image|media|poster)/i.test(key)) {
var normalized = normalizeUrl(value);
if (normalized) return normalized;
}
}
return null;
}
function attributeUrl(node, names) {
for (var i = 0; i < names.length; i++) {
var value = node.getAttribute && node.getAttribute(names[i]);
var normalized = normalizeUrl(value);
if (normalized) return normalized;
}
return null;
}
function extractCandidate(node) {
if (!node || node.nodeType !== 1) return null;
var tag = node.tagName;
if (tag === 'IMG') {
var imgUrl = normalizeUrl(node.currentSrc || node.src || node.getAttribute('src') || node.getAttribute('data-src'));
if (imgUrl) return candidate(imgUrl, 'image', node.getAttribute('download') || null, null);
}
if (tag === 'VIDEO' || tag === 'AUDIO') {
var mediaUrl = normalizeUrl(node.currentSrc || node.src || node.getAttribute('src'));
if (mediaUrl) return candidate(mediaUrl, 'media', null, null);
var posterUrl = normalizeUrl(node.getAttribute('poster'));
if (posterUrl) return candidate(posterUrl, 'image', null, null);
}
if (tag === 'A') {
var linkUrl = normalizeUrl(node.href || node.getAttribute('href'));
if (linkUrl) return candidate(linkUrl, 'link', node.getAttribute('download') || null, null);
}
if (tag === 'SOURCE') {
var sourceUrl = normalizeUrl(node.src || node.getAttribute('src'));
if (sourceUrl) return candidate(sourceUrl, 'media', null, mimeFromElement(node));
}
var attrNames = ['src', 'href', 'data-src', 'data-url', 'data-image-url', 'data-fullimage', 'data-media-url', 'poster'];
var attrUrl = attributeUrl(node, attrNames);
if (attrUrl) {
var inferredKind = /(jpg|jpeg|png|gif|webp|bmp|svg|avif)(?:$|[?#])/i.test(attrUrl) ? 'image' : 'file';
return candidate(attrUrl, inferredKind, node.getAttribute && node.getAttribute('download'), null);
}
var bgUrl = backgroundImageUrl(node);
if (bgUrl) return candidate(bgUrl, 'image', null, null);
var dsUrl = datasetUrl(node);
if (dsUrl) {
var dsKind = /(jpg|jpeg|png|gif|webp|bmp|svg|avif)(?:$|[?#])/i.test(dsUrl) ? 'image' : 'file';
return candidate(dsUrl, dsKind, null, null);
}
if (typeof node.querySelector === 'function') {
var child = node.querySelector('img, video, audio, source, a[href], [src], [data-src]');
if (child) return extractCandidate(child);
}
return null;
}
document.addEventListener('contextmenu', function (e) {
var node = e.target;
var visited = new Set();
while (node && node.nodeType === 1) {
if (visited.has(node)) break;
visited.add(node);
var found = extractCandidate(node);
if (found) {
try {
window.webkit.messageHandlers.LingXia.postMessage(JSON.stringify({
type: 'browserContextMenuDownload',
payload: found
}));
} catch (_) {}
return;
}
node = node.parentElement;
}
}, true);
})();