Skip to content

Commit db24adb

Browse files
committed
refactor(extension): remove legacy element serializer
1 parent 4fb84d0 commit db24adb

File tree

2 files changed

+1
-351
lines changed

2 files changed

+1
-351
lines changed

packages/chrome-extension/src/utils/element.ts

Lines changed: 1 addition & 332 deletions
Original file line numberDiff line numberDiff line change
@@ -1,340 +1,9 @@
11
// Disable ESLint rule for underscore dangle usage in this file (React internals)
22
/* eslint-disable no-underscore-dangle */
33

4-
import {
5-
ComponentInfo,
6-
CSSDetailLevel,
7-
CSSProperties,
8-
DEFAULT_CSS_LEVEL,
9-
DEFAULT_TEXT_DETAIL,
10-
ElementPosition,
11-
TargetedElement,
12-
TextDetailLevel,
13-
TextSnapshots,
14-
RawPointedDOMElement,
15-
} from '@mcp-pointer/shared/types';
16-
import { CSS_LEVEL_FIELD_MAP } from '@mcp-pointer/shared/detail';
4+
import { RawPointedDOMElement } from '@mcp-pointer/shared/types';
175
import logger from './logger';
186

19-
export interface ReactSourceInfo {
20-
fileName: string;
21-
lineNumber?: number;
22-
columnNumber?: number;
23-
}
24-
25-
export interface ElementSerializationOptions {
26-
textDetail?: TextDetailLevel;
27-
cssLevel?: CSSDetailLevel;
28-
}
29-
30-
function toKebabCase(property: string): string {
31-
return property
32-
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
33-
.replace(/_/g, '-')
34-
.toLowerCase();
35-
}
36-
37-
function toCamelCase(property: string): string {
38-
return property
39-
.replace(/^-+/, '')
40-
.replace(/-([a-z])/g, (_, char: string) => char.toUpperCase());
41-
}
42-
43-
function getStyleValue(style: CSSStyleDeclaration, property: string): string | undefined {
44-
const camelValue = (style as any)[property];
45-
if (typeof camelValue === 'string' && camelValue.trim().length > 0) {
46-
return camelValue;
47-
}
48-
49-
const kebab = toKebabCase(property);
50-
const value = style.getPropertyValue(kebab);
51-
if (typeof value === 'string' && value.trim().length > 0) {
52-
return value;
53-
}
54-
55-
return undefined;
56-
}
57-
58-
function extractFullCSSProperties(style: CSSStyleDeclaration): Record<string, string> {
59-
const properties: Record<string, string> = {};
60-
61-
for (let i = 0; i < style.length; i += 1) {
62-
const property = style.item(i);
63-
64-
if (property && !property.startsWith('-')) {
65-
const value = style.getPropertyValue(property);
66-
if (typeof value === 'string' && value.trim().length > 0) {
67-
const camel = toCamelCase(property);
68-
properties[camel] = value;
69-
}
70-
}
71-
}
72-
73-
return properties;
74-
}
75-
76-
function getElementCSSProperties(
77-
style: CSSStyleDeclaration,
78-
cssLevel: CSSDetailLevel,
79-
fullCSS: Record<string, string>,
80-
): CSSProperties | undefined {
81-
if (cssLevel === 0) {
82-
return undefined;
83-
}
84-
85-
if (cssLevel === 3) {
86-
return fullCSS;
87-
}
88-
89-
const fields = CSS_LEVEL_FIELD_MAP[cssLevel];
90-
const properties: CSSProperties = {};
91-
92-
fields.forEach((property) => {
93-
const value = getStyleValue(style, property);
94-
if (value !== undefined) {
95-
properties[property] = value;
96-
}
97-
});
98-
99-
return properties;
100-
}
101-
102-
function collectTextVariants(element: HTMLElement): TextSnapshots {
103-
const visible = element.innerText || '';
104-
const full = element.textContent || visible;
105-
106-
return {
107-
visible,
108-
full,
109-
};
110-
}
111-
112-
function resolveTextByDetail(variants: TextSnapshots, detail: TextDetailLevel): string | undefined {
113-
if (detail === 'none') {
114-
return undefined;
115-
}
116-
117-
if (detail === 'visible') {
118-
return variants.visible;
119-
}
120-
121-
return variants.full || variants.visible;
122-
}
123-
124-
/**
125-
* Get source file information from a DOM element's React component
126-
*/
127-
export function getSourceFromElement(element: HTMLElement): ReactSourceInfo | null {
128-
// Find React Fiber key
129-
const fiberKey = Object.keys(element).find((key) => key.startsWith('__reactFiber$')
130-
|| key.startsWith('__reactInternalInstance$'));
131-
132-
if (!fiberKey) return null;
133-
134-
const fiber = (element as any)[fiberKey];
135-
if (!fiber) return null;
136-
137-
// Walk up fiber tree to find component fiber (skip DOM fibers)
138-
let componentFiber = fiber;
139-
while (componentFiber && typeof componentFiber.type === 'string') {
140-
componentFiber = componentFiber.return;
141-
}
142-
143-
if (!componentFiber) return null;
144-
145-
// Try multiple source locations (React version differences)
146-
// React 18: _debugSource
147-
if (componentFiber._debugSource) {
148-
return {
149-
fileName: componentFiber._debugSource.fileName,
150-
lineNumber: componentFiber._debugSource.lineNumber,
151-
columnNumber: componentFiber._debugSource.columnNumber,
152-
};
153-
}
154-
155-
// React 19: _debugInfo (often null)
156-
if (componentFiber._debugInfo) {
157-
return componentFiber._debugInfo;
158-
}
159-
160-
// Babel plugin: __source on element type
161-
if (componentFiber.elementType?.__source) {
162-
return {
163-
fileName: componentFiber.elementType.__source.fileName,
164-
lineNumber: componentFiber.elementType.__source.lineNumber,
165-
columnNumber: componentFiber.elementType.__source.columnNumber,
166-
};
167-
}
168-
169-
// Alternative: _owner chain
170-
if (componentFiber._debugOwner?._debugSource) {
171-
return {
172-
fileName: componentFiber._debugOwner._debugSource.fileName,
173-
lineNumber: componentFiber._debugOwner._debugSource.lineNumber,
174-
columnNumber: componentFiber._debugOwner._debugSource.columnNumber,
175-
};
176-
}
177-
178-
// Check pendingProps for __source
179-
if (componentFiber.pendingProps?.__source) {
180-
return {
181-
fileName: componentFiber.pendingProps.__source.fileName,
182-
lineNumber: componentFiber.pendingProps.__source.lineNumber,
183-
columnNumber: componentFiber.pendingProps.__source.columnNumber,
184-
};
185-
}
186-
187-
return null;
188-
}
189-
190-
/**
191-
* Extract React Fiber information from an element
192-
*/
193-
export function getReactFiberInfo(element: HTMLElement): ComponentInfo | undefined {
194-
try {
195-
// Use comprehensive source detection
196-
const sourceInfo = getSourceFromElement(element);
197-
198-
// Also get component name
199-
const fiberKey = Object.keys(element).find((key) => key.startsWith('__reactFiber$')
200-
|| key.startsWith('__reactInternalInstance$'));
201-
202-
if (fiberKey) {
203-
const fiber = (element as any)[fiberKey];
204-
if (fiber) {
205-
// Find component fiber
206-
let componentFiber = fiber;
207-
while (componentFiber && typeof componentFiber.type === 'string') {
208-
componentFiber = componentFiber.return;
209-
}
210-
211-
if (componentFiber && componentFiber.type && typeof componentFiber.type === 'function') {
212-
const componentName = componentFiber.type.displayName
213-
|| componentFiber.type.name
214-
|| 'Unknown';
215-
216-
let sourceFile: string | undefined;
217-
if (sourceInfo) {
218-
const fileName = sourceInfo.fileName.split('/').pop() || sourceInfo.fileName;
219-
sourceFile = sourceInfo.lineNumber
220-
? `${fileName}:${sourceInfo.lineNumber}`
221-
: fileName;
222-
}
223-
224-
const result = {
225-
name: componentName,
226-
sourceFile,
227-
framework: 'react' as const,
228-
};
229-
230-
logger.debug('🧬 Found React Fiber info:', result);
231-
return result;
232-
}
233-
}
234-
}
235-
236-
return undefined;
237-
} catch (error) {
238-
logger.error('🚨 Error extracting Fiber info:', error);
239-
return undefined;
240-
}
241-
}
242-
243-
/**
244-
* Extract all attributes from an HTML element
245-
*/
246-
export function getElementAttributes(element: HTMLElement): Record<string, string> {
247-
const attributes: Record<string, string> = {};
248-
for (let i = 0; i < element.attributes.length; i += 1) {
249-
const attr = element.attributes[i];
250-
attributes[attr.name] = attr.value;
251-
}
252-
return attributes;
253-
}
254-
255-
/**
256-
* Generate a CSS selector for an element
257-
*/
258-
export function generateSelector(element: HTMLElement): string {
259-
let selector = element.tagName.toLowerCase();
260-
if (element.id) selector += `#${element.id}`;
261-
if (element.className) {
262-
const classNameStr = typeof element.className === 'string'
263-
? element.className
264-
: (element.className as any).baseVal || '';
265-
const classes = classNameStr.split(' ').filter((c: string) => c.trim());
266-
if (classes.length > 0) selector += `.${classes.join('.')}`;
267-
}
268-
return selector;
269-
}
270-
271-
/**
272-
* Get element position relative to the page
273-
*/
274-
export function getElementPosition(element: HTMLElement): ElementPosition {
275-
const rect = element.getBoundingClientRect();
276-
return {
277-
x: rect.left + window.scrollX,
278-
y: rect.top + window.scrollY,
279-
width: rect.width,
280-
height: rect.height,
281-
};
282-
}
283-
284-
/**
285-
* Extract CSS classes from an element as an array
286-
*/
287-
export function getElementClasses(element: HTMLElement): string[] {
288-
if (!element.className) return [];
289-
const classNameStr = typeof element.className === 'string'
290-
? element.className
291-
: (element.className as any).baseVal || '';
292-
return classNameStr.split(' ').filter((c: string) => c.trim());
293-
}
294-
295-
export function adaptTargetToElement(
296-
element: HTMLElement,
297-
options: ElementSerializationOptions = {},
298-
): TargetedElement {
299-
const textDetail = options.textDetail ?? DEFAULT_TEXT_DETAIL;
300-
const cssLevel = options.cssLevel ?? DEFAULT_CSS_LEVEL;
301-
302-
const textVariants = collectTextVariants(element);
303-
const resolvedText = resolveTextByDetail(textVariants, textDetail);
304-
305-
const computedStyle = window.getComputedStyle(element);
306-
const fullCSS = extractFullCSSProperties(computedStyle);
307-
const cssProperties = getElementCSSProperties(computedStyle, cssLevel, fullCSS);
308-
309-
const target: TargetedElement = {
310-
selector: generateSelector(element),
311-
tagName: element.tagName,
312-
id: element.id || undefined,
313-
classes: getElementClasses(element),
314-
attributes: getElementAttributes(element),
315-
position: getElementPosition(element),
316-
cssLevel,
317-
cssProperties,
318-
cssComputed: Object.keys(fullCSS).length > 0 ? fullCSS : undefined,
319-
componentInfo: getReactFiberInfo(element),
320-
timestamp: Date.now(),
321-
url: window.location.href,
322-
textDetail,
323-
textVariants,
324-
textContent: textVariants.full,
325-
};
326-
327-
if (resolvedText !== undefined) {
328-
target.innerText = resolvedText;
329-
}
330-
331-
if (!target.textContent && textVariants.visible) {
332-
target.textContent = textVariants.visible;
333-
}
334-
335-
return target;
336-
}
337-
3387
/**
3398
* Extract raw React Fiber from an element (if present)
3409
*/

packages/chrome-extension/src/utils/types.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

0 commit comments

Comments
 (0)