Skip to main content
Glama

Stealth Browser MCP

by vibheksoni
comprehensive_element_extractor.js10.4 kB
(function() { const selector = "$SELECTOR$"; const includeChildren = $INCLUDE_CHILDREN$; /** * Extracts comprehensive information from a single DOM element. * @param {Element} element - The DOM element to extract data from. * @returns {Object} An object containing: * - html: {Object} HTML details (outerHTML, innerHTML, tagName, id, className, attributes) * - styles: {Object} Computed CSS styles * - eventListeners: {Array} Event listeners detected by multiple methods * - cssRules: {Array} CSS rules matching the element * - pseudoElements: {Object} Styles and content for pseudo-elements * - animations: {Object} Animation, transition, and transform properties * - fonts: {Object} Font family, size, and weight */ async function extractSingleElement(element) { const computedStyles = window.getComputedStyle(element); const styles = {}; for (let i = 0; i < computedStyles.length; i++) { const prop = computedStyles[i]; styles[prop] = computedStyles.getPropertyValue(prop); } const html = { outerHTML: element.outerHTML, innerHTML: element.innerHTML, tagName: element.tagName, id: element.id, className: element.className, attributes: Array.from(element.attributes).map(attr => ({ name: attr.name, value: attr.value })) }; const eventListeners = []; for (const attr of element.attributes) { if (attr.name.startsWith('on')) { eventListeners.push({ type: attr.name.substring(2), handler: attr.value, source: 'inline' }); } } if (typeof getEventListeners === 'function') { try { const listeners = getEventListeners(element); for (const eventType in listeners) { listeners[eventType].forEach(listener => { eventListeners.push({ type: eventType, handler: listener.listener.toString().substring(0, 200) + '...', useCapture: listener.useCapture, passive: listener.passive, once: listener.once, source: 'addEventListener' }); }); } } catch (e) {} } const commonEvents = ['click', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'focus', 'blur', 'change', 'input', 'submit']; commonEvents.forEach(eventType => { if (element[`on${eventType}`] && typeof element[`on${eventType}`] === 'function') { const handler = element[`on${eventType}`].toString(); if (!eventListeners.some(l => l.type === eventType && l.source === 'inline')) { eventListeners.push({ type: eventType, handler: handler, handlerPreview: handler.substring(0, 100) + (handler.length > 100 ? '...' : ''), source: 'property' }); } } }); try { const reactKeys = Object.keys(element).filter(key => key.startsWith('__react')); if (reactKeys.length > 0) { const reactDetails = []; reactKeys.forEach(key => { try { const reactData = element[key]; if (reactData && reactData.memoizedProps) { const props = reactData.memoizedProps; Object.keys(props).forEach(prop => { if (prop.startsWith('on') && typeof props[prop] === 'function') { const funcStr = props[prop].toString(); reactDetails.push({ event: prop.substring(2).toLowerCase(), handler: funcStr, handlerPreview: funcStr.substring(0, 100) + (funcStr.length > 100 ? '...' : '') }); } }); } } catch (e) {} }); eventListeners.push({ type: 'framework', handler: 'React event handlers detected', source: 'react', details: `Found ${reactKeys.length} React properties`, reactHandlers: reactDetails }); } } catch (e) {} try { if (element._events || element.__events) { const events = element._events || element.__events; Object.keys(events).forEach(eventType => { const handlers = Array.isArray(events[eventType]) ? events[eventType] : [events[eventType]]; handlers.forEach(handler => { if (typeof handler === 'function') { const funcStr = handler.toString(); eventListeners.push({ type: eventType, handler: funcStr, handlerPreview: funcStr.substring(0, 100) + (funcStr.length > 100 ? '...' : ''), source: 'registry' }); } }); }); } } catch (e) {} const cssRules = []; const sheets = document.styleSheets; for (let i = 0; i < sheets.length; i++) { try { const rules = sheets[i].cssRules || sheets[i].rules; for (let j = 0; j < rules.length; j++) { const rule = rules[j]; if (rule.type === 1 && element.matches(rule.selectorText)) { cssRules.push({ selector: rule.selectorText, css: rule.style.cssText, source: sheets[i].href || 'inline' }); } } } catch (e) {} } const pseudoElements = {}; ['::before', '::after', '::first-line', '::first-letter'].forEach(pseudo => { const pseudoStyles = window.getComputedStyle(element, pseudo); const content = pseudoStyles.getPropertyValue('content'); if (content && content !== 'none') { pseudoElements[pseudo] = { content: content, styles: {} }; for (let i = 0; i < pseudoStyles.length; i++) { const prop = pseudoStyles[i]; pseudoElements[pseudo].styles[prop] = pseudoStyles.getPropertyValue(prop); } } }); const animations = { animation: styles.animation || 'none', transition: styles.transition || 'none', transform: styles.transform || 'none' }; const fonts = { computed: styles.fontFamily, fontSize: styles.fontSize, fontWeight: styles.fontWeight }; return { html, styles, eventListeners, cssRules, pseudoElements, animations, fonts }; } /** * Calculates the depth of a child element relative to a parent element. * @param {Element} child - The child DOM element. * @param {Element} parent - The parent DOM element. * @returns {number} The depth (number of levels) between child and parent. */ function getElementDepth(child, parent) { let depth = 0; let current = child; while (current && current !== parent) { depth++; current = current.parentElement; } return depth; } /** * Generates a CSS-like path from a child element to a parent element. * @param {Element} child - The child DOM element. * @param {Element} parent - The parent DOM element. * @returns {string} The path string (e.g., "div > span[1] > a"). */ function getElementPath(child, parent) { const path = []; let current = child; while (current && current !== parent) { const tag = current.tagName.toLowerCase(); const index = Array.from(current.parentElement.children) .filter(el => el.tagName === current.tagName) .indexOf(current); path.unshift(index > 0 ? `${tag}[${index}]` : tag); current = current.parentElement; } return path.join(' > '); } const element = document.querySelector(selector); if (!element) return { error: 'Element not found' }; const result = { element: null, children: [] }; result.element = extractSingleElement(element); if (includeChildren) { let targetElement = element; const children = element.querySelectorAll('*'); if (children.length === 0 && element.parentElement) { targetElement = element.parentElement; result.extractedFrom = 'parent'; result.originalElement = extractSingleElement(element); result.element = extractSingleElement(targetElement); } const allChildren = targetElement.querySelectorAll('*'); for (let i = 0; i < allChildren.length; i++) { const childData = extractSingleElement(allChildren[i]); childData.depth = getElementDepth(allChildren[i], targetElement); childData.path = getElementPath(allChildren[i], targetElement); if (allChildren[i] === element) { childData.isOriginallySelected = true; } result.children.push(childData); } } return result; })();

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vibheksoni/stealth-browser-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server