-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathview-hierarchy.ts
More file actions
132 lines (109 loc) · 3.46 KB
/
view-hierarchy.ts
File metadata and controls
132 lines (109 loc) · 3.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import type { Attachment, Event, EventHint, ViewHierarchyData, ViewHierarchyWindow } from '@sentry/core';
import { defineIntegration, getComponentName } from '@sentry/core';
import { WINDOW } from '../helpers';
interface OnElementArgs {
/**
* The element being processed.
*/
element: HTMLElement;
/**
* Lowercase tag name of the element.
*/
tagName: string;
/**
* The component name of the element.
*/
componentName?: string;
}
interface Options {
/**
* Whether to attach the view hierarchy to the event.
*
* Default: Always attach.
*/
shouldAttach?: (event: Event, hint: EventHint) => boolean;
/**
* A function that returns the root element to start walking the DOM from.
*
* Default: `window.document.body`
*/
rootElement?: () => HTMLElement | undefined;
/**
* Called for each HTMLElement as we walk the DOM.
*
* Return an object to include the element with any additional properties.
* Return `skip` to exclude the element and its children.
* Return `children` to skip the element but include its children.
*/
onElement?: (prop: OnElementArgs) => Record<string, string | number | boolean> | 'skip' | 'children';
}
/**
* An integration to include a view hierarchy attachment which contains the DOM.
*/
export const viewHierarchyIntegration = defineIntegration((options: Options = {}) => {
const skipHtmlTags = ['script'];
/** Walk an element */
function walk(element: HTMLElement, windows: ViewHierarchyWindow[]): void {
// With Web Components, we need to walk into shadow DOMs
const children = 'shadowRoot' in element && element.shadowRoot ? element.shadowRoot.children : element.children;
for (const child of children) {
if (!(child instanceof HTMLElement)) {
continue;
}
const componentName = getComponentName(child) || undefined;
const tagName = child.tagName.toLowerCase();
if (skipHtmlTags.includes(tagName)) {
continue;
}
const result = options.onElement?.({ element: child, componentName, tagName }) || {};
if (result === 'skip') {
continue;
}
// Skip this element but include its children
if (result === 'children') {
walk(child, windows);
continue;
}
const { x, y, width, height } = child.getBoundingClientRect();
const window: ViewHierarchyWindow = {
identifier: (child.id || undefined) as string,
type: componentName || tagName,
visible: true,
alpha: 1,
height,
width,
x,
y,
...result,
};
const children: ViewHierarchyWindow[] = [];
window.children = children;
// Recursively walk the children
walk(child, window.children);
windows.push(window);
}
}
return {
name: 'ViewHierarchy',
processEvent: (event, hint) => {
if (options.shouldAttach?.(event, hint) === false) {
return event;
}
const root: ViewHierarchyData = {
rendering_system: 'DOM',
positioning: 'absolute',
windows: [],
};
walk(options.rootElement?.() || WINDOW.document.body, root.windows);
const attachment: Attachment = {
filename: 'view-hierarchy.json',
attachmentType: 'event.view_hierarchy',
contentType: 'application/json',
data: JSON.stringify(root),
};
hint.attachments = hint.attachments || [];
hint.attachments.push(attachment);
return event;
},
};
});